JUC高并发编程(03) -- 线程间通信

该博客探讨了Java中两种线程间通信的实现方式:synchronized关键字和Lock接口。通过一个数值加减的案例展示了如何使用这两种方式确保线程安全,防止虚假唤醒问题,并详细解释了每个方法的步骤和逻辑。

JUC高并发编程

三、线程间通信

线程间通信的模型有两种:共享内存和消息传递,以下方式都是基本这两种模型来实现的。

3.1)线程间通信案例

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

3.1.1)synchronized 方案

package com.study.sync;
​
//第一步 创建资源类,定义属性和操作方法
class Share {
    //初始值
    private int number = 0;
    //+1的方法
    public synchronized void incr() throws InterruptedException {
        //第二步 判断 干活 通知
        //判断:number值是否是0,如果不是0,等待
        // if (number != 0) {  --> 会产生虚假唤醒问题,即只对第一次进入的条件进行了判断,使用while判断可以避免虚假唤醒
        while (number != 0) {
            //在哪里睡,就在哪里醒
            this.wait();
        }
        //干活:如果number值是0,就+1操作
        number++;
        System.out.println("当前线程名:" + Thread.currentThread().getName() + " :: " + number);
        //通知:通知其他线程
        this.notifyAll();
    }
​
    //-1的方法
    public synchronized void decr() throws InterruptedException {
        //判断:number值是否是1,如果不是1,等待
        // if (number != 1) {  --> 会产生虚假唤醒问题,即只对第一次进入的条件进行了判断,使用while判断可以避免虚假唤醒
        while (number != 1) {
            this.wait();
        }
        //干活:如果number值是1,就-1操作
        number--;
        System.out.println("当前线程名:" + Thread.currentThread().getName() + " :: " + number);
        //通知:通知其他线程
        this.notifyAll();
    }
}
​
public class ThreadDemo1 {
    //第三步 创建多个线程,调用资源类的操作方法
    public static void main(String[] args) {
        Share share = new Share();
        //创建线程
        // 创建线程 AA
        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    share.incr(); //+1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "AA").start();
        // 创建线程 BB
        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    share.decr(); //-1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "BB").start();
        // 创建线程 CC
        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    share.incr(); //+1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "CC").start();
        // 创建线程 DD
        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    share.decr(); //-1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "DD").start();
    }
}
输出:

com.study.sync.ThreadDemo1
当前线程名:AA :: 1
当前线程名:BB :: 0
当前线程名:AA :: 1
当前线程名:BB :: 0
当前线程名:AA :: 1
当前线程名:BB :: 0
当前线程名:AA :: 1
当前线程名:BB :: 0
当前线程名:AA :: 1
当前线程名:BB :: 0
当前线程名:CC :: 1
当前线程名:BB :: 0
当前线程名:AA :: 1
当前线程名:BB :: 0
当前线程名:CC :: 1
当前线程名:BB :: 0
当前线程名:AA :: 1
当前线程名:DD :: 0
当前线程名:CC :: 1
当前线程名:BB :: 0
当前线程名:AA :: 1
当前线程名:DD :: 0
当前线程名:CC :: 1
当前线程名:BB :: 0
当前线程名:AA :: 1
当前线程名:DD :: 0
当前线程名:CC :: 1
当前线程名:DD :: 0
当前线程名:AA :: 1
当前线程名:DD :: 0
当前线程名:CC :: 1
当前线程名:DD :: 0
当前线程名:CC :: 1
当前线程名:DD :: 0
当前线程名:CC :: 1
当前线程名:DD :: 0
当前线程名:CC :: 1
当前线程名:DD :: 0
当前线程名:CC :: 1
当前线程名:DD :: 0

3.1.2)Lock 方案

package com.study.lock;
​
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
​
//第一步 创建资源类,定义属性和操作方法
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 {
            //判断:number值是否是0,如果不是0,等待
            // if (number != 0) {  --> 会产生虚假唤醒问题,即只对第一次进入的条件进行了判断,使用while判断可以避免虚假唤醒
            while (number != 0) {
                condition.await();
            }
            //干活:如果number值是0,就+1操作
            number++;
            System.out.println("当前线程名:" + Thread.currentThread().getName() + " :: " + number);
            //通知:通知其他线程
            condition.signalAll();
        } finally {
            //解锁
            lock.unlock();
        }
    }
​
    //-1 方法
    public void decr() throws InterruptedException {
        //上锁
        lock.lock();
        try {
            //判断:number值是否是1,如果不是1,等待
            // if (number != 1) {  --> 会产生虚假唤醒问题,即只对第一次进入的条件进行了判断,使用while判断可以避免虚假唤醒
            while (number != 1) {
                condition.await();
            }
            //干活:如果number值是1,就-1操作
            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();
        // 创建线程 AA
        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    // 调用 +1 方法
                    share.incr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "AA").start();
        // 创建线程 BB
        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    // 调用 -1 方法
                    share.decr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "BB").start();
        // 创建线程 CC
        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    // 调用 +1 方法
                    share.incr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "CC").start();
        // 创建线程 DD
        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    // 调用 -1 方法
                    share.decr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "DD").start();
    }
​
}

输出:

com.study.lock.ThreadDemo2
当前线程名:AA :: 1
当前线程名:BB :: 0
当前线程名:AA :: 1
当前线程名:BB :: 0
当前线程名:AA :: 1
当前线程名:BB :: 0
当前线程名:AA :: 1
当前线程名:BB :: 0
当前线程名:AA :: 1
当前线程名:BB :: 0
当前线程名:AA :: 1
当前线程名:BB :: 0
当前线程名:AA :: 1
当前线程名:BB :: 0
当前线程名:AA :: 1
当前线程名:BB :: 0
当前线程名:AA :: 1
当前线程名:BB :: 0
当前线程名:AA :: 1
当前线程名:BB :: 0
当前线程名:CC :: 1
当前线程名:DD :: 0
当前线程名:CC :: 1
当前线程名:DD :: 0
当前线程名:CC :: 1
当前线程名:DD :: 0
当前线程名:CC :: 1
当前线程名:DD :: 0
当前线程名:CC :: 1
当前线程名:DD :: 0
当前线程名:CC :: 1
当前线程名:DD :: 0
当前线程名:CC :: 1
当前线程名:DD :: 0
当前线程名:CC :: 1
当前线程名:DD :: 0
当前线程名:CC :: 1
当前线程名:DD :: 0
当前线程名:CC :: 1
当前线程名:DD :: 0

3.2)多线程编程步骤

  1. 创建资源类,在资源类中创建属性和操作方法
  2. 在资源类中操作方法【判断 、干活 、通知 】
  3. 创建多个线程,调用资源类的操作方法
  4. 防止虚假唤醒问题
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值