Synchronized锁、锁的四种状态、锁的升级(偏向锁,轻量级锁,重量级锁)

目录

1. Synchronized锁

1.1 介绍

1.2 三种应用方式★

1.2.1 synchronized同步方法

1.2.2 synchronized 同步静态方法

1.2.3 synchronized 同步代码块

1.3 Synchronized锁底层原理

1.3.1 简答

1.3.2 详述

1. Monitor对象

2. Monitor与对象锁关联时 具体的流程:

2. 锁的升级★

3. 偏向锁(Biased Locking)

4. 轻量级锁

5. 偏向锁、轻量级锁、重量级锁对比

6. 偏向锁、轻量级锁 底层原理

7.小结★


1. Synchronized锁

1.1 介绍

介绍:Synchronized【对象锁】采用互斤的方式让同一时刻至多只有一个线程能持有【对象锁】,其它线程再想获取这个【对象锁】时就会阻塞住。

Java中提供了两种实现同步的基础语义:synchronized方法和synchronized块

1.2 三种应用方式★

synchronized 关键字最主要有以下 3 种应用方式:

  • 同步方法:为当前对象(this)加锁,进入同步代码前要获得当前对象的锁
  • 同步静态方法:为当前类加锁(锁的是 Class 对象),进入同步代码前要获得当前类的锁
  • 同步代码块:指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁

举例:

1.2.1 synchronized同步方法

通过在方法声明中加入 synchronized 关键字,可以保证在任意时刻,只有一个线程能执行该方法

代码:

public class AccountingSync implements Runnable {//共享资源(临界资源)static int i = 0;// synchronized 同步方法public synchronized void increase() {i ++;}@Overridepublic void run() {for(int j=0;j<1000000;j++){increase();}}public static void main(String args[]) throws InterruptedException {AccountingSync instance = new AccountingSync();Thread t1 = new Thread(instance);Thread t2 = new Thread(instance);t1.start();t2.start();t1.join();t2.join();System.out.println("static, i output:" + i);}
}

运行结果:

/*** 输出结果:* static, i output:2000000*/

上面代码 如果在方法 increase() 前不加 synchronized,因为 i++ 不具备原子性,所以最终结果会小于 2000000

解释:

如果increase()方法没有被声明为synchronized,那么在多线程环境下,两个线程可能会同时执行i++操作。这时,可能会发生以下情况:

  • 线程A读取了i的值。
  • 线程B读取了i的值。
  • 线程A将i的值增加1,并写回内存。
  • 线程B也将i的值增加1,并写回内存。

由于线程A和线程B读取的是同一个i的值,并且都对这个值增加了1,所以实际上i只增加了1,而不是2。这就导致了i的最终值小于预期的2000000

这种情况被称为“丢失更新”,因为线程B的操作覆盖了线程A的操作,导致线程A对i的增加没有被计入最终结果。

注意:一个对象只有一把锁,当一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,所以无法访问该对象的其他 synchronized 方法,但是其他线程还是可以访问该对象的其他非 synchronized 方法。

1.2.2 synchronized 同步静态方法

如果一个线程 A 需要访问对象 obj1 的 synchronized 方法 f1(当前对象锁是 obj1),另一个线程 B 需要访问对象 obj2 的 synchronized 方法 f2(当前对象锁是 obj2),这样是允许的,

但是线程安全是无法保证的,例如:

public class AccountingSyncBad implements Runnable {//共享资源(临界资源)static int i = 0;// synchronized 同步方法public synchronized void increase() {i ++;}@Overridepublic void run() {for(int j=0;j<1000000;j++){increase();}}public static void main(String args[]) throws InterruptedException {// new 两个AccountingSync新实例Thread t1 = new Thread(new AccountingSyncBad());Thread t2 = new Thread(new AccountingSyncBad());t1.start();t2.start();t1.join();t2.join();System.out.println("static, i output:" + i);}
}

输出结果:

/*** 输出结果:* static, i output:1224617*/

上述代码与前面不同的是,我们创建了两个对象 AccountingSyncBad,然后启动两个不同的线程对共享变量 i 进行操作,但很遗憾,操作结果是 1224617 而不是期望的结果 2000000。

因为上述代码犯了严重的错误,虽然使用了 synchronized 同步 increase 方法,但却 new 了两个不同的对象,这也就意味着存在着两个不同的对象锁,因此 t1 和 t2 都会进入各自的对象锁,也就是说 t1 和 t2 线程使用的是不同的锁,因此线程安全是无法保证的。

每个对象都有一个对象锁,不同的对象,他们的锁不会互相影响。

解决这种问题的的方式是将 synchronized 作用于静态的 increase 方法,这样的话,对象锁就锁的是当前的类,由于无论创建多少个对象,类永远只有一个,所有在这样的情况下对象锁就是唯一的。

1.2.3 synchronized 同步代码块

某些情况下,我们编写的方法代码量比较多,存在一些比较耗时的操作,而需要同步的代码块只有一小部分,如果直接对整个方法进行同步,可能会得不偿失,此时我们可以使用同步代码块的方式对需要同步的代码进行包裹。

示例如下:

public class AccountingSync2 implements Runnable {static AccountingSync2 instance = new AccountingSync2(); // 饿汉单例模式static int i=0;@Overridepublic void run() {//省略其他耗时操作....//使用同步代码块对变量i进行同步操作,锁对象为instancesynchronized(instance){for(int j=0;j<1000000;j++){i++;}}}public static void main(String[] args) throws InterruptedException {Thread t1=new Thread(instance);Thread t2=new Thread(instance);t1.start();t2.start();t1.join();t2.join();System.out.println(i);}
}

输出结果:

/*** 输出结果:* 2000000*/

我们将 synchronized 作用于一个给定的实例对象 instance,即当前实例对象就是锁的对象,当线程进入 synchronized 包裹的代码块时就会要求当前线程持有 instance 实例对象的锁,如果当前有其他线程正持有该对象锁,那么新的线程就必须等待,这样就保证了每次只有一个线程执行 i++ 操作。

当然除了用 instance 作为对象外,我们还可以使用 this 对象(代表当前实例)或者当前类的 Class 对象作为锁,如下代码:

//this,当前实例对象锁
synchronized(this){for(int j=0;j<1000000;j++){i++;}
}
//Class对象锁
synchronized(AccountingSync.class){for(int j=0;j<1000000;j++){i++;}
}

1.3 Synchronized锁底层原理

1.3.1 简答

1.3.2 详述

synchronized 底层使用的JVM级别中的Monitor (监视器)来决定当前线程是否获得了锁,如果某一个线程获得了锁,在没有释放锁之前,其他线程是不能或得到锁的。synchronized 属于悲观锁。

如果使用 synchronized 给对象上锁(重量级锁)之后,对象会交给Monitor(监视器)管理,

1. Monitor对象

monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是

首先需要明确的一点是:Java 多线程的锁都是基于对象的,Java 中的每一个对象都可以作为一个锁。还有一点需要注意的是,我们常听到的类锁其实也是对象锁

为什么Java中任意对象可以作为锁的原因?

Monitor内部维护了三个变量:

  • WaitSet:保存处于Waiting状态的线程

  • EntryList:保存处于Blocked状态的线程

  • Owner:持有锁的线程

2. Monitor与对象锁关联时 具体的流程:

1. 代码进入synchorized代码块,先让lock(对象锁)关联monitor(监视器),然后判断Owner是否有线程持有;
2. 如果没有线程持有,则让当前线程持有,表示该线程获取锁成功;
3. 如果有线程持有,则让当前线程进入entryList进行阻塞,如果Owner持有的线程已经释放了锁,在EntryList中的线程去竞争锁的持有权(非公平即阻塞队列中的线程会出现插队);
4. 如果代码块中调用了wait()方法,则会进去WaitSet中进行等待。

