Java多线程锁

多线程锁

本专栏学习内容又是来自尚硅谷周阳老师的视频

有兴趣的小伙伴可以点击视频地址观看

Synchronized

Synchronized是Java中锁的一种实现方法,我们需要了解他锁在什么地方,锁的类型有哪些

阿里巴巴开发手册规定:

高并发时,同步调用应该去考量锁的性能消耗,能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁

说明:尽可能使加锁的代码块工作量尽可能的小,避免在锁代码块中调用RPC方法

同步方法

操控两个线程、一个资源类

资源类

class Phone{public synchronized void sendEmail(){try {TimeUnit.MILLISECONDS.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("----sendEmail");}public synchronized void sendSMS(){System.out.println("----sendSMS");}public void hello(){System.out.println("hello");}
}

一个资源对象执行两个同步方法

线程A执行sendEmail()时会加锁,锁的对象是new Phone()也就是堆空间中的那个对象,在线程B调用sendSMS()时,锁对象也是new Phone(),所以需要等待线程A执行完毕才能获取锁

public class SyncDemo1 {public static void main(String[] args) throws InterruptedException {Phone phone = new Phone();new Thread(() -> {phone.sendEmail();},"a").start();//保证线程a先运行TimeUnit.MILLISECONDS.sleep(200);new Thread(() -> {phone.sendSMS();},"b").start();}
}//结果
----sendEmail
----sendSMS

一个资源对象执行一个同步方法和一个普通方法

这个就比较简单,因为hello()不需要获取锁,可以直接执行

public static void main(String[] args) throws InterruptedException {Phone phone = new Phone();new Thread(() -> {phone.sendEmail();},"a").start();//保证线程a先运行TimeUnit.MILLISECONDS.sleep(200);new Thread(() -> {phone.hello();},"b").start();
}//结果
hello
----sendEmail

两个资源对象执行两个同步方法

因为这两个方法的锁对象不同,所以互不影响

public static void main(String[] args) throws InterruptedException {Phone phone = new Phone();Phone phone2 = new Phone();new Thread(() -> {phone.sendEmail();},"a").start();//保证线程a先运行TimeUnit.MILLISECONDS.sleep(200);new Thread(() -> {phone2.sendSMS();},"b").start();
}//结果
----sendSMS
----sendEmail

静态同步方法

资源类

class Phone{public static synchronized void sendEmail(){try {TimeUnit.MILLISECONDS.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("----sendEmail");}public static synchronized void sendSMS(){System.out.println("----sendSMS");}public void hello(){System.out.println("hello");}
}

一个资源对象执行两个静态同步方法

对于静态同步方法,锁住的是Phone这个Class对象,也就是存在与方法区中的Phone,所以不管是一个资源对象还是多个资源对象,调用静态同步方法,使用的都是同一个锁

public static void main(String[] args) throws InterruptedException {Phone phone = new Phone();new Thread(() -> {phone.sendEmail();},"a").start();//保证线程a先运行TimeUnit.MILLISECONDS.sleep(200);new Thread(() -> {phone.sendSMS();},"b").start();
}//结果
----sendEmail
----sendSMS

同步代码块

同步代码块的锁,就是括号中填的对象,可以是对象锁,也可以是类锁

synchronized (this) {}

字节码角度分析

使用javap -c xxxx.class可以反编译字节码文件,如果要看详细信息可以使用javap -v xxxx.class

同步代码块

public class SyncDemo2 {Object object = new Object();public void m1() {synchronized (object) {System.out.println("m1 method");}}public static void main(String[] args) throws InterruptedException {}
}

在JVM中是由monitore来控制锁的,但是在同步代码块中,发现有一个获取锁,有两个释放锁

第二个释放锁有点保护机制的意思,如果同步代码块中出现异常,无法正常释放锁,会有异常的释放方式

image-20230725162440971

同步方法和静态同步方法

