为什么要有线程锁?
多个线程之间会抢夺资源,所以有可能一个线程执行到一半,就被另一个线程抢夺了资源,这样就会造成线程的不安全,为了保证线程的安全性,我们可以使用线程锁来解决这个问题。
比如下面的例子,正常应该打印出两句“今天你学习了吗?”,但是有可能thread1刚执行到一半,thread2就抢夺了资源,所以就会导致执行顺序错乱:
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
myThread1.start();
myThread2.start();
}
static class MyThread extends Thread {
@Override
public void run() {
for (char c : "今天你学习了吗?".toCharArray()) {
System.out.print(c);
}
}
}
}
执行结果: 今天你学习今天你学了吗?习了吗? //顺序错乱❌
怎么解决线程不安全?
上面的例子会导致线程不安全,我们可以使用synchronized来解决:
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
myThread1.start();
myThread2.start();
}
static class MyThread extends Thread {
@Override
public void run() {
//这里加锁✅
synchronized (ThreadDemo.class){
for (char c : "今天你学习了吗?".toCharArray()) {
System.out.print(c);
}
}
}
}
}
执行结果: 今天你学习了吗?今天你学习了吗? //顺序正确✅
Synchronized详解
上面代码我们只需要加上synchronized就可以解决线程安全问题,synchronized也被我们成为“同步锁”,synchronized是一个关键字,可以修饰在成员方法也可以单独写成同步代码块,被synchronized修饰的代码块中,同一时刻最多只能有一个线程执行代码块中的代码。
从上面图中可以看到,为了防止多个线程同时修改代码影响代码的安全性,我们用锁将一部分代码锁了起来,多个线程之间要抢夺锁,同时只有一个线程可以拿到锁获得执行权限,其他线程只能在外面等着这个线程释放锁,然后再次抢夺锁获得执行权限。这样就可以解决线程不安全的问题。
对象锁
方法一
private synchronized void count(){
...
}
方法二
private void count(){
synchronized (this){
...
}
}
方法三
private Object mObject = new Object();
private void count(){
synchronized (mObject){
...
}
}
类锁
方法一
private synchronized static void count(){
...
}
方法二
private static void count(){
synchronized (ThreadDemo.class){
...
}
}
方法三
private static Object mObject = new Object();
private void count(){
synchronized (mObject){
...
}
}
总结
对象锁:
对象锁锁的是对象的实例,多线程使用的时候需要保证是同一个实例,否则加锁将没有意义。
类锁:
类锁锁的是类的唯一class对象,静态方法加synchronized默认就是类锁。
注意:
类锁和对象锁可以同时使用,互不干扰。
wait()和notify()、notifyAll()
wait():
当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态,直到被notify()/notifyAll()唤醒。
notify():
唤醒一个处于wait状态的线程。
notifyAll():
唤醒所有处于wait状态的线程。
两个线程交替打印0~100
利用wait()和notify()以及synchronized可以很轻松实现这个功能:
public class ThreadDemo {
private static int num;
private static Object sObject = new Object();
public static void main(String[] args) {
new Thread(new MyRunnable(),"线程1").start();
new Thread(new MyRunnable(),"线程2").start();
}
static class MyRunnable implements Runnable {
@Override
public void run() {
while (true) {
synchronized (sObject){
System.out.println(Thread.currentThread().getName() + ": "+ num++);
sObject.notify();
if (num>=100) return;
try {
sObject.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
打印:
线程1: 0
线程2: 1
线程1: 2
...
线程2: 99
线程1: 100