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

JUC高并发编程5:多线程锁

1 锁的八个问题演示

  1. 标准访问,先打印短信还是邮件
class Phone{public synchronized void sendSMS() throws InterruptedException {System.out.println("----------sendSMS");}public synchronized void sendEmail(){System.out.println("-----------sendEmail");}public void getHello(){System.out.println("-------------getHello");}
}public class Lock_8 {public static void main(String[] args) throws InterruptedException {Phone phone = new Phone();new Thread(()->{try {phone.sendSMS();} catch (Exception e) {throw new RuntimeException(e);}},"AA").start();Thread.sleep(100);new Thread(()->{try {phone.sendEmail();} catch (Exception e) {throw new RuntimeException(e);}},"BB").start();}
}
  • 运行结果:
----------sendSMS
-----------sendEmail
  1. 停4秒在短信方法内,先打印短信还是邮件
class Phone{public synchronized void sendSMS() throws InterruptedException {TimeUnit.SECONDS.sleep(4);System.out.println("----------sendSMS");}public synchronized void sendEmail(){System.out.println("-----------sendEmail");}public void getHello(){System.out.println("-------------getHello");}
}public class Lock_8 {public static void main(String[] args) throws InterruptedException {Phone phone = new Phone();new Thread(()->{try {phone.sendSMS();} catch (Exception e) {throw new RuntimeException(e);}},"AA").start();Thread.sleep(100);new Thread(()->{try {phone.sendEmail();} catch (Exception e) {throw new RuntimeException(e);}},"BB").start();}
}
  • 运行结果:
----------sendSMS
-----------sendEmail
  1. 新增普通的hello方法,是先打短信还是hello
class Phone{public synchronized void sendSMS() throws InterruptedException {TimeUnit.SECONDS.sleep(4);System.out.println("----------sendSMS");}public synchronized void sendEmail(){System.out.println("-----------sendEmail");}public void getHello(){System.out.println("-------------getHello");}
}public class Lock_8 {public static void main(String[] args) throws InterruptedException {Phone phone = new Phone();new Thread(()->{try {phone.sendSMS();} catch (Exception e) {throw new RuntimeException(e);}},"AA").start();Thread.sleep(100);new Thread(()->{try {phone.getHello();} catch (Exception e) {throw new RuntimeException(e);}},"BB").start();}
}
  • 运行结果:
-------------getHello
----------sendSMS
  1. 现在有两部手机,先打印短信还是邮件
class Phone{public synchronized void sendSMS() throws InterruptedException {TimeUnit.SECONDS.sleep(4);System.out.println("----------sendSMS");}public synchronized void sendEmail(){System.out.println("-----------sendEmail");}public void getHello(){System.out.println("-------------getHello");}
}public class Lock_8 {public static void main(String[] args) throws InterruptedException {Phone phone = new Phone();Phone phone2 = new Phone();new Thread(()->{try {phone.sendSMS();} catch (Exception e) {throw new RuntimeException(e);}},"AA").start();Thread.sleep(100);new Thread(()->{try {phone2.sendEmail();} catch (Exception e) {throw new RuntimeException(e);}},"BB").start();}
}
  • 运行结果:
-----------sendEmail
----------sendSMS

5.两个静态同步方法,1部手机,先打印短信还是邮件

class Phone{public static synchronized void sendSMS() throws InterruptedException {TimeUnit.SECONDS.sleep(4);System.out.println("----------sendSMS");}public static synchronized void sendEmail(){System.out.println("-----------sendEmail");}public void getHello(){System.out.println("-------------getHello");}
}public class Lock_8 {public static void main(String[] args) throws InterruptedException {Phone phone = new Phone();new Thread(()->{try {phone.sendSMS();} catch (Exception e) {throw new RuntimeException(e);}},"AA").start();Thread.sleep(100);new Thread(()->{try {phone.sendEmail();} catch (Exception e) {throw new RuntimeException(e);}},"BB").start();}
}
  • 运行结果:
----------sendSMS
-----------sendEmail
  1. 两个静态同步方法,2部手机,先打印短信还是邮件
