https://redspider.gitbook.io/concurrent/di-yi-pian-ji-chu-pian/5#51-suo-yu-tong-bu

合理的使用Java多线程可以更好地利用服务器资源。一般来讲,线程内部有自己私有的线程上下文,互不干扰。但是当我们需要多个线程之间相互协作的时候,就需要我们掌握Java线程的通信方式。本文将介绍Java线程之间的几种通信原理。

5.1 锁与同步

在Java中,锁的概念都是基于对象的,所以我们又经常称它为对象锁。线程和锁的关系,我们可以用婚姻关系来理解。一个锁同一时间只能被一个线程持有。也就是说,一个锁如果和一个线程“结婚”(持有),那其他线程如果需要得到这个锁,就得等这个线程和这个锁“离婚”(释放)。

在我们的线程之间,有一个同步的概念。什么是同步呢,假如我们现在有2位正在抄暑假作业答案的同学:线程A和线程B。当他们正在抄的时候,老师突然来修改了一些答案,可能A和B最后写出的暑假作业就不一样。我们为了A,B能写出2本相同的暑假作业,我们就需要让老师先修改答案,然后A,B同学再抄。或者A,B同学先抄完,老师再修改答案。这就是线程A,线程B的线程同步。

可以以解释为:线程同步是线程之间按照一定的顺序执行。

为了达到线程同步,我们可以使用锁来实现它。

我们先来看看一个无锁的程序:

Copy

public class NoneLock {

    static class ThreadA implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println("Thread A " + i);
            }
        }
    }

    static class ThreadB implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println("Thread B " + i);
            }
        }
    }

    public static void main(String[] args) {
        new Thread(new ThreadA()).start();
        new Thread(new ThreadB()).start();
    }
}

执行这个程序,你会在控制台看到,线程A和线程B各自独立工作,输出自己的打印值。如下是我的电脑上某一次运行的结果。每一次运行结果都会不一样。

Copy

....
Thread A 48
Thread A 49
Thread B 0
Thread A 50
Thread B 1
Thread A 51
Thread A 52
....

那我现在有一个需求,我想等A先执行完之后,再由B去执行,怎么办呢?最简单的方式就是使用一个“对象锁”:

Copy

public class ObjectLock {
    private static Object lock = new Object();

    static class ThreadA implements Runnable {
        @Override
        public void run() {
            synchronized (lock) {
                for (int i = 0; i < 100; i++) {
                    System.out.println("Thread A " + i);
                }
            }
        }
    }

    static class ThreadB implements Runnable {
        @Override
        public void run() {
            synchronized (lock) {
                for (int i = 0; i < 100; i++) {
                    System.out.println("Thread B " + i);
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(new ThreadA()).start();
        Thread.sleep(10);
        new Thread(new ThreadB()).start();
    }
}

这里声明了一个名字为lock的对象锁。我们在ThreadAThreadB内需要同步的代码块里,都是用synchronized关键字加上了同一个对象锁lock

上文我们说到了,根据线程和锁的关系,同一时间只有一个线程持有一个锁,那么线程B就会等线程A执行完成后释放lock,线程B才能获得锁lock

这里在主线程里使用sleep方法睡眠了10毫秒,是为了防止线程B先得到锁。因为如果同时start,线程A和线程B都是出于就绪状态,操作系统可能会先让B运行。这样就会先输出B的内容,然后B执行完成之后自动释放锁,线程A再执行。