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

embedded/2024/12/23 5:23:16/

1 锁的八个问题演示

  1. 标准访问,先打印短信还是邮件
java">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();}
}
  • 运行结果:
java">----------sendSMS
-----------sendEmail
  1. 停4秒在短信方法内,先打印短信还是邮件
java">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();}
}
  • 运行结果:
java">----------sendSMS
-----------sendEmail
  1. 新增普通的hello方法,是先打短信还是hello
java">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();}
}
  • 运行结果:
java">-------------getHello
----------sendSMS
  1. 现在有两部手机,先打印短信还是邮件
java">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();}
}
  • 运行结果:
java">-----------sendEmail
----------sendSMS

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

java">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();}
}
  • 运行结果:
java">----------sendSMS
-----------sendEmail
  1. 两个静态同步方法,2部手机,先打印短信还是邮件
java">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();}
}
  • 运行结果:
java">----------sendSMS
-----------sendEmail
  1. 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件
java">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();}
}
  • 运行结果
java">-----------sendEmail
----------sendSMS
    1. 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件
java">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();}
}
  • 运行结果:
java">-----------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类可以通过构造函数参数指定是否使用公平锁。

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

2.2 非公平锁(Non-Fair Lock)

2.2.1 概念

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

2.2.2 特点

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

2.2.3 Java中的实现

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

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

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

2.3 对比与选择

2.3.1 公平锁 vs 非公平锁

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

2.3.2 选择原则

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

3 可重入锁

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

3.1 可重入锁的概念

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

3.2 使用 synchronized 实现可重入锁

  • synchronized实现同步代码块
java">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();}
}
  • 运行结果:
java">t1 外层
t1 中层
t1 内层
  • synchronized实现同步方法
java">public class SyncLockDemo {public synchronized void add(){add();}public static void main(String[] args) {new SyncLockDemo().add();}
}
  • 运行结果:
    在这里插入图片描述

3.3 使用Lock实现可重入锁

  • Lock实现可重入锁
java">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();}
}
  • 运行结果:
java">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 示例:死锁代码

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

java">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();}}
  • 运行结果:
java">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并发编程】


http://www.ppmy.cn/embedded/120170.html

相关文章

Java入门3——操作符+String

在入门2中忘了提 String 的事情了&#xff0c;所以这篇就放在开头啦&#xff0c;很有用 话不多说&#xff0c;开始正题~ 一、String 引用数据类型之——String 1.字符串的拼接 在Java中&#xff0c;如果要把两句话合并到一句话的时候&#xff0c;其实是很简单的&#xff0c;…

【以图搜图代码实现2】--faiss工具实现犬类以图搜图

第一篇&#xff1a;【以图搜图代码实现】–犬类以图搜图示例 使用保存成h5文件&#xff0c;使用向量积来度量相似性&#xff0c;实现了以图搜图&#xff0c;说明了可以优化的点。 第二篇&#xff1a;【使用resnet18训练自己的数据集】 准对模型问题进行了优化&#xff0c;取得了…

LSTM模型改进实现多步预测未来30天销售额

关于深度实战社区 我们是一个深度学习领域的独立工作室。团队成员有&#xff1a;中科大硕士、纽约大学硕士、浙江大学硕士、华东理工博士等&#xff0c;曾在腾讯、百度、德勤等担任算法工程师/产品经理。全网20多万粉丝&#xff0c;拥有2篇国家级人工智能发明专利。 社区特色…

ant design vue做表单验证及form表单外验证、父子嵌套多个表单校验

1、form表单验证(若有时遇到输入框有值但是还是触发验证规则了&#xff0c;请检查form表单绑定正确吗、校验规则正确吗、表格数据字段名正确吗) <a-form:model"formState":label-col"{ span: 8 }":wrapper-col"{ span: 16 }":rules"rul…

spring模块都有哪些

Spring 框架是一个庞大而灵活的生态系统&#xff0c;它包含了多个模块&#xff0c;每个模块都提供了特定的功能和服务。以下是一些主要的 Spring 模块&#xff1a; Spring Core&#xff1a; 核心容器&#xff0c;提供了 IoC&#xff08;控制反转&#xff09;和 DI&#xff08;…

强化学习-python案例

强化学习是一种机器学习方法&#xff0c;旨在通过与环境的交互来学习最优策略。它的核心概念是智能体&#xff08;agent&#xff09;在环境中采取动作&#xff0c;从而获得奖励或惩罚。智能体的目标是最大化长期奖励&#xff0c;通过试错的方式不断改进其决策策略。 在强化学习…

HAL+M4学习记录_2

一、Boot配置 内存地址是固定的&#xff0c;代码从0x0000 0000开始&#xff0c;而数据从0x2000 0000开始&#xff0c;F4支持三种不同的boot模式 复位芯片时&#xff0c;在SYSCLK的第4个上升沿BOOT引脚值被锁存&#xff0c;STM32F407通过此时BOOT[1:0]引脚值选择Boot模式 BOOT1…

Flet介绍:平替PyQt的好用跨平台Python UI框架

随着Python在各个领域的广泛应用&#xff0c;特别是在数据科学和Web开发领域&#xff0c;对于一个简单易用且功能强大的用户界面&#xff08;UI&#xff09;开发工具的需求日益增长。传统的Python GUI库如Tkinter、PyQt虽然功能强大&#xff0c;但在易用性和现代感方面略显不足…