JUC高并发编程3:线程间通信

14 篇文章 0 订阅

1 线程间通信

线程间通信的模型有两种:共享内存和消息传递,以下方式都是基本这两种模型来实现的。我们来基本一道面试常见的题目来分析

场景:两个线程,一个线程对当前数值加 1,另一个线程对当前数值减 1,要求用线程间通信

1.1 synchronized方法

// 第一步 创建资源类,定义属性和操作方法
class Share{

    //初始值
    private int number = 0;

    // +1的方法
    public synchronized void incr() throws InterruptedException {
        // 判断
        while(number != 0) {
            this.wait();
        }
        // 干活
        number++;
        System.out.println(Thread.currentThread().getName() + " :: " + number);
        //通知其他线程
        this.notifyAll();
    }

    // -1的方法
    public synchronized void decr() throws InterruptedException {
        // 判断
        while(number != 1) {
            this.wait();
        }
        // 干活
        number--;
        System.out.println(Thread.currentThread().getName() + " :: " + number);
        //通知其他线程
        this.notifyAll();
    }

}
public class ThreadDemo1 {

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

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.decr(); //-1
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"BB").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.incr();//+1
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"CC").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.decr(); //-1
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"DD").start();

    }
}

1.2 Lock方案

// 第一步 创建资源类,定义属性和操作方法
class Share{
    //初始值
    private int number = 0;

    //创建Lock
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    // +1
    public void incr() throws InterruptedException {
        //上锁
        lock.lock();
        try {
            //判断
            while (number != 0){
                condition.await();
            }
            //干活
            number++;
            System.out.println(Thread.currentThread().getName() + " :: " + number);
            //通知
            condition.signalAll();
        }finally {
            // 解锁
            lock.unlock();
        }
    }

    // -1
    public void decr() throws InterruptedException {
        //上锁
        lock.lock();
        try {
            //判断
            while (number != 1){
                condition.await();
            }
            //干活
            number--;
            System.out.println(Thread.currentThread().getName() + " :: " + number);
            //通知
            condition.signalAll();
        }finally {
            // 解锁
            lock.unlock();
        }
    }
}
public class ThreadDemo2 {

    // 第三步创建多个线程,调用资源类的操作方法
    public static void main(String[] args) {
        Share share = new Share();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.incr();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"AA").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.decr();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"BB").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.incr();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"CC").start();


        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.decr();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"DD").start();
    }
}

1.3 虚假唤醒问题

在Java中,线程间通信通常使用 Object 类的 wait()notify()notifyAll() 方法来实现。这些方法与条件变量类似,但同样可能会出现虚假唤醒(Spurious Wakeup)的问题。

1.3.1 什么是虚假唤醒?

虚假唤醒是指一个线程在没有被显式通知的情况下被唤醒。换句话说,即使没有调用 notify()notifyAll() 方法,等待的线程也可能被唤醒。这种现象在某些操作系统或线程库中是允许的,因为它可以简化某些实现。

1.3.2 为什么会出现虚假唤醒?

虚假唤醒的原因可能包括:

  1. 操作系统调度:操作系统可能在某些情况下唤醒线程,即使没有显式的通知。
  2. 多核处理器:在多核处理器上,线程可能在不同的核心上运行,导致某些同步机制不完全可靠。
  3. Java 实现:Java 的线程库实现可能会允许虚假唤醒,以提高性能或简化实现。

1.3.3 如何处理虚假唤醒?

为了避免虚假唤醒带来的问题,通常的做法是在循环中检查条件变量。这样,即使线程被虚假唤醒,它也会在循环中重新检查条件,如果条件不满足,线程会继续等待。

以下是一个使用 wait()notifyAll() 的典型示例:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SpuriousWakeupExample {
    private static final Lock lock = new ReentrantLock();
    private static final Condition condition = lock.newCondition();
    private static boolean ready = false;

    public static void main(String[] args) throws InterruptedException {
        Thread workerThread = new Thread(new Worker());
        workerThread.start();

        // 主线程设置条件并通知
        Thread.sleep(1000); // 模拟一些工作
        lock.lock();
        try {
            ready = true;
            condition.signalAll(); // 通知等待的线程
        } finally {
            lock.unlock();
        }

        workerThread.join();
    }

    static class Worker implements Runnable {
        @Override
        public void run() {
            lock.lock();
            try {
                // 在循环中检查条件
                while (!ready) {
                    condition.await(); // 等待条件满足
                }
                // 条件满足,执行工作
                System.out.println("Worker thread is processing data");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                lock.unlock();
            }
        }
    }
}

