03_线程间通信

news2024/11/29 10:50:39

面试题:两个线程打印

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

public class Demo01 {
    public static void main(String[] args) {
        ShareData05 shareData05 = new ShareData05();
        new Thread(()->{
            for (int i = 0; i < 26; i++) {
                shareData05.printChar();
            }
        }).start();
        new Thread(()->{
            for (int i = 0; i < 26; i++) {
                shareData05.printNum();
            }
        }).start();

    }
}
/**
 * 两个线程,一个线程打印1-52,另一个打印字母A-Z打印顺序为12A34B...5152Z,要求用线程间通信
 *  A线程:
 *      int变量记录输出的数字
 *  B线程:
 *      int变量记录输出的字母的值
 *
 *  互斥:
 *      int变量
 *
 *    打印数字、打印字母  加锁
 */
class ShareData05{
    private int num = 1;
    private char c = 'A';
    private int i = 0;//0打印数字、1打印字母
    private ReentrantLock lock = new ReentrantLock();
    private Condition numC = lock.newCondition();
    private Condition cC = lock.newCondition();
    public void printNum(){
        lock.lock();
        try {
            while(i!=0){
                numC.await();
            }
            System.out.print(num++);
            System.out.print(num++);
            i = 1;
            cC.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    public void printChar(){
        lock.lock();
        try {
            while(i!=1){
                cC.await();
            }
            System.out.print(c++);
            i = 0;
            numC.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

}

1. 回顾线程通信

先来简单案例:

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

线程间通信模型:

  1. 生产者+消费者

  2. 通知等待唤醒机制

多线程编程模板中:

  1. 判断

  2. 干活

  3. 通知

代码实现:

class ShareDataOne {
    private Integer number = 0;

    /**
     *  增加1
     */
    public synchronized void increment() throws InterruptedException {
        // 1. 判断
        if (number != 0) {
            this.wait();
        }

        // 2. 干活
        number++;
        System.out.println(Thread.currentThread().getName() + ": " + number);

        // 3. 通知
        this.notifyAll();
    }

    /**
     * 减少1
     */
    public synchronized void decrement() throws InterruptedException {
        // 1. 判断
        if (number != 1) {
            this.wait();
        }

        // 2. 干活
        number--;
        System.out.println(Thread.currentThread().getName() + ": " + number);

        // 3. 通知
        this.notifyAll();
    }
}

/**
 * 现在两个线程,
 * 可以操作初始值为零的一个变量,
 * 实现一个线程对该变量加1,一个线程对该变量减1,
 * 交替,来10轮。
 *
 * 笔记:Java里面如何进行工程级别的多线程编写
 * 1 多线编程模板(套路)-----上
 *    1.1  线程    操作    资源类
 *    1.2  高内聚  低耦合
 * 2 多线程编程模板(套路)-----中
 *    2.1  判断
 *    2.2  干活
 *    2.3  通知
 */
public class NotifyWaitDemo {

    public static void main(String[] args) {
        ShareDataOne shareDataOne = new ShareDataOne();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    shareDataOne.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "AAA").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    shareDataOne.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "BBB").start();

    }
}

部分打印结果:AAA和BBB交互执行,执行结果是1 0 1 0... 一共10轮

AAA: 1
BBB: 0
AAA: 1
BBB: 0
AAA: 1
BBB: 0
AAA: 1
BBB: 0
。。。。

如果换成4个线程会怎样?

改造mian方法,加入CCC和DDD两个线程:

public static void main(String[] args) {
        ShareDataOne shareDataOne = new ShareDataOne();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    shareDataOne.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "AAA").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    shareDataOne.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "BBB").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    shareDataOne.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "CCC").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    shareDataOne.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "DDD").start();
    }

打印结果,依然会有概率是,10101010...。但是,多执行几次,也会出现错乱的现象:

AAA: 1
BBB: 0
CCC: 1
AAA: 2
CCC: 3
BBB: 2
CCC: 3
DDD: 2
AAA: 3
DDD: 2
CCC: 3
BBB: 2

2. 虚假唤醒

换成4个线程会导致错误,虚假唤醒

原因:wait()会释放锁, 在java多线程判断时,不能用if,程序出事出在了判断上面。

注意,消费者被唤醒后是从wait()方法(被阻塞的地方)后面执行,而不是重新从同步块开头。

解决虚假唤醒:if换成while。中断和虚假唤醒是可能产生的,所以要用loop循环,if只判断一次,while是只要唤醒就要拉回来再判断一次。

class ShareDataOne {
    private Integer number = 0;

    /**
     *  增加1
     */
    public synchronized void increment() throws InterruptedException {
        // 1. 判断
        while (number != 0) {
            this.wait();
        }

        // 2. 干活
        number++;
        System.out.println(Thread.currentThread().getName() + ": " + number);

        // 3. 通知
        this.notifyAll();
    }