面试中你只回答以上 Synchronized锁底层原理,相当于说了一半,请继续往下看 

2. 锁的升级

面试官追问:Monitor实现的锁属于重量级锁,你了解过锁升级吗?

  • Monitor实现的锁属于重量级锁,里面涉及到了用户态和内核态的切换、进程的上下文切换,成本较高,性能比较低。
  • JDK 1.6引入了两种新型锁机制:偏向锁轻量级锁,它们的引入是为了解决在没有多线程竞争或基本没有竞争的场景下因使用传统锁机制带来的性能开销问题。

synchronized的锁升级
在Java中,锁的升级过程是为了在不同的竞争情况下提供最佳的性能和资源利用。锁的升级过程可以分为以下几个阶段:

  • 无锁状态:在对象被创建时,默认处于无锁状态。此时,任何线程都可以自由地访问和修改对象的数据,没有任何同步措施。
  • 偏向锁(Biased Locking):当一个线程第一次访问一个对象时,会尝试获取偏向锁。在第一次获得锁时,会有一个CAS操作,之后该线程再获取锁,只需要判断mark word中是否是自己的线程id即可,而不是开销相对较大的CAS命令
  • 轻量级锁(Lightweight Locking):如果另一个线程尝试获取已经被偏向的锁,偏向锁就会升级为轻量级锁。轻量级修改了对象头的锁标志,相对重量级锁性能提升很多每次修改都是CAS操作,保证原子性。如果CAS成功,线程可以继续执行,否则就会进一步升级为重量级锁。
  • 重量级锁(Heavyweight Locking):如果轻量级锁的CAS操作失败,表示存在竞争情况,锁就会升级为重量级锁。重量级锁底层使用的Monitor实现,里面涉及到了用户态和内核态的切换、进程的上下文切换,成本较高,性能比较低

更加详细的锁升级流程可以参考这里 :

synchronized到底锁的什么?偏向锁、轻量级锁、重量级锁到底是什么?

一旦锁发生了竞争,都会升级为重量级锁

锁的升级过程是为了提供最佳的性能和资源利用。在无竞争情况下,偏向锁可以减少锁操作的开销。在轻量级竞争情况下,轻量级锁可以提供更好的性能。而在重度竞争情况下,重量级锁可以保证线程的同步和数据的一致性。

3. 偏向锁(Biased Locking)

偏向锁是为了减少在单线程环境下锁的获取与释放开销而引入的机制。

核心思想:当一个线程第一次访问一个同步代码块时,它会尝试获取偏向锁,如果成功获取,后续的访问都无需竞争,直接获得锁(以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁。)。

只有在其他线程尝试获取锁时,偏向锁才会升级为轻量级锁或重量级锁。

使用背景

  • 适用于大多数情况下只被一个线程访问的场景。

  • 在无竞争的情况下,偏向锁可以显著减少获取锁的开销,提升性能。(它允许同一个线程在后续获取锁时无需进行额外的同步操作,几乎消除了锁的竞争开销。)

4. 轻量级锁

轻量级锁是在竞争不激烈的情况下,为了减少锁的开销而引入的机制。当一个线程尝试获取一个已经被偏向锁持有的锁时,会尝试将锁升级为轻量级锁。升级的过程中,会使用CAS(Compare and Swap)操作来尝试获取锁。如果获取成功,线程就可以进入临界区,执行同步操作。如果获取失败,表示有其他线程竞争锁,锁会膨胀为重量级锁。

使用背景

  • 适用于线程交替执行同步代码块的情况,尤其是在多个线程短时间内轮流申请同一锁资源,而非长时间持续竞争的场景中。

  • 轻量级锁能够有效地减少线程之间的同步开销,优化程序执行效率。

偏向锁对比轻量级锁

偏向锁只会执行一次CAS操作,而轻量级锁在发生锁的竞争和释放时每次都会执行cas的操作,会造成一定的性能开销。