class Phone{public static synchronized void sendSMS() throws InterruptedException {TimeUnit.SECONDS.sleep(4);System.out.println("----------sendSMS");}public static synchronized void sendEmail(){System.out.println("-----------sendEmail");}public void getHello(){System.out.println("-------------getHello");}
}public class Lock_8 {public static void main(String[] args) throws InterruptedException {Phone phone = new Phone();Phone phone2 = new Phone();new Thread(()->{try {phone.sendSMS();} catch (Exception e) {throw new RuntimeException(e);}},"AA").start();Thread.sleep(100);new Thread(()->{try {phone2.sendEmail();} catch (Exception e) {throw new RuntimeException(e);}},"BB").start();}
}
  • 运行结果:
----------sendSMS
-----------sendEmail
  1. 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件
class Phone{public static synchronized void sendSMS() throws InterruptedException {TimeUnit.SECONDS.sleep(4);System.out.println("----------sendSMS");}public synchronized void sendEmail(){System.out.println("-----------sendEmail");}public void getHello(){System.out.println("-------------getHello");}
}public class Lock_8 {public static void main(String[] args) throws InterruptedException {Phone phone = new Phone();new Thread(()->{try {phone.sendSMS();} catch (Exception e) {throw new RuntimeException(e);}},"AA").start();Thread.sleep(100);new Thread(()->{try {phone.sendEmail();} catch (Exception e) {throw new RuntimeException(e);}},"BB").start();}
}
  • 运行结果
-----------sendEmail
----------sendSMS
    1. 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件
class Phone{public static synchronized void sendSMS() throws InterruptedException {TimeUnit.SECONDS.sleep(4);System.out.println("----------sendSMS");}public synchronized void sendEmail(){System.out.println("-----------sendEmail");}public void getHello(){System.out.println("-------------getHello");}
}public class Lock_8 {public static void main(String[] args) throws InterruptedException {Phone phone = new Phone();Phone phone2 = new Phone();new Thread(()->{try {phone.sendSMS();} catch (Exception e) {throw new RuntimeException(e);}},"AA").start();Thread.sleep(100);new Thread(()->{try {phone2.sendEmail();} catch (Exception e) {throw new RuntimeException(e);}},"BB").start();}
}
  • 运行结果:
-----------sendEmail
----------sendSMS
  • 结论
    synchronized实现同步的基础:Java中的每一个对象都可以作为锁。具体表现为以下3种形式。
    • 对于普通同步方法,锁是当前实例对象。
    • 对于静态同步方法,锁是当前类的class对象。
    • 对于同步方法块,锁是synchonized括号里配置的对象

2 公平锁与非公平锁

在多线程编程中,锁(Lock)是一种用于控制多个线程对共享资源访问的同步机制。锁可以分为公平锁(Fair Lock)和非公平锁(Non-Fair Lock)两种类型,它们在实现方式和行为上有显著的区别。

2.1 公平锁(Fair Lock)

2.1.1 概念

公平锁是指多个线程按照请求锁的顺序依次获得锁,即先请求的线程先获得锁。公平锁遵循FIFO(先进先出)原则,确保每个线程都有公平的机会获得锁。

2.1.2 特点

  • 公平性:确保每个线程按照请求锁的顺序获得锁,避免线程饥饿(Starvation)现象。
  • 性能较低:由于需要维护请求队列并按顺序分配锁,公平锁的性能通常比非公平锁低。
  • 适用场景:适用于需要严格保证线程执行顺序的场景,如某些实时系统或对公平性要求较高的应用。

2.1.3 Java中的实现

在Java中,ReentrantLock类可以通过构造函数参数指定是否使用公平锁。

ReentrantLock fairLock = new ReentrantLock(true); // 创建公平锁

2.2 非公平锁(Non-Fair Lock)

2.2.1 概念

非公平锁是指多个线程请求锁时,不保证按照请求的顺序获得锁。非公平锁允许线程插队,即一个新请求的线程可以抢占正在等待的线程的锁。

2.2.2 特点

  • 非公平性:不保证线程按照请求的顺序获得锁,允许线程插队。
  • 性能较高:由于不需要维护请求队列和按顺序分配锁,非公平锁的性能通常比公平锁高。
  • 适用场景:适用于对性能要求较高且对线程执行顺序要求不严格的场景,如大多数并发应用。

