三、线程间通信

线程间通信模型:

  1. 生产者+消费者
  2. 通知等待唤醒机制

多线程编程模板(上):

  1. 线程 操作 资源类
  2. 高内聚 低耦合

多线程编程模板(中):

  1. 判断
  2. 干活
  3. 通知

多线程编程模板(下):

  • 防止虚假唤醒(使用while,代替if)

简单案例:两个线程操作一个初始值为0的变量,实现一个线程对变量增加1,一个线程对变量减少1,交替10轮。

使用通知等待机制wait-notify,来完成线程间的通信。

class ShareDateOne {
    private Integer number = 0;

    public synchronized void increment() {
        try {
            // 1. 判断:是否轮到我执行,则干活;否则等待
            if(number != 0) {
                this.wait();
            }

            // 2. 干活
            number++;
            System.out.println(Thread.currentThread().getName() + " 执行加法 " + number);

            // 3. 通知:唤醒等待的线程
            this.notifyAll();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    public synchronized void decrement() {
        try {
            // 1. 判断:是否轮到我执行,则干活;否则等待
            if(number != 1) {
                this.wait();
            }
            // 2. 干活
            number--;
            System.out.println(Thread.currentThread().getName() + " 执行减法 " + number);

            // 3. 通知:唤醒等待的线程
            this.notifyAll();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}
public class NotifyWaitDemo {

    public static void main(String[] args) {
        ShareDateOne shareDateOne = new ShareDateOne();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                shareDateOne.increment();
            }
        }).start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                shareDateOne.decrement();
            }
        }).start();
    }
}
  • 那如果变多几个线程呢?

3.1 虚假唤醒

【变成 4 个线程,两个加法两个减法】虚假唤醒错误

原因:Java多线程判断时,不能用if,错误出在了判断if上面。

注意:消费者被唤醒后是从wait()方法(被阻塞的地方)后面执行,而不是重新从同步块开头;两个加法或者减法,同时运行所以原本的0 -> -1 1 -> 2

改变加法减法顺序,更是如此,甚至会其他的-2现象 2什么的

class ShareDateOne {
    private Integer number = 0;

    public synchronized void increment() {
        try {
            // 1. 判断:是否轮到我执行,则干活;否则等待
            if(number != 0) {
                this.wait();
            }

            // 2. 干活
            number++;
            System.out.println(Thread.currentThread().getName() + " 执行加法 " + number);

            // 3. 通知:唤醒等待的线程
            this.notifyAll();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    public synchronized void decrement() {
        try {
            // 1. 判断:是否轮到我执行,则干活;否则等待
            if(number != 1) {
                this.wait();
            }
            // 2. 干活
            number--;
            System.out.println(Thread.currentThread().getName() + " 执行减法 " + number);

            // 3. 通知:唤醒等待的线程
            this.notifyAll();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}
public class NotifyWaitDemo {

    public static void main(String[] args) {
        ShareDateOne shareDateOne = new ShareDateOne();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                shareDateOne.increment();
            }
        }).start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                shareDateOne.increment();
            }
        }).start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                shareDateOne.decrement();
            }
        }).start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                shareDateOne.decrement();
            }
        }).start();
    }
}

中断和虚假唤醒是可能产生的,所以要用循环,if 只判断一次,while是只要唤醒就要重新再判断一次。

多线程编程模板(下):

  • 防止虚假唤醒(使用while,代替if)

if 改成 while,因为通知是所有的线程,可能就唤醒了是哪一个,它上一个线程 if 判断的时候,在这wait这儿了。现在唤醒了,可能又一个线程也正好一起了,所以就会这样出现这样,要么是 2 要么是 -2,这种是虚拟唤醒。

while 不会这样,即使是卡在 while 那,还需要重新判断,所以不满足就不会出现这样的情况。

两个线程无所谓进行争抢,确实 if/while 无所谓,但是未来可能是多线程,通知等待唤醒,使用 while,防止虚假唤醒。

修改为 while,次数进行 100次

无论怎样,也不会出错了!

// 1. 判断:是否轮到我执行,则干活;否则等待
while (number != 1) {
    this.wait();
}

3.2 线程间通信

  • Synchronized 有 wait和notify,那 lock 有什么呢?

Condition 接口中对应的方法:

lock.newCondition() 的 await 和 signal

class ShareDateTwo {

    private final ReentrantLock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();

    private Integer number = 0;

    public void increment() {
        lock.lock();
        try {
            // 1. 判断:是否轮到我执行,则干活;否则等待
            while(number != 0) {
                condition.await();
            }

            // 2. 干活
            number++;
            System.out.println(Thread.currentThread().getName() + " 执行加法 " + number);

            // 3. 通知:唤醒等待的线程
            condition.signalAll();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }

    public void decrement() {
        lock.lock();
        try {
            // 1. 判断:是否轮到我执行,则干活;否则等待
            while (number != 1) {
                condition.await();
            }
            // 2. 干活
            number--;
            System.out.println(Thread.currentThread().getName() + " 执行减法 " + number);

            // 3. 通知:唤醒等待的线程
            condition.signalAll();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }
}
public class NotifyWaitDemo2 {

    public static void main(String[] args) {
        ShareDateTwo shareDateTwo = new ShareDateTwo();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                shareDateTwo.increment();
            }
        }).start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                shareDateTwo.decrement();
            }
        }).start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                shareDateTwo.increment();
            }
        }).start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                shareDateTwo.decrement();
            }
        }).start();
    }
}

