javaSE学习笔记21-线程(thread)-锁(synchronized 与Lock)

死锁

多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程 都在等待对方释放资源,都停止执行的情形,某一个同步块同时拥有“两个以上对象的锁”时,就可能 会发生“死锁"的问题;

死锁是指多个线程在执行过程中,因为争夺资源而造成的一种互相等待的现象,导致这些线程都无法继续执行下去。

练习代码

package com.lock;/*
死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程
都在等待对方释放资源,都停止执行的情形,某一个同步块同时拥有“两个以上对象的锁”时,就可能
会发生“死锁"的问题;*///死锁:多个线程互相抱着对方需要的资源,然后形成僵持
public class DeadLock {public static void main(String[] args) {Makeup g1 = new Makeup(0,"灰姑凉");Makeup g2 = new Makeup(0,"白雪公主");g1.start();g2.start();}
}//口红(Lipstick)
class Lipstick{}//镜子(Mirror)
class Mirror{}//化妆(Makeup)
class Makeup extends Thread{//需要的资源只有一份,用static来抱着只有一份static Lipstick lipstick = new Lipstick();static Mirror mirror = new Mirror();int choice;//选择String girlName;//使用化妆品的人Makeup(int choice,String girlName){this.choice = choice;this.girlName = girlName;}@Overridepublic void run() {//化妆try {makeup();} catch (InterruptedException e) {e.printStackTrace();}}//化妆,互相持有对方的锁,就是需要拿到对方的资源private void makeup() throws InterruptedException {if (choice == 0) {synchronized (lipstick) {//获得口红的锁System.out.println(this.girlName + "获得口红的锁");Thread.sleep(1000);synchronized (mirror){//1s钟后想获得镜子 的锁System.out.println(this.girlName + "获得镜子的锁");}}}else {synchronized (mirror) {//获得镜子的锁System.out.println(this.girlName + "获得镜子的锁");Thread.sleep(2000);synchronized (lipstick){//2s钟后想获得口红 的锁System.out.println(this.girlName + "获得口红的锁");}}}}
}

代码结构

  1. DeadLock类:这是主类,包含main方法,用于启动两个线程。

  2. Lipstick类和Mirror类:这两个类分别代表口红和镜子,是共享资源。

  3. Makeup类:继承自Thread类,表示一个化妆的线程。每个线程代表一个女孩,她们需要使用口红和镜子来化妆。

代码逻辑

  1. 共享资源

    • LipstickMirror是两个共享资源,分别代表口红和镜子。

    • 这两个资源被声明为static,确保它们在所有Makeup实例之间共享。

  2. Makeup类

    • choice:表示女孩的选择,决定她们先获取哪个资源。

    • girlName:表示女孩的名字。

    • run()方法:线程启动后执行的方法,调用makeup()方法。

    • makeup()方法:模拟化妆过程,尝试获取口红和镜子的锁。

  3. 死锁的产生

    • 如果choice为0,线程会先获取口红的锁,然后尝试获取镜子的锁。

    • 如果choice为1,线程会先获取镜子的锁,然后尝试获取口红的锁。

    • 由于两个线程的执行顺序不同,可能会导致以下情况:

      • 线程1(灰姑凉)持有口红的锁,等待镜子的锁。

      • 线程2(白雪公主)持有镜子的锁,等待口红的锁。

    • 这样,两个线程互相等待对方释放资源,导致死锁。

代码执行流程

  1. 启动线程

    • g1g2两个线程分别启动,代表灰姑凉和白雪公主。

    • g1choice为0,g2choice为1。

  2. 线程执行

    • g1先获取口红的锁,然后尝试获取镜子的锁。

    • g2先获取镜子的锁,然后尝试获取口红的锁。

  3. 死锁发生

    • g1持有口红的锁,等待g2释放镜子的锁。

    • g2持有镜子的锁,等待g1释放口红的锁。

    • 两个线程都无法继续执行,形成死锁。

如何避免死锁

  1. 锁的顺序:确保所有线程以相同的顺序获取锁。例如,所有线程都先获取口红的锁,再获取镜子的锁。

  2. 超时机制:在获取锁时设置超时时间,如果超时则释放已持有的锁并重试。

  3. 死锁检测:使用工具或算法检测死锁,并采取相应措施解除死锁。

优化后代码