    /**
     * 减少1
     */
    public synchronized void decrement() throws InterruptedException {
        // 1. 判断
        while (number != 1) {
            this.wait();
        }

        // 2. 干活
        number--;
        System.out.println(Thread.currentThread().getName() + ": " + number);

        // 3. 通知
        this.notifyAll();
    }
}

/**
 * 现在两个线程,
 * 可以操作初始值为零的一个变量,
 * 实现一个线程对该变量加1,一个线程对该变量减1,
 * 交替,来10轮。
 *
 * 笔记:Java里面如何进行工程级别的多线程编写
 * 1 多线程编程模板(套路)-----上
 *    1.1  线程    操作    资源类
 *    1.2  高内聚  低耦合
 * 2 多线程编程模板(套路)-----中
 *    2.1  判断
 *    2.2  干活
 *    2.3  通知
 * 3 多线程编程模板(套路)-----下
 *    防止虚假唤醒(while)
 */
public class NotifyWaitDemo {

    public static void main(String[] args) {
        ShareDataOne shareDataOne = new ShareDataOne();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    shareDataOne.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "AAA").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    shareDataOne.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "BBB").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    shareDataOne.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "CCC").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    shareDataOne.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "DDD").start();
    }
}

3. 线程通信(Condition)

使用Condition实现线程通信,改造之前的代码(只需要改造ShareDataOne):删掉increment和decrement方法的synchronized

class ShareDataOne {
    private Integer number = 0;

    final Lock lock = new ReentrantLock(); // 初始化lock锁
    final Condition condition = lock.newCondition(); // 初始化condition对象

    /**
     *  增加1
     */
    public void increment() throws InterruptedException {
        lock.lock(); // 加锁
        try {
            // 1. 判断
            while (number != 0) {
                // this.wait();
                condition.await();
            }

            // 2. 干活
            number++;
            System.out.println(Thread.currentThread().getName() + ": " + number);

            // 3. 通知
            // this.notifyAll();
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 减少1
     */
    public void decrement() throws InterruptedException {
        lock.lock();
        try {
            // 1. 判断
            while (number != 1) {
                // this.wait();
                condition.await();
            }

            // 2. 干活
            number--;
            System.out.println(Thread.currentThread().getName() + ": " + number);

            // 3. 通知
            //this.notifyAll();
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

4. 定制化调用通信

① 案例:

​ 多线程之间按顺序调用,实现A->B->C。三个线程启动,要求如下:

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

​ 接着

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

​ 。。。打印10轮

② 分析实现方式:

  1. 有一个锁Lock,3把钥匙Condition

  2. 有顺序通知(切换线程),需要有标识位

  3. 判断标志位

  4. 输出线程名 + 内容

  5. 修改标识符,通知下一个

③ 具体实现:

 ReentrantLock 实现

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(Thread.currentThread().getName() + "\t" + (i + 1));
            }
            flag = 2;
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void print10() {
        lock.lock();
        try {
            while (flag != 2) {
                condition2.await();
            }
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + (i + 1));
            }
            flag = 3;
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void print15() {
        lock.lock();
        try {
            while (flag != 3) {
                condition3.await();
            }
            for (int i = 0; i < 15; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + (i + 1));
            }
            flag = 1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

/**
 * 多线程之间按顺序调用,实现A->B->C
 * 三个线程启动,要求如下:
 * AA打印5次,BB打印10次,CC打印15次
 * 接着
 * AA打印5次,BB打印10次,CC打印15次
 * ......来10轮
 */
public class ThreadOrderAccess {

    public static void main(String[] args) {

        ShareDataTwo sdt = new ShareDataTwo();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                sdt.print5();
            }
        }, "AAA").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                sdt.print10();
            }
        }, "BBB").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                sdt.print15();
            }
        }, "CCC").start();
    }
}

Synchronized 实现

public class Demo10 {
    public static void main(String[] args) {
        ShareData04 shareData04 = new ShareData04();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    shareData04.printB();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"AA").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    shareData04.printA();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"BB").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    shareData04.printC();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"CC").start();
    }
}
class ShareData04{
    private int i = 0;
    public synchronized void printA() throws InterruptedException {
        while(i!=0){
            this.wait();
        }
        for (int i1 = 0; i1 < 5; i1++) {
            System.out.print("AA\t");
        }
        i=1;
        this.notifyAll();//唤醒的是所有线程
    }
    public synchronized void printB() throws InterruptedException {
        while(i!=1){
            this.wait();
        }
        for (int i1 = 0; i1 < 10; i1++) {
            System.out.print("BB\t");
        }
        i=2;
        this.notifyAll();//唤醒的是所有线程
    }
    public synchronized void printC() throws InterruptedException {
        while(i!=2){
            this.wait();
        }
        for (int i1 = 0; i1 < 15; i1++) {
            System.out.print("CC\t");
        }
        System.out.println();
        i=0;
        this.notifyAll();//唤醒的是所有线程
    }
}