2.2.3 Java中的实现

在Java中,ReentrantLock类默认使用非公平锁。

ReentrantLock nonFairLock = new ReentrantLock(); // 创建非公平锁

ReentrantLock nonFairLock = new ReentrantLock(false); // 创建非公平锁

2.3 对比与选择

2.3.1 公平锁 vs 非公平锁

  • 公平性:公平锁保证线程按照请求顺序获得锁,非公平锁不保证。
  • 性能:公平锁性能较低,非公平锁性能较高。
  • 适用场景:公平锁适用于需要严格保证线程执行顺序的场景,非公平锁适用于对性能要求较高且对线程执行顺序要求不严格的场景。

2.3.2 选择原则

  • 性能优先:如果对性能要求较高,且对线程执行顺序要求不严格,选择非公平锁。
  • 公平性优先:如果需要严格保证线程执行顺序,避免线程饥饿现象,选择公平锁。

3 可重入锁

可重入锁是一种允许同一个线程多次获取同一个锁的同步机制。可重入锁的主要特点是,当一个线程已经持有某个锁时,它可以再次获取该锁而不会被阻塞。这种特性避免了死锁的发生,因为线程可以多次进入同一个锁保护的代码块

3.1 可重入锁的概念

可重入锁允许同一个线程多次获取同一个锁,而不会导致死锁。每次获取锁时,锁的计数器会增加,每次释放锁时,计数器会减少。只有当计数器归零时,锁才会被完全释放。

3.2 使用 synchronized 实现可重入锁

  • synchronized实现同步代码块
public class SyncLockDemo {public static void main(String[] args) {// synchronizedObject o = new Object();new Thread(()->{synchronized (o){System.out.println(Thread.currentThread().getName() + " 外层");synchronized (o){System.out.println(Thread.currentThread().getName() + " 中层");synchronized (o){System.out.println(Thread.currentThread().getName() + " 内层");}}}},"t1").start();}
}
  • 运行结果:
t1 外层
t1 中层
t1 内层
  • synchronized实现同步方法
public class SyncLockDemo {public synchronized void add(){add();}public static void main(String[] args) {new SyncLockDemo().add();}
}
  • 运行结果:
    在这里插入图片描述

3.3 使用Lock实现可重入锁

  • Lock实现可重入锁
public class SyncLockDemo {public static void main(String[] args) {//Lock演示可重入锁Lock lock = new ReentrantLock();//创建线程new Thread(()->{try {// 上锁lock.lock();System.out.println(Thread.currentThread().getName() + " 外层");try {// 上锁lock.lock();System.out.println(Thread.currentThread().getName() + " 内层");} finally {// 解锁lock.unlock();}} finally {// 解锁lock.unlock();}},"t1").start();}
}
  • 运行结果:
t1 外层
t1 内层
  • 注意:上锁lock.lock()和解锁lock.unlock()一定要成对出现,千万不要忘记解锁。

4 死锁

4.1 死锁的概念

两个或者两个以上进程在执行过程中,因为争夺资源而造成一种互相等待的现象,如果没有外力干涉,他们无法再执行下去
在这里插入图片描述

4.2 产生死锁的原因

  • 系统资源不足
  • 进程运行推进顺序不合适
  • 资源分配不当

4.3 验证是否死锁

在多线程编程中,死锁是一种常见的问题,它发生在两个或多个线程相互等待对方释放资源的情况下。为了验证是否发生了死锁,可以使用Java提供的工具 jpsjstack

4.3.1 jps 工具

jps 是Java Virtual Machine Process Status Tool的缩写,类似于Linux中的 ps -ef 命令,用于列出当前正在运行的Java进程。

  • 使用方法

在命令行中输入以下命令:

jps -l
  • 输出示例
47872
29048 com.yunyang.javacoder.sync.DeadLock
  • 4787229048 是Java进程的PID(进程ID)。
  • DeadLock 是Java进程的名称。

4.3.2 jstack 工具

jstack 是JVM自带的堆栈跟踪工具,用于生成Java虚拟机当前时刻的线程快照(thread dump)。通过分析线程快照,可以检查是否存在死锁。