1.3.4 关键点

  1. 循环检查条件:在 await() 方法周围使用 while 循环来检查条件,而不是 if 语句。这样可以确保即使线程被虚假唤醒,它也会重新检查条件。
  2. 使用 LockCondition:在示例中使用了 ReentrantLockCondition,这是 Java 中更灵活的同步机制。你也可以使用 synchronized 关键字和 Objectwait()notify()notifyAll() 方法,但原理是相同的。
  3. 处理中断:在 await() 方法中捕获 InterruptedException,并处理线程中断的情况。

通过这种方式,可以有效避免虚假唤醒带来的问题,确保线程在条件真正满足时才继续执行。

1.4 多线程编程步骤

  • 第一步创建资源类,在资源类创建属性和操作方法

  • 第二步在资源类操作方法

    • 判断
    • 干活
    • 通知
  • 第三步创建多个线程,调用资源类的操作方法

  • 第四步防止虚假唤醒问题

2 线程间定制化通信

让线程按照指定顺序进行通信。

2.1 案例介绍

启动三个线程,按照如下要求执行:
AA线程打印 5 次 A,BB 线程打印 10 次 B,CC 线程打印 15 次 C,按照此顺序循环 10 轮

2.2 流程分析

在这里插入图片描述

2.3 代码实现

// 第一步 创建资源类,定义属性和操作方法
class ShareResourse{
    // 定义标识位
    private int flag = 1; // 1:AA; 2:BB; 3:CC
    //创建Lock
    private Lock lock = new ReentrantLock();

    //创建三个condition
    private Condition c1 = lock.newCondition();
    private Condition c2 = lock.newCondition();
    private Condition c3 = lock.newCondition();

    //打印5次,参数第几轮
    public void print5(int loop) throws InterruptedException {
        //上锁
        lock.lock();
        try{
            while (flag != 1){
                //等待
                c1.await();
            }
            //干活
            for (int i = 0; i <= 5; i++) {
                System.out.println(Thread.currentThread().getName() + " :: " + i +", 轮数:"+ loop);
            }
            //通知
            flag = 2; //修改标识位2
            c2.signal();//通知BB线程
        }finally {
            // 释放锁
            lock.unlock();
        }
    }

    //打印10次,参数第几轮
    public void print10(int loop) throws InterruptedException {
        //上锁
        lock.lock();
        try{
            while (flag != 2){
                //等待
                c2.await();
            }
            //干活
            for (int i = 0; i <= 10; i++) {
                System.out.println(Thread.currentThread().getName() + " :: " + i +", 轮数:"+ loop);
            }
            //通知
            flag = 3; //修改标识位3
            c3.signal();//通知CC线程
        }finally {
            // 释放锁
            lock.unlock();
        }
    }


    //打印15次,参数第几轮
    public void print15(int loop) throws InterruptedException {
        //上锁
        lock.lock();
        try{
            while (flag != 3){
                //等待
                c3.await();
            }
            //干活
            for (int i = 0; i <= 15; i++) {
                System.out.println(Thread.currentThread().getName() + " :: " + i +", 轮数:"+ loop);
            }
            //通知
            flag = 1; //修改标识位1
            c1.signal();//通知AA线程
        }finally {
            // 释放锁
            lock.unlock();
        }
    }
}
public class ThreadDemo3 {
    // 第三步创建多个线程,调用资源类的操作方法
    public static void main(String[] args) {
        ShareResourse shareResourse = new ShareResourse();
        new Thread(()->{
            for (int i = 0; i <= 10; i++) {
                try {
                    shareResourse.print5(i);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"AA").start();

        new Thread(()->{
            for (int i = 0; i <= 10; i++) {
                try {
                    shareResourse.print10(i);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"BB").start();

        new Thread(()->{
            for (int i = 0; i <= 10; i++) {
                try {
                    shareResourse.print15(i);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"CC").start();
    }
}

synchronized实现同步的基础:Java中的每一个对象都可以作为锁。具体表现为以下3种形式。
对于普通同步方法,锁是当前实例对象。
对于静态同步方法,锁是当前类的class对象。
对于同步方法块,锁是synchonized括号里配置的对象

3 思维导图

在这里插入图片描述

4 参考链接

【尚硅谷】大厂必备技术之JUC并发编程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值