5. 死锁问题排查

jps(JVM Process Status Tool):显示当前系统的 Java 进程情况

jstack:java虚拟机自带的一种堆栈跟踪工具,查看Java进程内的线程堆栈信息,使用jstack命令查看线程堆栈信息时可能会看到的线程的几种状态:

  • NEW 未启动的。

  • RUNNABLE 运行中。

  • BLOCKED 受阻塞并等待监视器锁。

  • WATING 等待另一个线程执行特定操作。

  • TIMED_WATING 有时限的等待另一个线程的特定操作。

  • TERMINATED 已退出的。

如果出现:Found one Java-level deadlock 代表死锁

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/470518.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DFIG控制11: 磁链定向矢量控制和仿真

DFIG控制11&#xff1a; 磁链定向矢量控制和仿真&#xff0c;主要是看下怎么根据DFIG模型来做矢量控制。 磁链定向和模型简化 原模型 dq同步坐标系下的模型&#xff1a;DFIG控制10&#xff1a; 双馈发电机的动态模型_Fantasy237的博客 电压方程&#xff1a; { u s d R s i…

AIhelp智能问答

前言 2023年,科技圈里,持续爆火的科技应用,毫无疑问是生成式AI,chatGPT了的,之所以令人惊叹,正是因为它的强大 可以这么认为,chatGPT能够解决很多问题,尤其是问答,问题答案的搜索,远比百度,google要精准,方便得多 如何提出高质量的问题,写好一个promot提示词,尤为重要,提出问题…

Anaconda+vscode+pytorch环境搭建

1、安装Anaconda ​ 不要添加到path&#xff0c;否则可能会引起其他冲突。 ​ 记得手动添加环境变量 2、安装vscode 已经不支持自定义安装啦&#xff0c;默认(D:\Program Files) 按住ctrlshiftp&#xff0c;输入language&#xff0c;选择第一个configure Display Language&…

TypeScript第一个程序HelloWorld

博主作品&#xff1a;《Java项目案例》主要基于SpringBootMyBatis/MyBatis-plusMySQLVue等前后端分离项目&#xff0c;可以在左边的分类专栏找到更多项目。《Uniapp项目案例》有几个有uniapp教程&#xff0c;企业实战开发。《微服务实战》专栏是本人的实战经验总结&#xff0c;…

新晋项目经理,如何快速胜任?

第一次当项目经理&#xff0c;往往会由于经验不足、项目管理知识的不足以及角色转换等原因&#xff0c;无从着手。 有时候我们会觉得一个项目经理&#xff0c;不像项目经理&#xff0c;那像什么呢&#xff1f;当然是像程序员。也就是说&#xff0c;他的职位虽然变化了&#x…

【MATLAB图像处理实用案例详解(16)】——利用概念神经网络实现手写体数字识别

目录 一、问题描述二、概念神经网络实现手写体数字识别原理三、算法步骤3.1 数据输入3.2 特征提取3.3 模型训练3.4 测试 四、运行结果 一、问题描述 手写体数字属于光学字符识别&#xff08;Optical Character Recognition&#xff0c;OCR&#xff09;的范畴&#xff0c;但分类…

07 【Sass语法介绍-控制指令】

1.前言 Sass 为我们提供了很多控制指令&#xff0c;使得我们可以更高效的来控制样式的输出&#xff0c;或者在函数中进行逻辑控制。本节内容我们就来讲解什么是 Sass 控制指令&#xff1f;它能用来做什么&#xff1f;它将使你更方便的编写 Sass 。 2.什么是 Sass 控制指令 控…

松下 OPF CMOS影像传感器

一、概述 不久前&#xff0c;松下在其国际网站公布了关于有机光电导膜&#xff08;OPF&#xff09;CMOS影像传感器技术的最新研发进展&#xff0c;并表示该技术已趋于成熟&#xff0c;有望在未来一段时间内正式投入商用。此外&#xff0c;松下还在3月15日至16日&#xff0c;于…

Oracle LiveLabs DB Security (数据库安全)实验汇总

在Oracle LiveLabs中&#xff0c;和数据库安全相关的实验分为2个系列&#xff0c;共12个实验。 Oracle数据库安全架构如下图&#xff1a; 这些实验涉及了Oracle安全相关的特性&#xff0c;企业版选件&#xff0c;独立产品和服务。 关于Oracle安全产品的中文主页可见&#…