5. 偏向锁、轻量级锁、重量级锁对比

特性偏向锁轻量级锁重量级锁
优点- 无竞争时性能极高- 低竞争时性能较好- 确保线程安全,适用于高竞争环境
- 减少锁操作的开销- 避免线程阻塞和上下文切换开销- 操作系统级别的锁,适用于需要严格同步的场景
缺点- 竞争发生时需要额外的锁升级开销- 竞争发生时可能会膨胀为重量级锁- 线程阻塞和唤醒开销大
- 适用于单线程环境- 竞争激烈时性能下降- 可能导致系统资源占用高
使用场景- 适用于单线程或几乎没有锁竞争的场景- 适用于低竞争的多线程环境- 适用于高锁竞争的多线程环境
锁获取- 无需额外操作,直接标记偏向- 通过CAS操作尝试获取- 操作系统互斥量,可能导致线程阻塞
锁释放- 无需额外操作,自动释放- 通过CAS操作尝试释放- 操作系统互斥量,需要显式释放
锁膨胀- 竞争发生时升级为轻量级锁或重量级锁- 竞争激烈时膨胀为重量级锁- 不会发生膨胀,始终为重量级锁

各种锁的优缺点对比(来自《Java 并发编程的艺术》):

优点缺点适用场景
偏向锁加锁和解锁不需要额外的消耗,和执行非同步方法比仅存在纳秒级的差距。如果线程间存在锁竞争,会带来额外的锁撤销的消耗。适用于只有一个线程访问同步块场景。
轻量级锁竞争的线程不会阻塞,提高了程序的响应速度。如果始终得不到锁竞争的线程使用自旋会消耗 CPU。追求响应时间。同步块执行速度非常快。
重量级锁线程竞争不使用自旋,不会消耗 CPU。线程阻塞,响应时间缓慢。追求吞吐量。同步块执行时间较长。

6. 偏向锁、轻量级锁 底层原理

java 中的锁 -- 偏向锁、轻量级锁、自旋锁、重量级锁_java owner指针-CSDN博客

深入解析Synchronized锁底层原理_synchronized底层-CSDN博客

7.小结

  • Java 中的每一个对象都可以作为一个锁,Java 中的锁都是基于对象的。
  • synchronized 关键字可以用来修饰方法和代码块,它可以保证在同一时刻最多只有一个线程执行该段代码。
  • synchronized 关键字在修饰方法时,锁为当前实例对象;在修饰静态方法时,锁为当前 Class 对象;在修饰代码块时,锁为括号里面的对象。
  • Java 6 为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁“。在 Java 6 以前,所有的锁都是”重量级“锁。所以在 Java 6 及其以后,一个对象其实有四种锁状态,它们级别由低到高依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。
  • 偏向锁会偏向于第一个访问锁的线程,如果在接下来的运行过程中,该锁没有被其他的线程访问,则持有偏向锁的线程将永远不需要触发同步。也就是说,偏向锁在资源无竞争情况下消除了同步语句,连 CAS 操作都不做了,提高了程序的运行性能。
  • 轻量级锁是通过 CAS 操作和自旋来实现的,如果自旋失败,则会升级为重量级锁。
  • 重量级锁依赖于操作系统的互斥量(mutex) 实现的,而操作系统中线程间状态的转换需要相对较长的时间,所以重量级锁效率很低,但被阻塞的线程不会消耗 CPU。

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

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

相关文章

【网络】数据链路层

目录 以太网 以太网的帧格式 MSS 交换机 MTU对UDP的影响 ARP协议 数据链路层是软件层的最底层协议&#xff0c;它的下面就是物理层&#xff0c;那么下面我们就来介绍一下它负责在网络通信中完成什么工作 我们前面说的IP协议是解决如何进行跨网络转发的&#xff0c;也就是…

零基础‘自外网到内网’渗透过程详细记录(cc123靶场)——下

