03_线程间通信

面试题:两个线程打印

两个线程,一个线程打印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 代表死锁

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

分布式事务处理方案及分布式锁相关

​ 本文偏理论 一、事务处理 1、事务处理的四个特性ACID Atomicity 原子性: 对于数据库的修改&#xff0c;全部执行or全部不执行 Consistency 一致性: Isolation 隔离性 : 亦称为串行化&#xff0c;防止事务间操作混淆&#xff0c;需要串行化或者序列化请求&#xff0c;使…

隐私权限是什么

导读&#xff1a; 隐私权在现代社会对于人们而言是重要的人格权&#xff0c;而随着互联网技术的发展&#xff0c;实践中侵犯隐私权的行为很常见。那么隐私权限是什么&#xff1f;侵犯隐私权的行为有哪些&#xff1f;侵犯他人隐私权要负什么法律责任&#xff1f;接下来将由找法…

使用docker搭建RocketMQ(非集群搭建官方镜像)

之前在使用 RocketMQ 官方的包在搭建的时候&#xff0c;发现好多问题&#xff0c;什么修改内存大小&#xff0c;然后启动 broker 报错&#xff0c;类似 service not available now, maybe disk full 等等… 最后决定还是重新用 docker 搭建下&#xff0c;感觉这样子玩坏了&…

微信仿真平台的设计和实现(设计+源码)_kaic

摘要 现如今&#xff0c;科技的发展带动着环保方式的更新&#xff0c;Internet是一个不断的开展和不停的扩充数据潮流&#xff0c;有了它&#xff0c;我们可以快速、容易地在世界的任何角落进行沟通&#xff0c;获取更多的信息与资料。Internet可以提供大量信息资源和文案数据库…

全国各省份影像下载地址(11级别)

安徽省https://pan.baidu.com/s/1fMuWhVZFvSH1UlCGU1bPpA?pwdeasy澳门特别行政区https://pan.baidu.com/s/1aU2D4o2bfeHTJTb6AkUtVA?pwdeasy北京市https://pan.baidu.com/s/1eaNzAWm1pUx_rjhD_wHHhA?pwdeasy福建省百度网盘 请输入提取码甘肃省https://pan.baidu.com/s/1mAqf…

时序预测相关技术分享

时序预测相关技术分享 时序预测是指对时间序列数据进行预测&#xff0c;以预测未来的趋势或行为。在实际生产和应用中&#xff0c;时序预测广泛应用于金融、电力、交通等领域。时序预测可以帮助人们更好地理解和掌握未来的趋势和规律&#xff0c;从而做出更明智的决策。 时序…

南大通用数据库-Gbase-8a-学习-34-gcdump(导出数据库对象定义)

目录 一、测试环境 二、介绍 三、命令帮助 四、参数介绍 1、--print-defaults &#xff08;1&#xff09;含义 &#xff08;2&#xff09;例子 2、--connect_timeout &#xff08;1&#xff09;含义 &#xff08;2&#xff09;例子 3、-A, --all-databases &#xf…

10款比较好用的网页设计工具

网页设计软件的轻量化和在线协作已成为当前网页制作软件的发展趋势。网页设计并不容易&#xff0c;易于使用的网页UI设计软件更难找到。随着网络的快速发展&#xff0c;网站迅速崛起&#xff0c;网页设计也很受欢迎。网页设计软件即时设计是一种在线协作设计工具&#xff0c;深…

密歇根大学Python系列之一:零基础 Python 入门

密歇根大学计算机专业注重将计算机科学理论与实践相结合&#xff0c;旨在帮助学生全面掌握计算机科学的基础理论和实践技能&#xff1a; 1.计算机程序设计&#xff1a;编程技能和常见编程语言&#xff0c;如C和Java和Python等&#xff1b; 2.数据结构和算法&#xff1a;数据结…

不同局域网下使用Python自带HTTP服务进行文件共享「端口映射」

文章目录 1. 前言2. 视频教程3. 本地文件服务器搭建3.1 python的安装和设置3.2 cpolar的安装和注册 4. 本地文件服务器的发布4.1 Cpolar云端设置4.2 Cpolar本地设置 5. 公网访问测试6. 结语 转载自内网穿透工具的文章&#xff1a;Python一行代码实现文件共享【内网穿透公网访问…

