死锁

什么是死锁?

什么情况下会产生死锁?

生产者与消费者

什么是生产者与消费者?

Object类的等待和唤醒方法

生产者-消费者案例(唤醒机制)

基本写法

代码书写技巧与“套路”

代码优化: ​​​​​

阻塞队列(唤醒机制)

继承结构

基本实现

示例代码

put 与 take底层逻辑


死锁

什么是死锁?

线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行

什么情况下会产生死锁?

(1)资源有限

(2)同步嵌套

这里我们使用的是Lambda的写法

Coding:

package 多线程.ThreadDemo09_死锁;public class Demo {public static void main(String[] args) {Object objA = new Object();Object objB = new Object();new Thread(()->{while(true){synchronized (objA){//线程一synchronized (objB){System.out.println("小康同学正在走路");}}}}).start();new Thread(()->{while(true){synchronized (objB){//线程二synchronized (objA){System.out.println("小薇同学正在走路");}}}}).start();}
}

显然,假设 线程一 先获取了CPU资源,代码运行到第10行,线程一发现objA锁是开的,则进去,objA锁上,然后时间片到,CPU使用权切换到 线程二 ,代码运行到第21行,线程二发现objB是开的,则进去,objB锁上,此时死锁发生!(即objA锁 与 objB锁同时锁上了)

代码常规写法(不使用Lambda)

Coding:

public class MyRunnable implements Runnable{Object a = new Object();Object b = new Object();@Overridepublic void run() {if ("小明".equals(Thread.currentThread().getName())) {while(true) {synchronized (a) {synchronized (b) {System.out.println(Thread.currentThread().getName() + "正在走路");}}}}if ("小红".equals(Thread.currentThread().getName())) {while(true) {synchronized (b) {synchronized (a) {System.out.println(Thread.currentThread().getName() + "正在走路");}}}}}
}
public class Demo2 {public static void main(String[] args) {MyRunnable mr = new MyRunnable();Thread t1 = new Thread(mr);Thread t2 = new Thread(mr);t1.setName("小明");t2.setName("小红");t1.start();t2.start();}
}

如何解决?

其中一种方法就是引入 生产者与消费者问题

生产者与消费者

什么是生产者与消费者?

生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻。

所谓生产者消费者问题,实际上主要是包含了两类线程:

一类是生产者线程用于生产数据

一类是消费者线程用于消费数据

为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库

生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为

消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为

Object类的等待和唤醒方法

方法名 说明
void wait() 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法
void notify() 唤醒正在等待对象监视器的单个线程
void notifyAll() 唤醒正在等待对象监视器的所有线程

生产者-消费者案例(唤醒机制)

基本写法

  • 桌子类(Desk):定义表示包子数量的变量,定义锁对象变量,定义标记桌子上有无包子的变量

  • 生产者类(Cooker):实现Runnable接口,重写run()方法,设置线程任务

    1.判断是否有包子,决定当前线程是否执行

    2.如果有包子,就进入等待状态,如果没有包子,继续执行,生产包子

    3.生产包子之后,更新桌子上包子状态,唤醒消费者消费包子

  • 消费者类(Foodie):实现Runnable接口,重写run()方法,设置线程任务

    1.判断是否有包子,决定当前线程是否执行

    2.如果没有包子,就进入等待状态,如果有包子,就消费包子

    3.消费包子后,更新桌子上包子状态,唤醒生产者生产包子

  • 测试类(Demo):里面有main方法,main方法中的代码步骤如下

    创建生产者线程和消费者线程对象

    分别开启两个线程

Coding:

public class Demo {public static void main(String[] args) {/*消费者步骤:1,判断桌子上是否有汉堡包。2,如果没有就等待。3,如果有就开吃4,吃完之后,桌子上的汉堡包就没有了叫醒等待的生产者继续生产汉堡包的总数量减一*//*生产者步骤:1,判断桌子上是否有汉堡包如果有就等待,如果没有才生产。2,把汉堡包放在桌子上。3,叫醒等待的消费者开吃。*/Foodie f = new Foodie();Cooker c = new Cooker();f.start();c.start();}
}
public class Desk {//定义一个标记//true 就表示桌子上有汉堡包的,此时允许吃货执行//false 就表示桌子上没有汉堡包的,此时允许厨师执行public static boolean flag = false;//汉堡包的总数量public static int count = 10;//锁对象//使用final关键字 表示lock一经设定,就不再更改public static final Object lock = new Object();
}
public class Foodie extends Thread {@Overridepublic void run() {/***  1,判断桌子上是否有汉堡包。*  2,如果没有就等待。*  3,如果有就开吃*  4,吃完之后,桌子上的汉堡包就没有了*     叫醒等待的生产者继续生产*     汉堡包的总数量减一*/while(true){//锁对象 Desk.locksynchronized (Desk.lock){if(Desk.count == 0){break;}else{//判断桌子上是否有汉堡包if(Desk.flag){//有System.out.println("吃货在吃汉堡包");Desk.flag = false;Desk.lock.notifyAll();Desk.count--;}else{//没有就等待//使用什么对象当做锁,那么就必须用这个对象去调用等待和唤醒的方法.try {Desk.lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}}
}
package 多线程.ThreadDemo10_生消;public class Cooker extends Thread {/*** 生产者步骤:*   1,判断桌子上是否有汉堡包*      如果有就等待,如果没有才生产。*   2,把汉堡包放在桌子上。*   3,叫醒等待的消费者开吃。*/@Overridepublic void run() {while(true){synchronized (Desk.lock){if(Desk.count == 0){break;}else{if(!Desk.flag){//生产System.out.println("厨师正在生产汉堡包");Desk.flag = true;Desk.lock.notifyAll();}else{try {Desk.lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}}
}

代码书写技巧与“套路”:
        1. while(true)死循环
        2. synchronized 锁,锁对象要唯一
        3. 判断,共享数据是否结束. 结束
        4. 判断,共享数据是否结束. 没有结束

代码优化: ​​​​​

  • 将Desk类中的变量,采用面向对象的方式封装起来

  • 生产者和消费者类中构造方法接收Desk类对象,之后在run方法中进行使用

  • 创建生产者和消费者线程对象,构造方法中传入Desk类对象

  • 开启两个线程

Coding:

public class Demo {public static void main(String[] args) {Desk desk = new Desk();Foodie f = new Foodie(desk);Cooker c = new Cooker(desk);f.start();c.start();}
}
public class Desk {//定义一个标记private boolean flag;//汉堡包的总数量private int count;//锁对象private final Object lock = new Object();public Desk() {this(false,10);// 在空参内部调用带参,对成员变量进行赋值,之后就可以直接使用成员变量了}public Desk(boolean flag, int count) {this.flag = flag;this.count = count;}//boolean的不是 getFlag() 而是 isFlag()public boolean isFlag() {return flag;}public void setFlag(boolean flag) {this.flag = flag;}public int getCount() {return count;}public void setCount(int count) {this.count = count;}//final修饰的没有Setter方法public Object getLock() {return lock;}@Overridepublic String toString() {return "Desk{" +"flag=" + flag +", count=" + count +", lock=" + lock +'}';}
}
public class Cooker extends Thread {private Desk desk;public Cooker(Desk desk) {this.desk = desk;}@Overridepublic void run() {while(true){synchronized (desk.getLock()){if(desk.getCount() == 0){break;}else{//System.out.println("验证一下是否执行了");if(!desk.isFlag()){//生产System.out.println("厨师正在生产汉堡包");desk.setFlag(true);desk.getLock().notifyAll();}else{try {desk.getLock().wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}}
}
public class Foodie extends Thread {private Desk desk;public Foodie(Desk desk) {this.desk = desk;}@Overridepublic void run() {while(true){synchronized (desk.getLock()){if(desk.getCount() == 0){break;}else{//System.out.println("验证一下是否执行了");if(desk.isFlag()){//有System.out.println("吃货在吃汉堡包");desk.setFlag(false);desk.getLock().notifyAll();desk.setCount(desk.getCount() - 1);}else{//没有就等待//使用什么对象当做锁,那么就必须用这个对象去调用等待和唤醒的方法.try {desk.getLock().wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}}
}

阻塞队列(唤醒机制)

继承结构

基本实现

常见BlockingQueue:

ArrayBlockingQueue: 底层是数组,有界

LinkedBlockingQueue: 底层是链表,无界.但不是真正的无界,最大为int的最大值

BlockingQueue的核心方法:

put(anObject): 将参数放入队列,如果放不进去会阻塞

take(): 取出第一个数据,取不到会阻塞

示例代码:

public class Demo02 {public static void main(String[] args) throws Exception {// 创建阻塞队列的对象,容量为 1ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(1);// 存储元素arrayBlockingQueue.put("汉堡包");// 取元素System.out.println(arrayBlockingQueue.take());System.out.println(arrayBlockingQueue.take()); // 取不到会阻塞//这一句不会执行,因为上一句code阻塞了System.out.println("程序结束了");}
}

put 与 take底层逻辑

源码如下:

put的底层

    public void put(E e) throws InterruptedException {Objects.requireNonNull(e);ReentrantLock lock = this.lock;lock.lockInterruptibly();try {while(this.count == this.items.length) {this.notFull.await();}this.enqueue(e);} finally {lock.unlock();}}

take的底层

    public E take() throws InterruptedException {ReentrantLock lock = this.lock;lock.lockInterruptibly();Object var2;try {while(this.count == 0) {this.notEmpty.await();}var2 = this.dequeue();} finally {lock.unlock();}return var2;}

显然在take与put方法中,都有获取锁lock与释放锁unlock,保证等待与唤醒机制的运行。

阅读下章可见

Java多线程编程(五)——线程池https://blog.csdn.net/weixin_43715214/article/details/122401766

Java多线程编程(四)——死锁问题相关推荐

  1. java多线程编程01---------基本概念

    一. java多线程编程基本概念--------基本概念 java多线程可以说是java基础中相对较难的部分,尤其是对于小白,次一系列文章的将会对多线程编程及其原理进行介绍,希望对正在多线程中碰壁的小 ...

  2. java多线程基础视频_【No996】2020年最新 Java多线程编程核心基础视频课程

    01.课程介绍.mp4 02.多线程编程基础-进程与线程.mp4 03.多线程编程基础-使用多线程-继承Thread类.mp4 04.多线程编程基础-使用多线程-实现Runnable接口.mp4 05 ...

  3. Java多线程编程-(4)-线程间通信机制的介绍与使用

    上一篇: Java多线程编程-(1)-线程安全和锁Synchronized概念 Java多线程编程-(2)-可重入锁以及Synchronized的其他基本特性 Java多线程编程-(3)-线程本地Th ...

  4. 计算机是如何工作的,Java多线程编程

    一.冯诺依曼体系 现代的计算机,大多遵守 冯诺依曼体系结构 (Von Neumann Architecture) CPU 中央处理器: 进行算术运算和逻辑判断. AMD Ryzen 7 580OU w ...

  5. 《Java多线程编程核心技术》读书笔记

    为什么80%的码农都做不了架构师?>>>    <Java多线程编程核心技术>读书笔记. ###第一章 Java多线程技能 使用Java多线程两种方式. 继承Thread ...

  6. Java多线程开发(一)Java多线程编程简介

    文章目录 参考 Java线程简介 Thread类构造方法和属性 常用Thread类方法 线程的生命周期 多线程编程的优势和风险 安全性问题 活跃性问题 性能问题 参考 [Java并发系列01]Thre ...

  7. java多线程编程从入门到卓越(超详细总结)

    导读:java多线程编程不太熟?或是听说过?或是想复习一下?找不到好的文章?别担心我给你们又安利一波,文章内容很全,并且考虑到很多开发中遇到的问题和解决方案.循环渐进,通俗易懂,文章较长,建议收藏再看 ...

  8. Java 多线程编程核心技术

    课程介绍 多线程编程在最大限度利用计算资源.提高软件服务质量方面扮演着至关重要的角色,而掌握多线程编程也成为了广大开发人员所必须要具备的技能. 本课程以基本概念.原理方法为主线,每篇文章结合大量演示实 ...

  9. Java多线程编程-(6)-两种常用的线程计数器CountDownLatch和循环屏障CyclicBarrier

    前几篇: Java多线程编程-(1)-线程安全和锁Synchronized概念 Java多线程编程-(2)-可重入锁以及Synchronized的其他基本特性 Java多线程编程-(3)-线程本地Th ...

最新文章

  1. C-二维数组,多维数组
  2. Java源码解读--CopyOnWriteList写时复制集合容器
  3. 微擎任意消息该公众号提供的服务器,该公众号提供的服务出现故障,请稍后再试...
  4. 算法问题---两艘船是否有最大承载量
  5. Simulink之变压器漏抗对整流电路的影响
  6. 帝国CMS7.5小品屋在线小品相声视频网站模板修复版
  7. 收获,不止SQL优化——抓住SQL的本质--第七章
  8. error while loading shared libraries: libstdc++.so.6
  9. 使用 Item,ItemManager 在 XNA 中创建物品和道具(十六)
  10. win7(64位)php5.5-Apache2.4-环境安装
  11. 确定单峰区间Matlab,0618法matlab实验报告.doc
  12. 轴系ansys命令流建模
  13. SQL Server事物日志
  14. 0x7fffffff解析
  15. MISC机制编写字符驱动程序
  16. Java面向对象常见概念
  17. 无法启动MySQL服务,提示“错误1069,由于登录失败而无法启动服务”
  18. Java正则表达式验证电话号码
  19. 【接口测试之自动化】接口测试工具
  20. 基于树莓派的WireGuard安装配置与使用

热门文章

  1. zookeeper配置文件zoo.cfg详细讲解
  2. Cron表达式的语法及详细用法
  3. Bootstrap学习-详解Bootstrap下拉菜单组件
  4. XShell 上传文件时 乱码问题
  5. linux命令下载电影,linux命令行---用wget下载电影
  6. 计算机照片打印设置方法,详解设置打印机纸张添加7寸照片尺寸在win7电脑中的操作的步骤...
  7. JAVA 二叉树 常见操作合集(前中后序递归非递归遍历 层序遍历 求深度宽度 判断兄弟结点 堂兄弟节点)
  8. Motan服务的启动
  9. C语言程序设计第五章循环结构程序设计总结
  10. c蔚语言艺术,晚唐张乔诗歌的语言艺术与美学风格-中国社会科学网.PDF