  • 使用方法

在命令行中输入以下命令:

jstack <PID>

其中 <PID> 是Java进程的PID,可以通过 jps 命令获取。

  • 输出示例
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.152-b16 mixed mode):"DestroyJavaVM" #24 prio=5 os_prio=0 tid=0x0000000002ee3800 nid=0x71c0 waitingon condition [0x0000000000000000]java.lang.Thread.State: RUNNABLE"B" #23 prio=5 os_prio=0 tid=0x0000000021811800 nid=0x94f4 waiting for monitorentry [0x00000000220df000]java.lang.Thread.State: BLOCKED (on object monitor)at com.yunyang.javacoder.sync.DeadLock.lambda$main$1(DeadLock.java:41)- waiting to lock <0x000000076baa67e0> (a java.lang.Object)- locked <0x000000076baa67f0> (a java.lang.Object)at com.yunyang.javacoder.sync.DeadLock$$Lambda$2/1419810764.run(Unknow
n Source)at java.lang.Thread.run(Thread.java:748)"A" #22 prio=5 os_prio=0 tid=0x0000000021836000 nid=0xd3d4 waiting for monitorentry [0x0000000021fdf000]java.lang.Thread.State: BLOCKED (on object monitor)at com.yunyang.javacoder.sync.DeadLock.lambda$main$0(DeadLock.java:27)- waiting to lock <0x000000076baa67f0> (a java.lang.Object)- locked <0x000000076baa67e0> (a java.lang.Object)at com.yunyang.javacoder.sync.DeadLock$$Lambda$1/913190639.run(UnknownSource)at java.lang.Thread.run(Thread.java:748)...(此处省略若干行)===================================================
"B":at com.yunyang.javacoder.sync.DeadLock.lambda$main$1(DeadLock.java:41)- waiting to lock <0x000000076baa67e0> (a java.lang.Object)- locked <0x000000076baa67f0> (a java.lang.Object)at com.yunyang.javacoder.sync.DeadLock$$Lambda$2/1419810764.run(Unknow
n Source)at java.lang.Thread.run(Thread.java:748)
"A":at com.yunyang.javacoder.sync.DeadLock.lambda$main$0(DeadLock.java:27)- waiting to lock <0x000000076baa67f0> (a java.lang.Object)- locked <0x000000076baa67e0> (a java.lang.Object)at com.yunyang.javacoder.sync.DeadLock$$Lambda$1/913190639.run(UnknownSource)at java.lang.Thread.run(Thread.java:748)Found 1 deadlock.

4.3.3 分析线程快照

  • BLOCKED 状态:表示线程正在等待获取某个对象的锁。
  • waiting to lock:表示线程正在等待获取某个对象的锁。
  • locked:表示线程已经持有某个对象的锁。

如果发现两个或多个线程相互等待对方持有的锁,则可能发生了死锁。例如,在上面的输出示例中,A 等待 B 持有的锁,而 B 等待 A 持有的锁,这表明可能发生了死锁。

4.3.4 示例:死锁代码

以下是一个简单的死锁示例代码:

public class DeadLock {static Object a = new Object();static Object b = new Object();public static void main(String[] args) {new Thread(()->{synchronized (a) {System.out.println(Thread.currentThread().getName() + " 持有锁a,试图获取锁b");try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (b){System.out.println(Thread.currentThread().getName() + " 获取锁b");}}},"A").start();new Thread(()->{synchronized (b) {System.out.println(Thread.currentThread().getName() + " 持有锁b,试图获取锁a");try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (a){System.out.println(Thread.currentThread().getName() + " 获取锁a");}}},"B").start();}}
  • 运行结果:
A 持有锁a,试图获取锁b
B 持有锁b,试图获取锁a

4.3.5 验证死锁

  1. 运行程序:运行上述死锁示例代码。
  2. 获取PID:使用 jps 命令获取Java进程的PID。
  3. 生成线程快照:使用 jstack <PID> 命令生成线程快照。
  4. 分析线程快照:检查线程快照中的 BLOCKED 状态和 waiting to lock 信息,确认是否存在死锁。

