当前位置: 首页 > news >正文

[Java EE] 多线程 -- 初阶(3)

6.3 死锁

死锁的核心 : 多个线程互相持有对方所需要的锁且都不释放

① 情况一 : 一个线程获取同一把锁 , 并加锁多次

这种情况在 java 中不会出现死锁

② 情况二 : 两个线程 , 两把锁 , 每个线程获取到一把锁后 , 尝试获取另一把锁 , 会产生死锁

public class demo20 {public static void main(String[] args) throws InterruptedException {Object locker1 = new Object();Object locker2 = new Object();Thread t1 = new Thread(()->{synchronized (locker1){System.out.println("开始执行t1线程");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException();}synchronized (locker2){System.out.println("t1线程结束");}}});Thread t2 = new Thread(()->{synchronized (locker2){System.out.println("开始执行t2线程");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException();}synchronized (locker1){System.out.println("t2线程结束");}}});System.out.println("main线程开始");t1.start();t2.start();t1.join();t2.join();System.out.println("main线程结束");}
}

但为了两个线程都能获取到锁 , 必须在每个线程获取到第一个锁后加入Thread.sleep(1000) ; 两个线程相互竞争 , 互不相让 ; (家钥匙锁车里了 , 车钥匙锁家里了)

只要线程中出现交叉等待锁的情况 , 仍然可能死锁

6.4 如何解决或避免死锁

① 构成死锁的必要条件

  1. 锁是互斥的 , 一个线程拿到锁之后 , 另一个线程在尝试获取锁 , 必然会阻塞等待
  2. 锁是不可抢占的 , 线程1 拿到锁 , 线程2 也尝试获取这个锁 , 线程2 必会陷入阻塞等待
  3. 请求和保持 , 一个线程拿到锁1 之后 , 不释放锁1 的前提下获取锁 2 ; 也就是 使用锁的时候尽量避免嵌套
  4. 循环等待 , 多个线程 , 多把锁之间的等待过程构成了循环 ; A 等待 B , B 也等待 A ;

② 解决死锁

在 Java 中 synchronized 是遵守前两个条件的 ; 所以只能从后两点解决

1) 请求和保持
public class demo21 {public static void main (String[]args) throws InterruptedException {Object locker1 = new Object();Object locker2 = new Object();Thread t1 = new Thread(() -> {synchronized (locker1) {System.out.println("开始执行t1线程");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException();}}synchronized (locker2) {System.out.println("t1线程结束");}});Thread t2 = new Thread(() -> {synchronized (locker2) {System.out.println("开始执行t2线程");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException();}}synchronized (locker1) {System.out.println("t2线程结束");}});System.out.println("main线程开始");t1.start();t2.start();t1.join();t2.join();System.out.println("main线程结束");}
}

解决核心 : 避免锁嵌套

2) 循环等待
public class demo21 {public static void main (String[]args) throws InterruptedException {Object locker1 = new Object();Object locker2 = new Object();Thread t1 = new Thread(() -> {synchronized (locker1) {System.out.println("开始执行t1线程");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException();}synchronized (locker2) {System.out.println("t1线程结束");}}});Thread t2 = new Thread(() -> {synchronized (locker1) {System.out.println("开始执行t2线程");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException();}synchronized (locker2) {System.out.println("t2线程结束");}}});System.out.println("main线程开始");t1.start();t2.start();t1.join();t2.join();System.out.println("main线程结束");}
}

解决核心 : 约定每个线程加锁的顺序 , 例如 : 按序号从小到大的顺序进行加锁

7.再谈内存可见性问题

7.1 可见性

一个线程对共享变量值的修改,能够及时的被其他线程看到

7.2 内存可见性

一个线程对共享变量的修改 , 其他线程不能及时看到

这是由于 JVM 的内存模型(JMM)中 , 线程会将共享变量从主内存拷贝到自己工作内存中进行操作 , 若缺乏同步机制 , 不同线程的工作内存数据可能不一致