细节较多&#xff0c;篇幅较大&#xff0c;分为上/下两部分发布在两篇文章内 另一部分详见下面文章 零基础‘自外网到内网’渗透过程详细记录(cc123靶场)——上https://blog.csdn.net/weixin_62808713/article/details/143572185 八、第二层数据库服务器权限获取 猜到新闻资…

13-鸿蒙开发中的综合实战:华为登录界面

大家好&#xff0c;欢迎来到鸿蒙开发系列教程&#xff01;今天&#xff0c;我们将通过一个综合实战项目来实现一个华为登录界面。这个项目将涵盖输入框组件、按钮组件、文本组件和布局容器的使用&#xff0c;帮助你更好地理解和应用这些组件。无论你是初学者还是有一定经验的开…

告别复杂协作:Adobe XD的简化替代方案

Adobe XD是一款集成UI/UX设计和原型创建功能的设计平台。它允许用户进行网页、移动应用的设计&#xff0c;以及原型的绘制&#xff0c;并且能够将静态设计转化为动态的交互原型。尽管Adobe XD提供了这些功能&#xff0c;但它依赖于第三方插件&#xff0c;且插件库有限&#xff…

ctfshow web文件上传 web166-170

1.web166 通过源码上传发现只能传zip&#xff0c;尝试一下图片上传也不行 把随便一张图片打包成zip文件&#xff0c;上传后发现有一个下载的地方,猜测是文件上传&#xff0c;尝试zip伪协议发现失败&#xff0c;打包php文件也失败了&#xff0c;不知为什么&#xff0c;&#x…

二开CS—上线流量特征shellcode生成修改模板修改反编译打包

前言 免杀几乎讲的差不多了&#xff0c;今天讲个CS的二次开发。我们原生态的CS特征肯定都是被提取完的了&#xff0c;包括它的流量特征&#xff0c;而我们要做的就是把它的流量特征给打乱&#xff0c;还可以修改生成的后门&#xff0c;使其生成即免杀。 实验环境 CS4.4&…

7.《双指针篇》---⑦三数之和(中等偏难)

题目传送门 方法一&#xff1a;双指针 1.新建一个顺序表用来返回结果。并排序数组。 2.for循环 i 从第一个数组元素遍历到倒数第三个数。 3.如果遍历过程中有值大于0的则break&#xff1b; 4.定义左右指针,以及target。int left i 1, right n - 1; int target -nums[i];…

Muse-Ant-Desgin-Vue 改造成 Vite+Vue3

后台地址&#xff1a;https://www.creative-tim.com/product/muse-vue-ant-design-dashboard?refantdv-official 一、配置 ViteAntDesginVue 配置ViteAntDesginVue ViteAntDesginVue配置&#xff1a;https://blog.csdn.net/qq_17523181/article/details/143241626 安装vue-ro…

实习作假:阿里健康实习做了RABC中台,还优化了短信发送流程

最近有二本同学说&#xff1a;“大拿老师&#xff0c;能帮忙看下简历吗&#xff1f;” 如果是从面试官的角度来看&#xff0c;这个同学的实习简历是很虚假的。 但是我们一直强调的是&#xff1a;校招的实习简历是不能出现明显的虚假。 首先&#xff0c;你去公司做事情&#…

疯狂Java讲义-Java基础类库

Java基础类库 本章思维导图 5-0Java基础类库.png 用户互动 使用Scanner获取键盘输入 Scanner主要提供了两个方法来扫描输入 hasNextXxx(); 是否还有下一个输入项&#xff0c;其中Xxx可以是int、long等代表基本数据类型的字符串。 nextXxx(); 获取下一个输入项。Xxx的含义与前一…

[前端] 为网站侧边栏添加搜索引擎模块

前言 最近想给我的个人网站侧边栏添加一个搜索引擎模块&#xff0c;可以引导用户帮助本站SEO优化&#xff08;让用户可以通过点击搜索按钮完成一次对本人网站的搜索&#xff0c;从而实现对网站的搜索引擎优化&#xff09;。 最开始&#xff0c;我只是想实现一个简单的百度搜索…