    public synchronized void m2() {System.out.println("m2 method");}public static synchronized void m3() {System.out.println("m2 method");}

JVM中使用ACC_SYNCHRONIZED来表示当前方法是同步方法,使用ACC_STATIC来表示该方法为静态方法

image-20230725162921402

公平锁、非公平锁

非公平锁

非公平锁是一种线程同步机制,它允许新的线程在获取锁时,不考虑其他等待线程的顺序,有可能插队获取到锁资源。相对于公平锁来说,非公平锁在一定程度上可以提高系统的吞吐量,但可能导致某些线程长时间地等待。

模拟卖票案例

一共50张票,交给a、b、c三个窗口去卖

public class LockDemo1 {public static void main(String[] args) {Ticket ticket = new Ticket();new Thread(() -> {for(int i = 0;i < 60;i++) ticket.buy();},"a").start();new Thread(() -> {for(int i = 0;i < 60;i++) ticket.buy();},"b").start();new Thread(() -> {for(int i = 0;i < 60;i++) ticket.buy();},"c").start();}
}class Ticket {private int sum = 50;//默认使用非公平锁private ReentrantLock lock = new ReentrantLock();public void buy() {try {lock.lock();if (sum > 0) {System.out.println(Thread.currentThread().getName() + "卖出第  " + sum + " 张票,还剩 " + --sum);}} finally {lock.unlock();}}
}

通过观察结果可以发现,可能有一个窗口把50张票卖完,也有可能一个窗口一张票都卖不出

image-20230726140159232

公平锁

公平锁是一种线程同步机制,它按照线程请求锁的顺序来分配锁资源,保证线程获取锁的顺序与其请求锁的顺序一致。公平锁可以避免线程饥饿的情况,但可能降低系统的吞吐量。

模拟买票案例

可以使用new ReentrantLock(true)来创建公平锁,可以从结果看出,运行一段时间后会保证顺序获取锁

image-20230726140425728

如何选择

一般来说,对于线程执行顺序要求不高的,完全可以使用非公平锁,因为线程之间的切换是非常消耗时间的,非公平锁可以提高吞吐量。

可重入锁

简单理解为:可以重复进入的同步锁,当然是有前提条件的

概念

可重入锁是一种线程同步机制,也称为递归锁。它允许同一个线程在拥有锁的情况下多次进入被锁定的代码块,而不会造成死锁。可重入锁在保证线程安全的同时,提供了更大的灵活性和方便性。

代码演示

synchronizedReentrantLock都属于可重入锁

synchronized

如果不是可重入锁,按照同步锁的理论知识,外层获取object锁时,第二层应该就不能获取到该锁,程序应该会卡死在那里,但是我们发现程序正常的执行完毕,由此可见synchronized是可重入锁

public static void main(String[] args) throws InterruptedException {final Object object = new Object();synchronized (object) {System.out.println(Thread.currentThread().getName() + "进入外层");synchronized (object) {System.out.println(Thread.currentThread().getName() + "进入中层");synchronized (object) {System.out.println(Thread.currentThread().getName() + "进入内层");}}}
}//结果
main进入外层
main进入中层
main进入内层

ReentrantLock

ReentrantLock锁对象是ReentrantLock类的实例,同样也是可重入锁

public static void main(String[] args) {ReentrantLock lock = new ReentrantLock();lock.lock();try {System.out.println(Thread.currentThread().getName() + "进入外层");lock.lock();try {System.out.println(Thread.currentThread().getName() + "进入中层");lock.lock();try {System.out.println(Thread.currentThread().getName() + "进入内层");}finally {lock.unlock();}}finally {lock.unlock();}}finally {lock.unlock();}
}//结果
main进入外层
main进入中层
main进入内层

原理

每一个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针

当执行monitorenter时,如果锁对象的计数器为0,那么说明他没有被其他线程所持有,JVM会将锁对象的持有线程设置为当前线程,并且将其计数器+1。

在目标锁的计数器不为0的情况下,如果锁对象的持有线程是当前线程,那么JVM可以将其计数器+1,否则需要等待。

当执行monitorexit时,JVM会将对象的计数器-1,计数器为0代表锁已经被释放。

死锁

死锁是多线程编程中一种常见的情况,指的是两个或多个线程无限期地等待对方释放资源,从而导致程序无法继续执行的状态。

public class SycnDemo04 {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");try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}synchronized (b){System.out.println(Thread.currentThread().getName() + "获取了锁b");}}},"A").start();new Thread(()->{synchronized (b){System.out.println(Thread.currentThread().getName() + "获取了锁b");try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}synchronized (a){System.out.println(Thread.currentThread().getName() + "获取了锁a");}}},"B").start();}
}//结果
A获取了锁a
B获取了锁b

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

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

相关文章

将Spring Session存储到Redis中实现持久化

文章目录 Session持久化1. 添加依赖2. 配置redis连接信息3. 存储和读取session从Redis Session持久化 1. 添加依赖 在项目中添加session依赖和redis依赖&#xff0c;如下所示&#xff1a; <dependency><groupId>org.springframework.boot</groupId><art…

基于VUE3+Layui从头搭建通用后台管理系统(前端篇)五:后台主页功能实现上

一、本章内容 本章实现后台主页框架实现、菜单的动态加载及生产、tab组件与菜单绑定、菜单与路由绑定等,工具栏按钮等界面及对应功能实现。 1. 详细课程地址: 待发布 2. 源码下载地址: 待发布 二、界面预览

vue脚手架文件说明

vue脚手架文件说明 1、文件介绍2、脚手架里面主要文件和作用 1、文件介绍 2、脚手架里面主要文件和作用 node_modules 都是下载的第三方包public/index.html 浏览器运行的网页src/main.js webpack打包的入口src/APP.vue Vue页面入口package.json 依赖包列表文件

E2E工程问题:小周期转大周期Gateway

摘要&#xff1a; 本文讨论一个具体的工程问题&#xff0c;E2E报文对应的信号&#xff0c;由小周期转大周期导致的E2E校验失败问题。 工程中&#xff0c;网关节点很重要的一个功能就是路由。当然&#xff0c;E2E&#xff08;End to End&#xff09;报文也可路由&#xff0c;但…

5分钟开发一个AI论文抓取和ChatGPT提炼应用

5分钟开发一个AI论文抓取和ChatGPT提炼应用 第一步 点击“即刻开始” -选择模板 python -修改标题 “AIPaper”&#xff0c;项目标识“AIPaper”&#xff0c;点击“创建项目” 第二步 在编程区域右侧AI区域&#xff0c;输入框输入以下内容&#xff1a; 请根据下面的内容&…

Java后端程序员不得不知道的 API 接口常识

说实话&#xff0c;我非常希望自己能早点看到本篇文章&#xff0c;大学那个时候懵懵懂懂&#xff0c;跟着网上的免费教程做了一个购物商城就屁颠屁颠往简历上写。 至今我仍清晰地记得&#xff0c;那个电商教程是怎么定义接口的&#xff1a; 管它是增加、修改、删除、带参查询&…

ELK 使用kibana查询和分析nginx日志

背景&#xff1a;使用kibana查询和分析nginx请求日志&#xff0c;方便开发人员查询系统日志和分析系统问题。 setp 1、定义Index patterns 2、定义Discover(Search 查询数据) 3、定义Visualizations 3.1 定义Vertical Bar 3.2 、Choose a source 3.3、定义图表 4、定义…

【一文搞懂】—带霍尔编码器的直流有刷减速电机

文章目录 一、直流有刷电机二、减速比三、霍尔编码器3.1 霍尔编码器3.2 霍尔编码器测速原理 四、测速程序设计4.1 跳变沿检测4.2 计算转速 一、直流有刷电机 宏观上说直流有刷电机由固定部分&#xff08;定子&#xff09;和旋转部分&#xff08;转子&#xff09;组成。在定子上…

同一份数据,Redis为什么要存两次

Redis作为目前最主流的高性能缓存&#xff0c;里面有很多精妙的设计&#xff0c;其中有一种数据类型&#xff0c;当在存储的时候会同时采用两种数据结构来进行分别存储&#xff0c;那么 Redis 为什么要这么做呢&#xff1f;这么做会造成同一份数据占用两倍空间吗&#xff1f; …

Reinforcement Learning with Code 【Chapter 7. Temporal-Difference Learning】

Reinforcement Learning with Code This note records how the author begin to learn RL. Both theoretical understanding and code practice are presented. Many material are referenced such as ZhaoShiyu’s Mathematical Foundation of Reinforcement Learning, . 文章…

高层金属做power mesh如何避免via stack

随着工艺精进&#xff0c;pr要处理的层次也越来越多&#xff0c;如何选择power plan的层次尤为关键&#xff0c;一方面决定ir drop的大小&#xff0c;影响着芯片的功能&#xff0c;一方面决定绕线资源&#xff0c;影响面积。 选择高层metal做power mesh的关键在于厚金属&#…

局域网内主机ping不通,但是可以调用对方http接口(防火墙阻止了icmp协议)(关闭防火墙或者启用ICMP回显请求(ICMPv4-In))

文章目录 背景可能的原因问题排查及解决 背景 局域网内有一台主机&#xff0c;ping它ping不通&#xff0c;但是可以调用它的http接口&#xff0c;很诡异。。。 可能的原因 可能的原因有以下几种&#xff1a; 防火墙设置&#xff1a;局域网内的主机可能设置了防火墙&#xff…

勘探开发人工智能应用:地震层位解释

1 地震层位解释 层位解释是地震构造解释的重要内容&#xff0c;是根据目标层位的地震反射特征如振幅、相位、形态、连续性、特征组合等信息在地震数据体上进行追踪解释获得地震层位数据的方法。 1.1 地震信号、层位与断层 图1.1 所示为地震信号采集的过程&#xff0c;地面炮…

opencv-21 alpha 通道详解(应用于 图像增强,合成,蒙版,特效 等)

什么是alpha 通道&#xff1f; Alpha通道是计算机图形学中用于表示图像透明度的一种通道。在一个图像中&#xff0c;通常会有三个颜色通道&#xff1a;红色&#xff08;R&#xff09;、绿色&#xff08;G&#xff09;、蓝色&#xff08;B&#xff09;&#xff0c;它们合在一起…

macOS 源码编译 Percona XtraBackup

percona-xtrabackup-2.4.28.tar.gz安装依赖 ╰─➤ brew install cmake ╰─➤ cmake --version cmake version 3.27.0brew 安装 ╰─➤ brew update╰─➤ brew search xtrabackup > Formulae percona-xtrabackup╰─➤ brew install percona-xtrabackup╰─➤ xtr…

scrcpy2.0+实时将手机画面显示在屏幕上并用鼠标模拟点击2023.7.26

想要用AI代打手游&#xff0c;除了模拟器登录&#xff0c;也可以直接使用第三方工具Scrcpy&#xff0c;来自github&#xff0c;它是一个开源的屏幕镜像工具&#xff0c;可以在电脑上显示Android设备的画面&#xff0c;并支持使用鼠标进行交互。 目录 1. 下载安装2. scrcpy的高级…

Go语言开发小技巧易错点100例(八)

往期回顾&#xff1a; Go语言开发小技巧&易错点100例&#xff08;一&#xff09;Go语言开发小技巧&易错点100例&#xff08;二&#xff09;Go语言开发小技巧&易错点100例&#xff08;三&#xff09;Go语言开发小技巧&易错点100例&#xff08;四&#xff09;Go…

【论文笔记】RCM-Fusion: Radar-Camera Multi-Level Fusion for 3D Object Detection

原文链接&#xff1a;https://arxiv.org/abs/2307.10249 1. 引言 目前的一些雷达-相机融合3D目标检测方法进行实例级的融合&#xff0c;从相机图像生成3D提案&#xff0c;并与雷达点云相关联以修正提案。但这种方法没有在最初阶段使用雷达&#xff0c;依赖于相机3D检测器&…

Spring中如何用注解方式存取JavaBean?有几种注入方式?

博主简介&#xff1a;想进大厂的打工人博主主页&#xff1a;xyk:所属专栏: JavaEE进阶 本篇文章将讲解如何在spring中使用注解的方式来存取Bean对象&#xff0c;spring提供了多种注入对象的方式&#xff0c;常见的注入方式包括 构造函数注入&#xff0c;Setter 方法注入和属性…

如何在局域网外SSH远程访问连接到家里的树莓派?

文章目录 如何在局域网外SSH远程访问连接到家里的树莓派&#xff1f;如何通过 SSH 连接到树莓派步骤1. 在 Raspberry Pi 上启用 SSH步骤2. 查找树莓派的 IP 地址步骤3. SSH 到你的树莓派步骤 4. 在任何地点访问家中的树莓派4.1 安装 Cpolar4.2 cpolar进行token认证4.3 配置cpol…