7.3 产生原因

CPU 缓存机制

现代 CPU 有多级缓存 , 线程操作变量时 先再缓存中执行 , 未及时同步到主内存 , 其他线程从主内存读取的还是旧值

编译器优化

编译器可能对代码进行重排序或缓存变量(如将变量缓存到寄存器) , 导致变量修改对其他线程不可见

实例说明 :

import java.util.Scanner;public class demo22 {private static int flag = 0;public static void main(String[] args) {Thread t1 = new Thread(()->{while(flag == 0){}System.out.println("t1线程结束");});Thread t2 = new Thread(()->{//针对flag进行修改Scanner scanner = new Scanner(System.in);System.out.println("请输入flag的值");flag = scanner.nextInt();});t1.start();t2.start();}
}

上述代码中 , 线程2 修改 flag 后 , 线程1 可能因内存可见性问题一直卡再 while 循环中 , 无法感知 flag 的变化

解决方法

① 对 while 循环微调 , 让编译器不做优化
import java.util.Scanner;public class demo22 {private static int flag = 0;public static void main(String[] args) {Thread t1 = new Thread(()->{while(flag == 0) {try {Thread.sleep(1);}catch (InterruptedException e){throw new RuntimeException();}}System.out.println("t1线程结束");});Thread t2 = new Thread(()->{//针对flag进行修改Scanner scanner = new Scanner(System.in);System.out.println("请输入flag的值");flag = scanner.nextInt();});t1.start();t2.start();}
}

本质上 : 增加了每次 while 循环的时间 , 此时编译器就不会将 读内存操作优化为度寄存器操作了 , 因为优化的时间无足轻重

② volatile 关键字

作用 : 会禁止编译器和 CPU 的重排序优化 , 且写操作会立即同步到主内存 , 读操作会从主内存中读取 , 从而保证可见性

import java.util.Scanner;public class demo22 {private volatile static int flag = 0;public static void main(String[] args) {Thread t1 = new Thread(()->{while(flag == 0) {}System.out.println("t1线程结束");});Thread t2 = new Thread(()->{//针对flag进行修改Scanner scanner = new Scanner(System.in);System.out.println("请输入flag的值");flag = scanner.nextInt();});t1.start();t2.start();}
}

注意 : volatile 不保证原子性 , 与 sunchronized 着本质的区别

8. wait() 和 notify()

在 Java 中 wait() 和 notify()是 Object 类的方法 , 用于线程间的协作 , 通常配合 synchronized 关键字使用 , 实现线程的等待与唤醒机制

8.1 核心作用 :

wait() / wait(long timeout) :

        作用 : 让当前线程释放持有的锁 , 并进入阻塞等待 , 直到其他线程调用同一对象的 notify()或 notifyAll()唤醒

        wait 结束等待的条件 : ① 其他线程调用相同锁对象的 notify()方法 ; ②wait(timeout) 的等待时间超时 ; ③ 其他线程调用该线程的 interrupted 方法 , 导致 wait 抛出 InterruptedException 异常

执行结果 : 从控制台看出 , 程序执行到 wait()后进入阻塞 , 这符合 wait()方法的特性 -- 若无其他线程唤醒 , 当前线程会一直等待

notify() :

        唤醒在次对象锁上等待的任意一个线程(如果是多个线程对应同一个锁对象 , 那么具体唤醒哪个是随机的) , 使其从等待状态进入就绪状态 , 重新竞争锁

使用时 务必要确保先 wait()再 notify()才会起作用 ;

notifyAll() :

        唤醒在此对象锁上等待的所有线程 , 让它们重新竞争锁(但不要过于依赖)

示例 1 :

import java.util.Scanner;public class demo24 {public static void main(String[] args) {Object locker1 = new Object();Object locker2 = new Object();Thread t1 = new Thread(()->{try{System.out.println("wait之前");synchronized (locker1){locker1.wait();//需要借助锁对象操作}System.out.println("wait之后");}catch (InterruptedException e){throw new RuntimeException();}});Thread t2 = new Thread(()->{Scanner scanner = new Scanner(System.in);System.out.println("输入任意内容唤醒 t1");scanner.next();synchronized (locker1){locker1.notify();//同样需要锁锁对象操作}});t1.start();t2.start();}
}

使用条件 :

  1. 必须在 synchronized 代码块中调用

因为 wait() 和 notify() 需要操作对象的锁 , 调用前必须确保当前线程已获取该对象的锁(否则抛出异常)

  1. 操作的必须是同一把锁

线程 A 在锁对象lock上调用wait(),只有线程 B 在同一lock上调用notify(),才能唤醒线程 A

示例 2 :

import java.util.Scanner;public class demo25 {public static void main(String[] args) {Object locker = new Object();Thread t1 = new Thread(()->{try{System.out.println("t1 wait之前");synchronized (locker){locker.wait();}System.out.println("t1 wait之后");}catch (InterruptedException e){throw new RuntimeException();}});Thread t2 = new Thread(()->{try{System.out.println("t2 wait之前");synchronized (locker){locker.wait();}System.out.println("t2 wait之后");}catch (InterruptedException e){throw new RuntimeException();}});Thread t3 = new Thread(()->{Scanner scanner = new Scanner(System.in);System.out.println("输入任意内容唤醒所有线程");scanner.next();synchronized (locker){locker.notifyAll();}});//        Thread t3 = new Thread(()->{//            Scanner scanner = new Scanner(System.in);//            System.out.println("输入任意内容,唤醒线程");//            scanner.next();//            synchronized (locker){//                locker.notify();//            }//            System.out.println("输入任意内容,唤醒另一个线程");//            scanner.next();//            synchronized (locker){//                locker.notify();//            }////        });t1.start();t2.start();t3.start();}

8.3 与sleep()的区别

特性

wait()

sleep(long)

锁释放

释放持有的锁

不释放锁

所属类

Object

Thread

使用场景

线程间协作(等待 / 唤醒)

单纯延迟执行

唤醒方式

需其他线程notify()

唤醒

时间到后自动唤醒

http://www.dtcms.com/a/607454.html

相关文章:

  • 网站建设中成本怎么描述推动高质量发展心得
  • 徐州市经济技术开发区建设局网站海尔网站建设的缺点
  • 网站页面组成研艺影楼网站建设
  • 个人网站排版设计企业qq邮箱登录入口
  • LoRaWAN通信协议详解:架构、加密机制与核心应用
  • 佛山 网站北京丰台网站优化
  • 培训班网站建设wordpress登陆可见
  • 网站关键词推广哪家好品牌定位策略
  • 网站 建设 汇报办公室装修效果实景图
  • 福永附近网站建设公司引流推广app
  • css,控制超出部分隐藏,显示... css,控制超出部分不隐藏,换行
  • 网站直播的功能怎样做小程序源码网免费
  • 第三章 Android常见界面控件
  • 上海 顶尖 网站设计主流网站关键词排名
  • 网站建设合同缴纳印花税吗手机分销网站建设
  • 徐州市经济技术开发区建设局网站公司网站如何被收录
  • 【连接器专题】电缆端子压接有关的术语
  • 假发网站是怎么做的wordpress 导入word
  • 网站建设 锐颖科技婚纱摄影网站源码asp
  • 微网站 留言板海外广告投放公司
  • 玩转Docker | 使用Docker部署Dokuwiki个人wiki系统
  • php mysql做网站网站图文列表
  • Spring Boot管理用户数据
  • python 网站开发实例教程邮件营销 wordpress
  • 网络服务器端口怎么查青海seo技术培训
  • 做php网站前端价格网站对网络营销的作用
  • 口碑好的盘锦网站建设免费找工程项目承包软件
  • 个人网站erp网站开发
  • 网站外接学软件工程培训就业机构
  • 如何提升网站的排名wordpress需要账号