package com.lock;/*
死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程
都在等待对方释放资源,都停止执行的情形,某一个同步块同时拥有“两个以上对象的锁”时,就可能
会发生“死锁"的问题;死锁避免方法
产生死锁的四个必要条件
1.互斥条件:一个资源每次只能被一个进程使用;
2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放;
3.不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺;
4,循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。只要想办法破其中的任意一个或多个条件就可以避免死锁发生*///死锁:多个线程互相抱着对方需要的资源,然后形成僵持
public class DeadLock {public static void main(String[] args) {Makeup g1 = new Makeup(0,"灰姑凉");Makeup g2 = new Makeup(0,"白雪公主");g1.start();g2.start();}
}//口红(Lipstick)
class Lipstick{}//镜子(Mirror)
class Mirror{}//化妆(Makeup)
class Makeup extends Thread{//需要的资源只有一份,用static来抱着只有一份static Lipstick lipstick = new Lipstick();static Mirror mirror = new Mirror();int choice;//选择String girlName;//使用化妆品的人Makeup(int choice,String girlName){this.choice = choice;this.girlName = girlName;}@Overridepublic void run() {//化妆try {makeup();} catch (InterruptedException e) {e.printStackTrace();}}//化妆,互相持有对方的锁,就是需要拿到对方的资源private void makeup() throws InterruptedException {if (choice == 0) {synchronized (lipstick) {//获得口红的锁System.out.println(this.girlName + "获得口红的锁");Thread.sleep(1000);}synchronized (mirror){//1s钟后想获得镜子 的锁System.out.println(this.girlName + "获得镜子的锁");}}else {synchronized (mirror) {//获得镜子的锁System.out.println(this.girlName + "获得镜子的锁");Thread.sleep(2000);}synchronized (lipstick){//2s钟后想获得口红 的锁System.out.println(this.girlName + "获得口红的锁");}}}
}

修改后的代码分析

关键修改点
  1. 锁的嵌套被移除

    • 在原始代码中,synchronized块是嵌套的,即一个线程在持有第一个锁的情况下尝试获取第二个锁。

    • 在修改后的代码中,synchronized块是分开的,线程在释放第一个锁之后才会尝试获取第二个锁。

  2. 锁的获取顺序

    • 修改后的代码中,线程不会同时持有两个锁,而是先释放一个锁,再尝试获取另一个锁。

    • 这样就不会出现两个线程互相等待对方释放锁的情况。


修改后的代码执行逻辑

线程1(灰姑凉)的执行流程:
  1. 获取lipstick的锁。

  2. 打印“灰姑凉获得口红的锁”。

  3. 释放lipstick的锁。

  4. 获取mirror的锁。

  5. 打印“灰姑凉获得镜子的锁”。

  6. 释放mirror的锁。

线程2(白雪公主)的执行流程:
  1. 获取mirror的锁。

  2. 打印“白雪公主获得镜子的锁”。

  3. 释放mirror的锁。

  4. 获取lipstick的锁。

  5. 打印“白雪公主获得口红的锁”。

  6. 释放lipstick的锁。


为什么避免了死锁?

  1. 锁的释放

    • 每个线程在获取一个锁后,会先释放它,再尝试获取另一个锁。

    • 这样就不会出现一个线程持有lipstick的锁并等待mirror的锁,而另一个线程持有mirror的锁并等待lipstick的锁的情况。

  2. 没有互相等待

    • 线程1和线程2不会同时持有对方需要的锁,因此不会形成互相等待的僵局。

Lock锁 

1、JDK5.0开始,Java提供了更强大的线程同步机制--通过显式定义同步锁对象来实现同步。
同步锁使用Lock对象充当
2、java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。
锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
ReentrantLoc类(可重入锁)实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是
ReentrantLock,可以显式加锁、释放锁

以下代码演示了如何使用ReentrantLock来实现线程同步,确保多个线程安全地访问共享资源。ReentrantLock是Java中提供的一种显式锁机制,相比于synchronized关键字,它提供了更灵活的锁控制方式。

package com.lock;import java.util.concurrent.locks.ReentrantLock;/*
Lock(锁)
1、JDK5.0开始,Java提供了更强大的线程同步机制--通过显式定义同步锁对象来实现同步。
同步锁使用Lock对象充当
2、java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。
锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
ReentrantLoc类(可重入锁)实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是
ReentrantLock,可以显式加锁、释放锁*/
//测试lock锁
public class TestLock {public static void main(String[] args) {TestLock2 testLock2 = new TestLock2();new Thread(testLock2).start();new Thread(testLock2).start();new Thread(testLock2).start();}
}class TestLock2 implements Runnable{int ticketNums = 10;//定义lock锁private final ReentrantLock lock = new ReentrantLock();@Overridepublic void run() {while (true){try {lock.lock();//加锁if (ticketNums > 0) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(ticketNums--);}else {break;}}finally {//解锁lock.unlock();}}}
}