Marior去除边距和迭代内容矫正用于自然文档矫正

一、简要介绍 本文简要介绍了论文**“ Marior: Margin Removal and Iterative Content Rectification for Document Dewarping in the Wild ”的相关工作。照相机捕捉到的文档图像通常会出现透视和几何变形。考虑到视觉美感较差和OCR系统性能下降&#xff0c;对其进行纠正具有重…

JavaScript实现输入圆的半径,输出周长、体积和面积的代码

以下为输入圆的半径,输出周长、体积和面积实现结果的代码和运行截图 目录 前言 一、请输入圆的半径,输出周长、体积和面积 1.1运行流程及思想 1.2代码段 1.3 JavaScript语句代码 1.4运行截图 前言 1.若有选择&#xff0c;您可以在目录里进行快速查找&#xff1b; 2.本博…

【crontab】如何解决命令末尾自动加^M,导致不生效的问题

目录 场景&#xff1a; 问题&#xff1a; 问题原因&#xff1a; 解决方案&#xff1a; Step 1&#xff1a;编辑文件yolov5 &#xff0c;并查看文件类型 Step 2&#xff1a;修改文件类型 yolov5 Step 3&#xff1a;yolov5中的定时任务加入到crontab中,并查看crontab 列表…

前端性能优化总结(SPA篇)

性能优化 所有开发者都无法避免的一个问题&#xff0c;即关于项目的性能优化。性能优化是一个经久不衰的问题&#xff0c;它几乎贯穿于整个项目的开发过程。做好性能优化的项目不仅能在用户体验上更胜一筹&#xff0c;还能让服务资源的分配更加的合理。 关于SPA&#xff08;单…

互联网医院开发|线上问诊系统开发|互联网医院功能开发

互联网医院在智慧医疗版块可以算的上是“核心成员”&#xff0c;无论是出色的实战能力还是在管理功能模块上&#xff0c;都为行业带来了切实的便利&#xff0c;就例如平时的工作安排&#xff0c;省时省力&#xff0c;也让患者有更方便的就医条件&#xff0c;互联网医院系统源码…

必须掌握的重写,重载,equals,==

生活是晨起暮落&#xff0c;日子是柴米油盐&#xff0c;时光匆匆&#xff0c;我们终将释怀。 重写与重载的区别 重写(Override) 1.发生在父类与子类之间 2.方法名&#xff0c;参数列表&#xff0c;返回类型必须相同 3.访问修饰符的限制一定要大于被重写方法的访问修饰符,如果…

【sunny land】利用Animation编辑器实现近战敌人判定

昨晚研究了一晚Boss近战判定&#xff0c;也找了一些方法&#xff0c;但始终找不到合适的 今天终于让我找到了[泪目] 让我们先看演示 这个效果是我们的Boss挥刀时不造成伤害&#xff0c;当火焰冒出来时再对主角造成伤害。 这个我讲详细点吧 步骤&#xff1a; 首先&#xff…

矿井水行业氟超标的解决方法

高矿化度的废水是指含有高浓度溶解性矿物质的废水&#xff0c;通常指的是含有高浓度钠、钙、镁、铁、铝、钾等离子的废水。这些离子通常来自于废水所处的环境、工业或生产过程中使用的原材料和化学品。高矿化度的废水通常具有高盐度、高电导率、高硬度等特征&#xff0c;对环境…

三星弃Google用Bing?谷歌赶工新AI搜索Magi

“三星考虑将手机端的默认搜索引擎由Google换成Bing”&#xff0c;《纽约时报》上的这则消息披露次日&#xff0c;微软股价上涨2%&#xff0c;谷歌母公司Alphabet股价下跌3%。 过去20年里&#xff0c;谷歌一直是在线搜索领域无人能敌的霸主&#xff0c;微软旗下的Bing只在3%-5…

录屏文件太大怎么办?您可以这样做!

案例&#xff1a;录制的录屏文件体积比较大怎么办&#xff1f; 【我发现我录制的电脑录屏文件体积比较大&#xff0c;不好保存&#xff0c;会占用电脑的内存。我想知道怎样才可以录制体积较小的录屏&#xff1f;有没有小伙伴有解决的办法&#xff1f;】 录屏是我们经常需要用…

ATTCK v12版本战术介绍——防御规避(四)

一、引言 在前几期文章中我们介绍了ATT&CK中侦察、资源开发、初始访问、执行、持久化、提权战术理论知识及实战研究、部分防御规避战术&#xff0c;本期我们为大家介绍ATT&CK 14项战术中防御规避战术第19-24种子技术&#xff0c;后续会介绍防御规避其他子技术&#xf…