Skip to content

线程间通信

线程间通信的模型有两种:共享内存和消息传递。
场景: 两个线程,一个线程对当前数值加 1,另一个线程对当前数值减 1,要求用线程间通信 多线程编程步骤:

  1. 创建资源类, 在资源类创建属性和操作方法
  2. 在资源类操作方法
    • 判断
    • 干活
    • 通知
  3. 创建多个线程,调用资源类的操作方法
  4. 防止虚假唤醒问题

1. synchronized方案

java
public class Thread_Synchronized_Communicate {

    // 第一步  创建资源类, 在资源类创建属性和操作方法
    static class Resource {
        private int num = 0;

        // 第二步  在资源类操作方法
        public synchronized void incr() throws InterruptedException {
            // 判断
            if (num != 0) {
                this.wait();  // 在哪里睡就在哪里醒
            }
            // 干活
            num++;
            System.out.println(Thread.currentThread().getName() + "执行加 1 操作, num: " + num);
             // 通知 
            this.notifyAll();
        }

        public synchronized void decr() throws InterruptedException {
            // 判断
            if (num != 1) {
                this.wait();  // 在哪里睡就在哪里醒
            }
            // 干活
            num--;
            System.out.println(Thread.currentThread().getName() + "执行减 1 操作, num: " + num);
            // 通知 
            this.notifyAll();
        }
    }

    public static void main(String[] args) {
        Resource resource = new Resource();
        // 第三步  创建多个线程,调用资源类的操作方法
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    resource.incr();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "生产者线程1").start();
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    resource.decr();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "消费者线程2").start();
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    resource.incr();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "生产者线程3").start();
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    resource.decr();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "消费者线程4").start();
    }
}

运行结果:
Alt text 可以看到执行到后面就出现问题了,原因就是存在虚假唤醒问题,所谓虚假唤醒就是线程被唤醒时候,即使资源条件不满足,仍然被错误的被执行了下去。wait()在被唤醒时执行特点是会接着执行下一行代码。 比如 生产者线程1 执行后释放锁,生产者线程3 拿到锁,num此时是1不是0,此时条件不满足,于是执行wait()被阻塞同时释放锁, 锁释放出去又被 生产者线程3 拿到,生产者线程3 被唤醒,它不会再去上一行执行条件判定,直接执行下面的代码,包括num++操作。解决办法就是将逻辑判断if改为while, 强行进行逻辑判定。
将if改成while后:

java
 public class Thread_Synchronized_Communicate {

    // 第一步  创建资源类, 在资源类创建属性和操作方法
    static class Resource {
        private int num = 0;

        // 第二步  在资源类操作方法
        public synchronized void incr() throws InterruptedException {
            while (num != 0) {   // 使用while避免虚假唤醒
                this.wait();
            }
            // 干活
            num++;
            System.out.println(Thread.currentThread().getName() + "执行加 1 操作, num: " + num);
            // 通知
            this.notifyAll();
        }

        public synchronized void decr() throws InterruptedException {
            while (num != 1) {  // 使用while避免虚假唤醒
                this.wait();
            }
            // 干活
            num--;
            System.out.println(Thread.currentThread().getName() + "执行减 1 操作, num: " + num);
            // 通知
            this.notifyAll();
        }
    }

    public static void main(String[] args) {
        Resource resource = new Resource();
        // 第三步  创建多个线程,调用资源类的操作方法
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    resource.incr();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "生产者线程1").start();
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    resource.decr();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "消费者线程2").start();
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    resource.incr();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "生产者线程3").start();
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    resource.decr();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "消费者线程4").start();
    }
}

运行结果:
Alt text

2. Lock方案

使用Condition对象也可以实现通知等待功能

java
public class Thread_ReentrantLock_Communicate {

    // 第一步  创建资源类, 在资源类创建属性和操作方法
    static class Resource {
        ReentrantLock rl = new ReentrantLock();
        Condition condition = rl.newCondition();
        private int num = 0;

        // 第二步  在资源类操作方法
        public void incr() throws InterruptedException {
            rl.lock();
            try{
                // 判断
                while (num != 0) {   // 使用while避免虚假唤醒
                    condition.await();
                }
                // 干活
                num++;
                System.out.println(Thread.currentThread().getName() + "执行加 1 操作, num: " + num);
                // 通知
                condition.signalAll();
            }finally {
                rl.unlock();
            }

        }

        public void decr() throws InterruptedException {
            rl.lock();
            try{
                // 判断
                while (num != 1) {  // 使用while避免虚假唤醒
                    condition.await();
                }
                // 干活
                num--;
                System.out.println(Thread.currentThread().getName() + "执行减 1 操作, num: " + num);
                // 通知
                condition.signalAll();
            }finally {
                rl.unlock();
            }
        }
    }

    public static void main(String[] args) {
        Resource resource = new Resource();
        // 第三步  创建多个线程,调用资源类的操作方法
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    resource.incr();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "生产者线程1").start();
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    resource.decr();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "消费者线程2").start();
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    resource.incr();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "生产者线程3").start();
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    resource.decr();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "消费者线程4").start();
    }
}

运行结果:
Alt text

3. 线程间定制化通信

需求: A线程打印2次A,B线程打印5次B,C线程打印10次C,按照此顺序循环10轮。
Synchronized不再适合此精细场景了,因为它不能指定唤醒那个线程。 要求是ABC轮流打印。
实现思路:
Alt text

java
ublic class Thread_ReentrantLock_Communicate_DIY {

    // 第一步  创建资源类, 在资源类创建属性和操作方法
    static class Resource {
        // 可重入锁
        ReentrantLock rl = new ReentrantLock();
        // 三把钥匙
        Condition c1 = rl.newCondition();
        Condition c2 = rl.newCondition();
        Condition c3 = rl.newCondition();
        private int flag = 1;

        // 第二步  在资源类操作方法
        public void printConsole2() throws InterruptedException {
            rl.lock();
            try{
                while (flag != 1) {
                    c1.await();
                }
                for (int i = 0; i < 2; i++) {
                    System.out.println(Thread.currentThread().getName()+"打印内容: A"+(i+1) );
                }
                // 干活
                flag = 2;
                // 通知
                c2.signal();// 唤醒B线程
            }finally {
                rl.unlock();
            }
        }

        public void printConsole5() throws InterruptedException {
            rl.lock();
            try{
                while (flag != 2) {
                    c2.await();
                }
                for (int i = 0; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName()+"打印内容: B"+(i+1) );
                }
                // 干活
                flag = 3;
                // 通知
                c3.signal();// 唤醒C线程
            }finally {
                rl.unlock();
            }
        }

        public void printConsole10() throws InterruptedException {
            rl.lock();
            try{
                while (flag != 3) {
                    c3.await();
                }
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName()+"打印内容: C"+(i+1) );
                }
                // 干活
                flag = 1;
                c1.signal();// 唤醒C线程
            }finally {
                rl.unlock();
            }
        }
    }

    public static void main(String[] args) {
        Resource resource = new Resource();
        // 第三步  创建多个线程,调用资源类的操作方法
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    resource.printConsole2();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "A").start();
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    resource.printConsole5();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "B").start();
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    resource.printConsole10();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "C").start();
    }
}

运行结果:
Alt text