3.3 小结

  1. 隐式锁
synchronized
object.wait()
object.notify()  //随机唤醒一个等待的线程
object.notifyAll() //唤醒所有等待的线程
  1. 显示锁

注意:这里创建 condition ,从前往后写才可以!

ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition()
condition.await()
condition.signal()  //随机唤醒一个等待的线程
condition.signalAll() //唤醒所有等待的线程

他们不要交叉使用,交叉使用await + notify这样也是会报错的!

3.4 定制化通信方式

  • 那既然Synclock都能实现线程间同行,有啥区别呢?

在Java中,一个ReentrantLock可以与多个Condition对象一起使用,每个Condition对象可以用于不同的线程协调和通信场景,以便更精细地控制多线程之间的执行顺序和互斥访问。

说白了,就是lock.condition可以控制下一个让谁醒,那就决定了执行顺序,这样更加强大。

【案例】

多线程之间按顺序调用,实现AA->BB->CC。三个线程启动,要求如下:

AA打印5次,BB打印10次,CC打印15次

接着

AA打印5次,BB打印10次,CC打印15次

。。。打印10轮

【分析实现方式】

  1. 有一个锁Lock,3把钥匙Condition
  2. 有顺序通知(切换线程),需要有标识位
  3. 判断标志位
  4. 输出线程名 + 内容
  5. 修改标识符,通知下一个
class ShareDataTwo {

    private Integer flag = 1; // 线程标识类 区分线程切换
    private final Lock lock = new ReentrantLock();


    private final Condition condition1 = lock.newCondition();
    private final Condition condition2 = lock.newCondition();
    private final Condition condition3 = lock.newCondition();

    public void print5() {
        lock.lock();
        try {

            while(flag != 1) {
                // 等待
                condition1.await();
            }

            for (int i = 0; i < 5; i++) {
                System.out.println("AA");
            }

            // 通知并且唤醒
            flag = 2;
            condition2.signal(); // 唤醒 B

        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }

    public void print10() {
        lock.lock();
        try {
            // 等待
            while (flag != 2) {
                condition2.await();
            }

            // 干活
            for (int i = 0; i < 10; i++) {
                System.out.println("BB");
            }

            // 通知唤醒
            flag = 3;
            condition3.signal();

        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }

    public void print15() {
        lock.lock();
        try {
            // 等待
            while(flag != 3) {
                condition3.await();
            }
            // 干活
            for (int i = 0; i < 15; i++) {
                System.out.println("CC");
            }
            // 通知唤醒
            flag = 1;
            condition1.signal();

        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }
}

public class ThreadOrder {
    public static void main(String[] args) {
        // 线程操作资源类
        ShareDataTwo shareDataTwo = new ShareDataTwo();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                shareDataTwo.print5();
            }
        },"AA").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                shareDataTwo.print10();
            }
        },"BB").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                shareDataTwo.print15();
            }
        },"CC").start();
    }
}

面试题:两个线程,一个线程打印1-52,另一个打印字母A-Z,打印顺序为12A34B...5152Z,要求用线程间通信

  1. 首先分析轮次,每个线程需要打印多少轮次
  2. 每个线程打印出来结果,需要什么变量
  3. 进行编写

class PrintNumAndChar {
    private final ReentrantLock lock = new ReentrantLock();

    private final Condition condition1 = lock.newCondition();
    private final Condition condition2 = lock.newCondition();

    private Integer flag = 1;
    private Integer number = 1;
    private Character character = 'A';


    public void printNumber() {
        lock.lock();

        try {
            // 判断
            while(flag != 1) {
                condition1.await();
            }

            // 干活
            for(int i = 0;i < 2;i++) {
                System.out.print(number);
                number++;
            }

            // 唤醒
            flag = 2;

            condition2.signal();

        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }

    public void printCharacter() {
        lock.lock();

        try {
            while(flag != 2) {
                condition2.await();
            }

            System.out.print(character);
            character++;

            flag = 1;

            condition1.signal();

        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }

}

public class CustomComm {
    public static void main(String[] args) {
        PrintNumAndChar printNumAndChar = new PrintNumAndChar();

        new Thread(() -> {
            for (int i = 0; i < 26; i++) {
                printNumAndChar.printNumber();
            }
        },"线程-1").start();

        new Thread(() -> {
            for (int i = 0; i < 26; i++) {
                printNumAndChar.printCharacter();
            }
        },"线程-1").start();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值