image-20221004203914215
image-20221004203914215

线程之间的共享变量(比如之前悬念中的value变量)存储在主内存(main memory)中,每个线程都有一个私有的工作内存(本地内存),工作内存中存储了该线程以读/写共享变量的副本。

private static int value = 0;

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        for (int i = 0; i < 10000; i++) value++;
        System.out.println("线程1完成");
    });
    Thread t2 = new Thread(() -> {
        for (int i = 0; i < 10000; i++) value++;
        System.out.println("线程2完成");
    });
    t1.start();
    t2.start();
    Thread.sleep(1000);  //主线程停止1秒,保证两个线程执行完成
    System.out.println(value);
}

当两个线程同时读取value的时候,可能会同时拿到同样的值,而进行自增操作之后,也是同样的值,再写回主内存后,本来应该进行2次自增操作,实际上只执行了一次!

image-20221004204439553
image-20221004204439553

通过synchronized关键字来创造一个线程锁,首先我们来认识一下synchronized代码块,它需要在括号中填入一个内容,必须是一个对象或是一个类,我们在value自增操作外套上同步代码块:

private static int value = 0;

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        for (int i = 0; i < 10000; i++) {
            synchronized (Main.class){  //使用synchronized关键字创建同步代码块
                value++;
            }
        }
        System.out.println("线程1完成");
    });
    Thread t2 = new Thread(() -> {
        for (int i = 0; i < 10000; i++) {
            synchronized (Main.class){
                value++;
            }
        }
        System.out.println("线程2完成");
    });
    t1.start();
    t2.start();
    Thread.sleep(1000);  //主线程停止1秒,保证两个线程执行完成
    System.out.println(value);
}

注意:必须是使用同一个对象的锁

synchronized关键字也可以作用于方法上,调用此方法时也会获取锁,如果该方法是静态方法,那么就是以当前这个类作为锁

public class test {
    private static int value = 0;
    private static synchronized void add(){
        value++;
    }
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                add();
            }
            System.out.println("线程1完成");
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
               add();
            }
            System.out.println("线程2完成");
        });
        Thread t3 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                value++;
            }
            System.out.println("线程3完成");
        });
        t1.start();
        t2.start();
        t3.start();
        Thread.sleep(1000);  
        System.out.println(value);
    }
}

如果我们测试上面的代码,只启动t1​和t2​线程的话,value​的值是正常的,但是如果还启动了t3​的话因为没有同步,所以结果是随机的

这个时候在t3​的线程中给value​加锁再自增,因为add()​是test​类的静态方法,所以使用synchronized(test.class)​加锁

如果synchronized关键字作用于非static方法上时,则是以该类的实例化对象作为锁

public class test {
    private static int value = 0;
    private synchronized void add(){
        value++;
    }
    public static void main(String[] args) throws InterruptedException {
        test t = new test();//test的实例化对象
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                t.add();
            }
            System.out.println("线程1完成");
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
               t.add();
            }
            System.out.println("线程2完成");
        });
        Thread t3 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                synchronized (t) {
                    value++;
                }
            }
            System.out.println("线程3完成");
        });
        t1.start();
        t2.start();
        t3.start();
        Thread.sleep(1000);  //主线程停止1秒,保证两个线程执行完成
        System.out.println(value);
    }
}

wait()​和notify()​方法

wait()​、notify()​以及notifyAll()​,需要配合synchronized来使用

public static void main(String[] args) throws InterruptedException {
    Object o1 = new Object();
    Thread t1 = new Thread(() -> {
        synchronized (o1){
            try {
                System.out.println("开始等待");
                o1.wait();     //进入等待状态并释放锁
                System.out.println("等待结束!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
    Thread t2 = new Thread(() -> {
        synchronized (o1){
            System.out.println("开始唤醒!");
            o1.notify();     //唤醒处于等待状态的线程
          	for (int i = 0; i < 50; i++) {
               	System.out.println(i);   
            }
          	//唤醒后依然需要等待这里的锁释放之前等待的线程才能继续
        }
    });
    t1.start();
    Thread.sleep(1000);
    t2.start();
}

我们可以发现,对象的wait()​方法会暂时使得此线程进入等待状态,同时会释放当前代码块持有的锁,这时其他线程可以获取到此对象的锁,当其他线程调用对象的notify()​方法后,会唤醒刚才变成等待状态的线程(这时并没有立即释放锁)。注意,必须是在持有锁(同步代码块内部)的情况下使用,否则会抛出异常!

notifyAll其实和notify一样,也是用于唤醒,但是前者是唤醒所有调用wait()​后处于等待的线程,而后者是看运气随机选择一个。