汇聚全球前沿科技产品,北京智能科技产业展览会·世亚智博会

在北京这座古老而又充满现代气息的城市中&#xff0c;一场科技与创新的盛宴正悄然上演——北京智能科技产业展览会&#xff08;简称&#xff1a;世亚智博会&#xff09;&#xff0c;作为全球前沿科技的汇聚地&#xff0c;不仅展示了人工智能、5G通信、虚拟现实等尖端技术的最新…

JAVA基础:数组 (习题笔记)

一&#xff0c;编码题 1&#xff0c;数组查找操作&#xff1a;定义一个长度为10 的一维字符串数组&#xff0c;在每一个元素存放一个单词&#xff1b;然后运行时从命令行输入一个单词&#xff0c;程序判断数组是否包含有这个单词&#xff0c;包含这个单词就打印出“Yes”&…

猎板PCB2到10层数的科技进阶与应用解析

1. 单层板&#xff08;Single-sided PCB&#xff09; 定义&#xff1a;单层板是最基本的PCB类型&#xff0c;导线只出现在其中一面&#xff0c;因此被称为单面板。限制&#xff1a;由于只有一面可以布线&#xff0c;设计线路上有许多限制&#xff0c;不适合复杂电路。应用&…

2025年山东省考报名流程图解

2025年山东公务员考试备考开始 为大家整理了从笔试到录用的全部流程&#xff0c;希望可以帮助到你们&#xff01;参考2024年山东省考公告整理&#xff0c;请以最新公告为准&#xff01; 一、阅读公告和职位表 二、职位查询 三、网上报名 四、确认缴费 五、网上打印准考证 六、参…

修改elementUI等UI组件样式的5种方法总结,哪些情况需要使用/deep/, :deep()等方式来穿透方法大全

文章目录 方法 1:全局修改样式示例:修改 `ElMessage` 的背景色和字体颜色方法 2:修改特定类型的 `ElMessage` 样式-全局-不需要穿透示例:修改 `ElMessage` 成功类型的样式方法 3:通过 Scoped CSS 在组件内部修改-局部-不需要穿透方法 4:使用 JavaScript 动态修改样式-不需…

pandas——对齐运算+函数应用

引言&#xff1a;对齐运算是数据清洗的重要过程&#xff0c;可以按索引对齐进行运算&#xff0c;如果没对齐的位置则补NaN&#xff0c;最后也可以填充NaN 一、Series的对齐运算 1.Series 按行、索引对齐 import pandas as pds1 pd.Series(range(10, 20), indexrange(10)) s2…

# Ubuntu 达人九步养成记(1)

Ubuntu 达人九步养成记&#xff08;1&#xff09; 目录&#xff1a; 一、ubuntu基本安装 二、设置语言环境 三、设置服务器镜像源 四、在启动栏添加终端图标 五、使用apt更新和升级系统软件 六、使用apt安装软件 七、使用apt删除软件以及apt-get 八、deb格式及谷歌浏览…

优选算法第五讲:位运算模块

优选算法第五讲&#xff1a;位运算模块 1.常见的位运算总结2.判断字符是否唯一3.丢失的数字4.两整数之和5.只出现一次的数字II6.消失的两个数字 1.常见的位运算总结 2.判断字符是否唯一 链接: link class Solution { public:bool isUnique(string astr) {if(astr.size() >…

计算机视觉算法真的难学吗?这些技巧让你轻松掌握

在当今这个数字化迅猛发展的时代&#xff0c;计算机视觉作为人工智能的重要分支&#xff0c;正在逐渐改变我们的生活和工作方式。很多人可能会觉得计算机视觉算法难以掌握&#xff0c;尤其是在面对复杂的数学和编程时&#xff0c;常常会感到无从下手。不过&#xff0c;实际上&a…