SpringBoot的配置和日志

1.配置文件的作用和意义 配置文件中配置整个项目中所有重要的数据&#xff0c;比如&#xff1a; 1.数据库的连接信息&#xff08;包含用户名和密码的设置&#xff09;&#xff1b; 2.项目的启动端口&#xff1b; 3.第三方系统的调用秘钥等信息&#xff1b; 4.用于发现和定位问…

如何利用 GPT 开发智能应用?微软提出这九大原则

毫无疑问&#xff0c;GPT 的出现以日新月异的速度改写了软件开发的工作方式。 兴奋&#xff0c;也许也有点焦虑。作为开发人员&#xff0c;如何在 GPT 时代与人工智能协作&#xff1f;在「万物皆有副驾驶」的年代&#xff0c;如何划定分工边界&#xff0c;让 GPT 成为开发人员…

如何使用命令行添加配置码云仓库SSH秘钥-git仓库也一样

使用命令行添加配置码云仓库SSH秘钥 为什么要如何使用命令行添加配置码云仓库SSH秘钥&#xff1f;生成密钥你可以按如下命令来生成 sshkey:可以参考下图执行指令 添加密钥登录你的码云&#xff0c;鼠标移入头像&#xff0c;设置。点击 SSH公钥&#xff0c;打开配置页面&#x…

【LeetCode: 1143. 最长公共子序列 | 暴力递归=>记忆化搜索=>动态规划】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

如何在比特币系统内创造人工生命

信息来源&#xff1a;coingeek.com 自2015年以来&#xff0c;关于比特币能否进行复杂计算以及比特币是否“图灵完备”的争论一直在持续。不幸的是&#xff0c;现在存在着一种流传甚广的谬论&#xff0c;有人说比特币并非图灵完备的&#xff0c;它不能像以太坊区块链那样进行复杂…

第四章 使用Maven:IDEA环境

1、创建 Project2、开启自动导入 TIP 各个 IDEA 不同版本在具体操作方面存在一定差异&#xff0c;这里我们以 2019.3.3 版本为例进行演示。其它版本大家灵活变通即可。 第一节 创建父工程 创建 Project 开启自动导入 创建 Project 后&#xff0c;IDEA 会自动弹出下面提示…

ROS学习5:ROS常用组件

【Autolabor初级教程】ROS机器人入门 1. TF 坐标变换 背景 现有一移动式机器人底盘&#xff0c;在底盘上安装了一雷达&#xff0c;雷达相对于底盘的偏移量已知&#xff0c;现雷达检测到一障碍物信息&#xff0c;获取到坐标分别为(x,y,z)&#xff0c;该坐标是以雷达为参考系的…

快速下载VScode并配置Python运行环境【详细教程】

快速下载VScode并配置Python运行环境【详细教程】 博主&#xff1a;命运之光 目录 快速下载VScode并配置Python运行环境【详细教程】前言下载vscode第一步vscode官网下载第二步点击下载![请添加图片描述](https://img-blog.csdnimg.cn/1d76c427314b4ddcbd350e0a7e5449d5.png)第…

数据湖Iceberg-存储结构(2)

文章目录 存储结构数据文件 data files表快照 Snapshot清单列表 Manifest list清单文件 Manifest file 数据湖Iceberg-简介(1) 数据湖Iceberg-存储结构(2) 数据湖Iceberg-Hive集成Iceberg(3) 数据湖Iceberg-SparkSQL集成(4) 数据湖Iceberg-FlinkSQL集成(5) 数据湖Iceberg-Flink…

【移动端网页布局】流式布局案例 ① ( 视口标签设置 | CSS 样式文件设置 | 布局宽度设置 | 设置最大宽度 | 设置最小宽度 )

文章目录 一、视口标签设置二、CSS 样式文件设置三、布局宽度设置1、设置布局宽度2、设置布局最大宽度3、设置布局最小宽度4、查看网页最大最小宽度5、布局宽度设置 四、代码示例1、主界面标签2、CSS 布局设置 一、视口标签设置 参考 【移动端网页布局】移动端网页布局基础概念…