4.3.6 总结

通过使用 jpsjstack 工具,可以方便地验证Java程序中是否存在死锁。jps 用于列出Java进程,jstack 用于生成线程快照,通过分析线程快照中的 BLOCKED 状态和 waiting to lock 信息,可以判断是否发生了死锁。

5 附录 思维导图

在这里插入图片描述

6 参考链接

【【尚硅谷】大厂必备技术之JUC并发编程】

相关文章:

  • 滚雪球学Oracle[7.1讲]:Oracle云数据库
  • Android Studio | 无法识别Icons.Default.Spa中的Spa
  • 实用工具推荐---- PDF 转换
  • AtCoder ABC371 A-D题解
  • 微信小程序处理交易投诉管理,支持多小程序
  • 需求梳理时,如何平衡各方的利益冲突?
  • 位运算(3)_判定字符是否唯一_面试题
  • BSS是什么
  • 谨防火灾!电瓶车检测算法助力城市/小区/园区多场景安全管理精细化、智能化
  • 【Vue】以RuoYi框架前端为例,ElementUI封装图片上传组件——将图片信息转成base64后提交到后端保存
  • 盛事启幕 | 第三届OpenHarmony技术大会重磅官宣,邀您共绘智联未来
  • 深度学习模型可视化工具 Netron 使用教程
  • AndroidStudio依赖报错
  • Qt QIntValidator详解
  • 【以图搜图代码实现2】--faiss工具实现犬类以图搜图
  • IE9 : DOM Exception: INVALID_CHARACTER_ERR (5)
  • [iOS]Core Data浅析一 -- 启用Core Data
  • 【翻译】babel对TC39装饰器草案的实现
  • AWS实战 - 利用IAM对S3做访问控制
  • ESLint简单操作
  • JavaScript DOM 10 - 滚动
  • Linux编程学习笔记 | Linux IO学习[1] - 文件IO
  • Markdown 语法简单说明
  • php ci框架整合银盛支付
  • puppeteer stop redirect 的正确姿势及 net::ERR_FAILED 的解决
  • rc-form之最单纯情况
  • uni-app项目数字滚动
  • Vultr 教程目录
  • WePY 在小程序性能调优上做出的探究
  • 阿里云应用高可用服务公测发布
  • 对话 CTO〡听神策数据 CTO 曹犟描绘数据分析行业的无限可能
  • 高性能JavaScript阅读简记(三)
  • 入手阿里云新服务器的部署NODE
  • ​sqlite3 --- SQLite 数据库 DB-API 2.0 接口模块​
  • #、%和$符号在OGNL表达式中经常出现
  • #define 用法
  • #pragma multi_compile #pragma shader_feature
  • ()、[]、{}、(())、[[]]等各种括号的使用
  • (3)选择元素——(14)接触DOM元素(Accessing DOM elements)
  • (4)通过调用hadoop的java api实现本地文件上传到hadoop文件系统上
  • (十八)Flink CEP 详解
  • (一)为什么要选择C++
  • (一)硬件制作--从零开始自制linux掌上电脑(F1C200S) <嵌入式项目>
  • **CI中自动类加载的用法总结
  • ..thread“main“ com.fasterxml.jackson.databind.JsonMappingException: Jackson version is too old 2.3.1
  • .NET C# 使用 iText 生成PDF
  • .net core 6 集成 elasticsearch 并 使用分词器
  • .NET CORE程序发布IIS后报错误 500.19
  • .NET Core实战项目之CMS 第十二章 开发篇-Dapper封装CURD及仓储代码生成器实现
  • .NET Core实战项目之CMS 第一章 入门篇-开篇及总体规划
  • .NET 同步与异步 之 原子操作和自旋锁(Interlocked、SpinLock)(九)
  • .php文件都打不开,打不开php文件怎么办
  • @antv/g6 业务场景:流程图
  • @PreAuthorize与@Secured注解的区别是什么?
  • [2019.3.5]BZOJ1934 [Shoi2007]Vote 善意的投票