提到线程通信,就不得不提经典的生产者消费者模式

1. 生产者消费者模式

实现双线程交替打印 0-1

public class ConProMode {
    public static void main(String[] args) {
        source s = new source();
        new Thread(()-> {   // 生产者线程
            for(int i=0; i<10; i++){
                try {
                s.increase();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }}
        },"生产者A").start();

        new Thread(()-> {   // 消费者线程
            for(int i=0; i<10; i++){
            try {
                s.decrease();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }}
        },"消费者A").start();
   }
}
class source{   // 资源类
    private int num = 0;

    public synchronized void increase() throws InterruptedException {  
        // 1.判断 
        if(num != 0){   // 此处应该用while
            this.wait();
        }
        // 2.核心业务
        num++;
        System.out.println(Thread.currentThread().getName() + "\t" + num);
        // 3.线程通信
        this.notifyAll();
    }

    public synchronized void decrease() throws InterruptedException { 
        if(num == 0){   
            this.wait();
        }
        num--;
        System.out.println(Thread.currentThread().getName() + "\t" + num);
        this.notifyAll();
    }
}

附上最近学习对我理解和编写多线程提升最大的几句话:

  • 高内聚低耦合,线程操控资源类
  • 操控资源方法集成在资源类中,给线程调用

2. 线程间的虚假唤醒现象

在1中的生产者消费者模式中,由于生产者和消费者都只有一个,因此不会出现虚假唤醒问题;而如果将生产者和消费者都设置为为两个,即共4条线程操控同一份资源且彼此有交互(即通信),则将会出现线程的虚假唤醒现象,机制可见下图:

image

原因是在线程被唤醒后,如果使用if判断的话,因为线程wait前已经经过一次if判断,而if语句是一个一次性判断,线程苏醒后不会再次执行判断,而是直接执行后面的业务语句,这样就可能导致本该同步的操作却失去了同步功能。因此,在多线程的程序中,控制线程状态的判断语句一定要使用while,实现重复判断,避免虚假唤醒的问题

3. Lock实现线程的精准通信

Syncronized同步监视器是一种公平的锁机制,即当线程释放锁后,监视器上的所有线程都有权利去争夺这把锁并获取执行权,即线程的执行是无序的(除非有且仅有两条线程,可以交替执行),但很多实际应用场景所需要的线程之间的交互是非公平的,而是有序的,这就需要监视器为其上的每条线程特备一个认证操作通道。通俗理解为锁对象为每条线程都配备一把钥匙,各线程可以在自己的任务中选择和特定的线程进行通信,以此达到让线程按指定顺序执行。Lock锁便实现了这种机制。

image

举例:

/**
 * A - B - C三个线程
 * 实现要求:AA连续打印1次  然后BB连续打印2次  然后CC连续打印3次
 * 重复5轮
 */

public class ConProDemo03 {
    public static void main(String[] args) {
        source03 s = new source03();
        new Thread(()->{ for(int i=0; i<5; i++) s.printA(); },"线程A").start();
        new Thread(()->{ for(int i=0; i<5; i++) s.printB(); },"线程B").start();
        new Thread(()->{ for(int i=0; i<5; i++) s.printC(); },"线程C").start();
    }
}

class source03{
    private int flag = 1;
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition(); // 为线程A配一把钥匙
    private Condition condition2 = lock.newCondition(); // 为线程B配一把钥匙
    private Condition condition3 = lock.newCondition(); // 为线程C配一把钥匙

    public void printA() {
        lock.lock();
        try{
            while(flag != 1){
                condition1.await();
            }
            for(int i=0; i<1; i++){
                System.out.println(Thread.currentThread().getName() + "\t" + "AA");
            }
            flag = 2;
            condition2.signal();  // 实现对线程的精准唤醒
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void printB() {
        lock.lock();
        try{
            while(flag != 2){
                condition2.await();
            }
            for(int i=0; i<2; i++){
                System.out.println(Thread.currentThread().getName() + "\t" + "BB");
            }
            flag = 3;
            condition3.signal();
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void printC() {
        lock.lock();
        try{
            while(flag != 3){
                condition3.await();
            }
            for(int i=0; i<3; i++){
                System.out.println(Thread.currentThread().getName() + "\t" + "CC");
            }
            flag = 1;
            condition1.signal();
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

Chord_Gll
51 声望74 粉丝

每一个领跑者,都曾是优秀而虔诚的追赶者