线程间通信

多线程编程步骤

  1. 创建资源类,编写属性和操作方法
  2. 在资源中的操作方法
    1. 判断
    2. 干活
    3. 通知
  3. 创建多个线程,调用资源类的操作方法
  4. 使用while循环进行条件判断,防止虚假唤醒问题

一个加减实例 & 虚假唤醒问题

实现对一个初始值0进行轮流加减操作

public class AddAndSub {
    private int num;
    public int getNum() {
        return num;
    }
    public AddAndSub() {
        num = 0;
    }
    public synchronized void add() {
        // 判断
        while(num != 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 业务逻辑
        num++;
        System.out.println(Thread.currentThread().getName() + " + 1");
        // 通知
        notifyAll();
    }
    public synchronized void sub() {
        while(num != 1) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        num--;
        System.out.println(Thread.currentThread().getName() + " - 1");
        notifyAll();
    }
}

class RunAddAndSub {
    public static void main(String[] args) {
        AddAndSub addAndSub = new AddAndSub();
        Runnable r1 = () -> {
            for(int i = 0; i < 10; i++) {
                addAndSub.add();
            }
        };
        Runnable r2 = () -> {
            for(int i = 0; i < 10; i++) {
                addAndSub.sub();
            }
        };
        new Thread(r1, "A").start();
        new Thread(r2, "B").start();
        System.out.println(addAndSub.getNum());
    }
}

⭐为什么要在条件判断使用while?

这是因为比如add方法A线程+1notifyAll后,可能是处于等待状态的C线程抢到了锁,但是这时候num并不是0,所以要使用while在线程唤醒后再次判断。这种问题称为虚假唤醒问题

Lock接口实现

lock.newCondition设置等待条件
class Share {
    private int num = 0;
    private final Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    public void incr() {
        lock.lock();
        while(num != 0) {
            try {
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        num++;
        System.out.println(Thread.currentThread().getName() + " + 1");
        condition.signalAll();
        lock.unlock();
    }
    public void decr() {
        lock.lock();
        while(num == 0) {
            try {
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        num--;
        System.out.println(Thread.currentThread().getName() + " - 1");
        condition.signalAll();
        lock.unlock();
    }
}
public class TheadDemo {
    public static void main(String[] args) {
        var share = new Share();
        Runnable r1 = () -> {
            for(int i = 0; i < 10; i++) {
                share.incr();
            }
        };
        Runnable r2 = () -> {
            for(int i = 0; i < 10; i++) {
                share.decr();
            }
        };;

        new Thread(r1, "A").start();
        new Thread(r2, "B").start();
        new Thread(r1, "C").start();
        new Thread(r2, "D").start();
    }
}

注意condition.signalAll()要写在unlock之前

线程间定制化通信

如上面的四个线程执行的时候,只是+-线程的顺序执行,执行相同方法的线程是没有顺序的,规定相同方法的线程的执行顺序,即是线程的定制化通信。

线程间通信案例

启动三个线程,要求如下:

  • AA打印5次,BB打印10次,CC打印15次
  • 如此进行10轮
设置标志位

当标志位为1,AA执行,当标志位为2,BB执行,当标志位为3,CC执行

class Print {
    private int flag = 1;

    public synchronized void print5(int num) throws InterruptedException {
        while (flag != 1) {
            this.wait();
        }
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ":::" + i + " ,loop" + num);
        }
        flag = 2;
        this.notifyAll();
    }

    public synchronized void print10(int num) throws InterruptedException {
        while (flag != 2) {
            this.wait();
        }
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + ":::" + i + " ,loop" + num);
        }
        flag = 3;
        this.notifyAll();
    }

    public synchronized void print15(int num) throws InterruptedException {
        while (flag != 3) {
            this.wait();
        }
        for (int i = 0; i < 15; i++) {
            System.out.println(Thread.currentThread().getName() + ":::" + i + " ,loop" + num);
        }
        flag = 1;
        this.notifyAll();
    }
}

public class ThreadDemo2 {
    public static void main(String[] args) {
        Print print = new Print();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    try {
                        print.print5(i);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "AA").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    try {
                        print.print10(i);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "BB").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    try {
                        print.print15(i);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "CC").start();
    }
}