代码结构

  1. TestLock类

    • 这是主类,包含main方法,用于启动多个线程。

    • 创建了一个TestLock2对象,并启动三个线程来执行该对象的run方法。

  2. TestLock2类

    • 实现了Runnable接口,表示一个任务,可以被多个线程执行。

    • 包含一个共享资源ticketNums(票数),多个线程会竞争访问和修改这个资源。

    • 使用ReentrantLock来确保对ticketNums的访问是线程安全的。


代码逻辑

1. 共享资源
  • ticketNums:表示剩余的票数,初始值为10。

  • 多个线程会同时访问和修改ticketNums,因此需要确保线程安全。

2. ReentrantLock
  • ReentrantLock是一个可重入锁,允许线程多次获取同一把锁。

  • 通过lock()方法加锁,通过unlock()方法解锁。

  • 使用try-finally块确保锁一定会被释放,避免死锁。

3. 线程执行逻辑
  • 每个线程执行TestLock2run方法。

  • while (true)循环中,线程不断尝试获取锁并访问共享资源ticketNums

  • 如果ticketNums > 0,线程会休眠1秒(模拟耗时操作),然后打印并减少ticketNums的值。

  • 如果ticketNums <= 0,线程退出循环,任务结束。


代码执行流程

  1. 启动线程

    • main方法中,创建了一个TestLock2对象,并启动三个线程。

    • 这三个线程会并发执行TestLock2run方法。

  2. 线程竞争锁

    • 每个线程在执行run方法时,会先调用lock.lock()尝试获取锁。

    • 只有一个线程能成功获取锁,其他线程会被阻塞,直到锁被释放。

  3. 访问共享资源

    • 获取锁的线程会检查ticketNums的值。

    • 如果ticketNums > 0,线程会休眠1秒,然后打印ticketNums的值并将其减1。

    • 如果ticketNums <= 0,线程会退出循环。

  4. 释放锁

    • 线程在完成对共享资源的操作后,会调用lock.unlock()释放锁。

    • 其他被阻塞的线程会竞争获取锁,继续执行。

  5. 任务结束

    • ticketNums的值减少到0时,所有线程都会退出循环,任务结束。

关键点

  1. ReentrantLock的作用

    • 确保多个线程对共享资源ticketNums的访问是互斥的,避免数据竞争。

    • 相比于synchronizedReentrantLock提供了更灵活的锁控制,例如可中断锁、超时锁等。

  2. try-finally的作用

    • try块中加锁,在finally块中解锁,确保锁一定会被释放,避免死锁。

  3. 线程安全

    • 通过ReentrantLock实现了对共享资源的线程安全访问。


改进建议

  1. 锁的粒度

    • 当前代码中,锁的粒度较大(整个while循环都在锁内),可能会影响并发性能。可以根据实际需求调整锁的粒度。

  2. 公平锁

    • ReentrantLock默认是非公平锁,可以通过构造函数new ReentrantLock(true)创建公平锁,确保线程按顺序获取锁。

  3. 锁的可中断性

    • ReentrantLock支持可中断的锁获取(lockInterruptibly()),可以在线程等待锁时响应中断。

改进后代码:

package com.lock;import java.util.concurrent.locks.ReentrantLock;/*
改进后的TestLock示例:
1. 缩小锁的粒度,只对共享资源的访问和修改加锁。
2. 使用公平锁,确保线程按顺序获取锁。
3. 优化代码结构,提高可读性和可维护性。
*/public class TestLock {public static void main(String[] args) {TestLock2 testLock2 = new TestLock2();// 启动多个线程new Thread(testLock2, "线程1").start();new Thread(testLock2, "线程2").start();new Thread(testLock2, "线程3").start();}
}class TestLock2 implements Runnable {private int ticketNums = 10; // 共享资源,表示剩余的票数// 定义公平锁private final ReentrantLock lock = new ReentrantLock(true);@Overridepublic void run() {while (true) {// 尝试获取锁lock.lock();try {if (ticketNums > 0) {// 模拟耗时操作try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}// 打印当前线程名和剩余的票数System.out.println(Thread.currentThread().getName() + " 售出票号:" + ticketNums--);} else {// 票已售完,退出循环break;}} finally {// 释放锁lock.unlock();}// 模拟线程切换,增加并发性try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}}}
}

synchronized 与Lock的对比

1、lock是显式锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,出了作用域自动释放
2、Lock只有代码块锁,synchronized有代码块锁和方法锁;
3、使用Lock锁,JVM将花费较少的时间来调度线程(性能更好,并且具有更好的扩展性(提供更多的子类))
4、优先使用顺序:
    Lock > 同步代码块(已经进入了方法体,分配了相应资源)> 同步方法(在方法体之外)

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

uni-app发起网络请求的三种方式

uni.request(OBJECT) 发起网络请求 具体参数可查看官方文档uni-app data:请求的参数; header&#xff1a;设置请求的 header&#xff0c;header 中不能设置 Referer&#xff1b; method&#xff1a;请求方法&#xff1b; timeout&#xff1a;超时时间&#xff0c;单位 ms&a…

SpringBoot速成概括

视频&#xff1a;黑马程序员SpringBoot3Vue3全套视频教程&#xff0c;springbootvue企业级全栈开发从基础、实战到面试一套通关_哔哩哔哩_bilibili 图示&#xff1a;

GoFound 与 MySQL 集成优化方案

GoFound 与 MySQL 集成优化方案 1. 明确需求 文章信息存储在 MySQL 数据库中。使用 GoFound 实现全文搜索功能。搜索时&#xff0c;先从 GoFound 中获取匹配的文章 ID&#xff0c;然后从 MySQL 中查询完整的文章信息。 2. 优化思路 数据同步&#xff1a;将 MySQL 中的文章数…

基于开源Odoo模块、SKF Phoenix API与IMAX-8数采网关的资产密集型企业设备智慧运维实施方案

一、方案背景与需求分析 1.1 华东地区产业特点与设备管理痛点 华东地区作为中国制造业核心区域&#xff0c;聚集了钢铁、化工、汽车、装备制造等资产密集型企业。以某长三角钢铁集团为例&#xff0c;其设备管理面临以下挑战&#xff1a; 非计划停机损失严重&#xff1a;2023…

《魔女的夜宴》无广版手游安卓苹果免费下载直装版

自娶 https://pan.xunlei.com/s/VOJS77k8NDrVawqcOerQln2lA1?pwdn6k8 《魔女的夜宴》&#xff1a;一场魔法与恋爱的奇幻之旅 在美少女游戏的世界中&#xff0c;柚子社&#xff08;Yuzusoft&#xff09;的作品总是以其精美的画面、动人的剧情和丰富的角色塑造而备受玩家喜爱…

深化与细化:提示工程(Prompt Engineering)的进阶策略与实践指南2

深化与细化&#xff1a;提示工程&#xff08;Prompt Engineering&#xff09;的进阶策略与实践指南 一、结构化提示的黄金框架 1. CRISPE框架&#xff08;角色-约束-意图-风格-示例&#xff09; 适用于复杂技术场景&#xff0c;确保输出精准可控&#xff1a; [角色] 你是一名…

N-bit ADC过采样和L阶噪声整形后的SQNR表达式

对于采用L阶理想高通滤波器进行噪声整形的DSM&#xff0c;OSR每增加一倍&#xff0c;SQNR提高3(2L1)dB,文中给出了DSM量化精度与量化器位数N、环路滤波器阶数L和过采样率OSR的关系&#xff0c;在进行DSM系统设计时通过设置目标SQNR即可筛选出满足设计需要的参数组合。

Linux环境开发工具

Linux软件包管理器yum Linux下安装软件方式&#xff1a; 源代码安装rpm安装——Linux安装包yum安装——解决安装源、安装版本、安装依赖的问题 yum对应于Windows系统下的应用商店 使用Linux系统的人&#xff1a;大部分是职业程序员 客户端怎么知道去哪里下载软件&#xff1…

自动化办公|通过xlwings进行excel格式设置

1. 介绍 xlwings 是一个强大的 Python 库&#xff0c;可以用来操作 Excel&#xff0c;包括设置单元格格式、调整行高列宽、应用条件格式以及使用内置样式。本文将详细介绍如何使用 xlwings 进行 Excel 格式化操作&#xff0c;并附带代码示例。 2. 基础格式设置&#xff08;字…

EasyRTC:智能硬件适配,实现多端音视频互动新突破

一、智能硬件全面支持&#xff0c;轻松跨越平台障碍 EasyRTC 采用前沿的智能硬件适配技术&#xff0c;无缝对接 Windows、macOS、Linux、Android、iOS 等主流操作系统&#xff0c;并全面拥抱 WebRTC 标准。这一特性确保了“一次开发&#xff0c;多端运行”的便捷性&#xff0c…

【架构思维基础:如何科学定义问题】

架构思维基础&#xff1a;如何科学定义问题 一、问题本质认知 1.1 问题矛盾 根据毛泽东《矛盾论》&#xff0c;问题本质是系统内部要素间既对立又统一的关系。例如&#xff1a; 电商系统矛盾演变&#xff1a; 90年代&#xff1a;商品供给不足 vs 消费需求增长00年代&#x…

从零开始构建一个小型字符级语言模型的详细教程(基于Transformer架构)之一数据准备

最近特别火的DeepSeek,是一个大语言模型,那一个模型是如何构建起来的呢?DeepSeek基于Transformer架构,接下来我们也从零开始构建一个基于Transformer架构的小型语言模型,并说明构建的详细步骤及内部组件说明。我们以构建一个字符级语言模型(Char-Level LM)为例,目标是通…

Effective Go-新手学习Go需要了解的知识

不知不觉从事Golang开发已有4+年了,回顾自己的成长经历,有很多感悟和心得。如果有人问我,学习Golang从什么资料开始,我一定给他推荐"Effective Go"。《Effective Go》是 Go 语言官方推荐的编程风格和最佳实践指南,其结构清晰,内容涵盖 Go 的核心设计哲学和常见…

坐井说天阔---DeepSeek-R1

前言 DeepSeek-R1这么火&#xff0c;虽然网上很多介绍和解读&#xff0c;但听人家的总不如自己去看看原论文。于是花了大概一周的时间&#xff0c;下班后有进入了研究生的状态---读论文。 DeepSeek这次的目标是探索在没有任何监督数据的情况下训练具有推理能力的大模型&#…

MySQL(1)基础篇

执行一条 select 语句&#xff0c;期间发生了什么&#xff1f; | 小林coding 目录 1、连接MySQL服务器 2、查询缓存 3、解析SQL语句 4、执行SQL语句 5、MySQL一行记录的存储结构 Server 层负责建立连接、分析和执行 SQL存储引擎层负责数据的存储和提取。支持InnoDB、MyIS…

IntelliJ IDEA 接入 AI 编程助手(Copilot、DeepSeek、GPT-4o Mini)

IntelliJ IDEA 接入 AI 编程助手&#xff08;Copilot、DeepSeek、GPT-4o Mini&#xff09; &#x1f4ca; 引言 近年来&#xff0c;AI 编程助手已成为开发者的高效工具&#xff0c;它们可以加速代码编写、优化代码结构&#xff0c;并提供智能提示。本文介绍如何在 IntelliJ I…

2025.2.20总结

今晚评测试报告&#xff0c;评到一半&#xff0c;由于看板数据没有分析完&#xff0c;最后让我搞完再评. 尽管工作了多年的同事告诉我&#xff0c;活没干完&#xff0c;差距比较大&#xff0c;没资格评报告&#xff0c;但还是本着试试的态度&#xff0c;结果没想到评审如此严苛…

ok113i——交叉编译音视频动态库

提示&#xff1a;buildroot支持ffmpeg和SDL&#xff0c;但博主的ffmpeg是按下面方法编译通过&#xff0c;SDL使用buildroot直接编译也通过&#xff1b; 1. 下载ffmpeg源码 下载链接&#xff1a;https://github.com/FFmpeg/FFmpeg/tags 根据版本需要自行下载压缩包&#xff0c…

什么叫不可变数据结构?

不可变数据结构(Immutable Data Structures)是指一旦创建之后,其内容就不能被修改的数据结构。这意味着任何对不可变数据结构的“修改”操作实际上都会返回一个新的数据结构,而原始数据结构保持不变。 一、不可变数据结构的核心特点 不可变性:一旦创建后,数据结构的内容…

深度学习之图像分类(一)

前言 图像回归主要是对全连接进行理解 而图像分类任务主要是对卷积的过程进行理解 这一部分会介绍一些基础的概念 卷积的过程&#xff08;包括单通道和多通道&#xff09; 理解一个卷积神经网络工作的过程 以及常见的模型的类别和创新点 图像分类是什么 定义 图像分类是指将输…