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

Synchronized锁的用法及其升级原理

Synchronized锁的用法及其升级原理

简介

Synchronized锁是用来解决多线程并发访问共享数据的安全性问题的,可以保证对共享数据访问的原子性和可见性。

原子性:Synchronized可以保证只有同时只有一个线程能够访问共享数据,也就是一个互斥锁,通过这种互斥机制,保证的操作的原子性。

可见性 :一个线程在获取到锁时,或强制将线程工作内存中的变量失效,从主内存中重新读取该变量。当线程释放锁时,会将工作内存中的变量刷新回主内存,然后删除工作内存中的变量。可以理解为,Synchronized锁会在操作前重新从主存中读取数据,保证数据是最新的,操作完成后立即刷新回主存中,保证其它线程读取到最新数据,这样就保证了操作的可见性。

锁的基本使用

Synchronized可以用于修饰实例方法静态方法代码块

修饰实例方法

Synchronized修饰实例方法时,线程进入该方法前,需要获取到对应实例的锁。

示例:

给User类的实例方法getName加Synchronized锁:

public class User {private int age = 18;public synchronized String getName(String name) {System.out.println(name + " 已成功获取到锁");try {// 睡眠3秒,模拟业务处理Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(name + " 业务处理完成,释放锁");return name;}public int getAge() {return age;}

构造两个线程竞争同一个示例user1中的方法getName:

public class Main {public static void main(String[] args) {User user1 = new User();Thread thread1 = new Thread(() -> {user1.getName("thread1");});Thread thread2 = new Thread(() -> {user1.getName("thread2");});thread1.start();thread2.start();}
}

运行结果显示,只有先抢到锁的thread1释放锁后,thread2才能获得锁去访问getName方法:

thread1 已成功获取到锁
thread1 业务处理完成,释放锁
thread2 已成功获取到锁
thread2 业务处理完成,释放锁

如果thread2访问的是另一个实例的getName方法:

public class Main {public static void main(String[] args) {User user1 = new User();User user2 = new User();Thread thread1 = new Thread(() -> {user1.getName("thread1");});Thread thread2 = new Thread(() -> {user2.getName("thread2");});thread1.start();thread2.start();}
}

运行结果显示,thread2不会受到thread1的影响,thread2可以在thread1还未释放锁时就获取到锁,因为user1和user2属于两个实例,二者的实例锁相互独立:

thread1 已成功获取到锁
thread2 已成功获取到锁
thread2 业务处理完成,释放锁
thread1 业务处理完成,释放锁

修饰静态方法

Synchronized修饰静态方法时,线程进入该方法前,需要获取到对应类的锁

我们让getName方法变成静态方法:

public synchronized static String getName(String name) {System.out.println(name + " 已成功获取到锁");try {// 睡眠3秒,模拟业务处理Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(name + " 业务处理完成,释放锁");return name;}

thread1和thread2访问不同实例的getName方法:

public static void main(String[] args) {User user1 = new User();User user2 = new User();Thread thread1 = new Thread(() -> {user1.getName("thread1");});Thread thread2 = new Thread(() -> {user2.getName("thread2");});thread1.start();thread2.start();}

结果显示,只有先抢到锁的thread1释放锁后,thread2才能获得锁去访问getName方法,因为user1和user2属于同一个类,他们的对象锁是同一个:

thread1 已成功获取到锁
thread1 业务处理完成,释放锁
thread2 已成功获取到锁
thread2 业务处理完成,释放锁

此时对于User类中的实例方法getAge:

public class User {private int age = 18;public synchronized static String getName(String name) {System.out.println(name + " 已成功获取到锁");try {// 睡眠3秒,模拟业务处理Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(name + " 业务处理完成,释放锁");return name;}public int getAge(String name) {System.out.println(name + " 成功获取到age");return age;}
}

如果thread2访问任一实例的getAge方法:

public class Main {public static void main(String[] args) {User user1 = new User();User user2 = new User();Thread thread1 = new Thread(() -> {user1.getName("thread1");});Thread thread2 = new Thread(() -> {user2.getAge("thread2");});thread1.start();try {Thread.sleep(1000); // 确保thread1先获取锁} catch (InterruptedException e) {throw new RuntimeException(e);}thread2.start();}
}

结果显示,thread2访问实例方法getAge并不受thread1影响,因为访问getAge方法不需要获取锁:

thread1 已成功获取到锁
thread2 成功获取到age
thread1 业务处理完成,释放锁

可以推理出,不需要锁的方法,不会受锁的影响。

静态方法的对象锁和实例方法的实例锁也是互不影响的。

当然,如果方法getName和getAge都是静态方法,且都被Synchronized修饰,那么两个方法是同属于一个对象,则受制于同一个锁。

修饰代码块

当锁的是实例时,结果是和修饰实例方法是一样的,本质上是进入该代码块前,需要先获取到实例的锁:

public String getName(String name) {synchronized (this) {System.out.println(name + " 已成功获取到锁");try {// 睡眠3秒,模拟业务处理Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(name + " 业务处理完成,释放锁");}return name;}
 public static void main(String[] args) {User user1 = new User();User user2 = new User();Thread thread1 = new Thread(() -> {user1.getName("thread1");});Thread thread2 = new Thread(() -> {user1.getName("thread2");});thread1.start();try {Thread.sleep(1000); // 确保thread1先获取锁} catch (InterruptedException e) {throw new RuntimeException(e);}thread2.start();}
thread1 已成功获取到锁
thread1 业务处理完成,释放锁
thread2 已成功获取到锁
thread2 业务处理完成,释放锁

当锁的是对象时,结果是和修饰静态方法是一样的,本质上是进入该代码块前,需要先获取到对象的锁:

public String getName(String name) {synchronized (User.class) {System.out.println(name + " 已成功获取到锁");try {// 睡眠3秒,模拟业务处理Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(name + " 业务处理完成,释放锁");}return name;}
 public static void main(String[] args) {User user1 = new User();User user2 = new User();Thread thread1 = new Thread(() -> {user1.getName("thread1");});Thread thread2 = new Thread(() -> {user2.getName("thread2");});thread1.start();try {Thread.sleep(1000); // 确保thread1先获取锁} catch (InterruptedException e) {throw new RuntimeException(e);}thread2.start();}
thread1 已成功获取到锁
thread1 业务处理完成,释放锁
thread2 已成功获取到锁
thread2 业务处理完成,释放锁

锁的升级

由于Synchronized锁的获取和释放需要调用操作系统的方法将线程挂起和阻塞,就涉及到用户态和内核态的转换,会消耗大量的cpu资源,效率低下,被成为重量级锁,所以在JDK6,引入了锁升级机制来优化:偏向锁->轻量级锁->重量级锁。

  1. 偏向锁

JVM启动后,会启动偏向锁,此时有线程来访问共享资源时,就会进入偏向锁状态(在启动偏向锁之前会有一个延迟时间,在此时间内访问共享资源,会直接进入轻量级锁状态)。此时,会将共享资源对象的Markword的锁信息标志为偏向锁,偏向锁ID存储为当前线程的ID,当该线程再次访问该共享资源对象时,比对偏向锁ID和线程ID,如果一致,就可以直接访问。相当于在共享资源对象里记录一下线程ID,表示当前“偏向”与这个线程,当这个线程再来访问的话,就直接访问,相当于无并发竞争的单线程情况了。在竞争很弱的情况下,偏向锁效率较高。

值得注意的是,在 JDK15 中,偏向锁被默认关闭(仍然可以使用 -XX:+UseBiasedLocking 启用偏向锁),在 JDK18 中,偏向锁已经被彻底废弃。

因为偏向锁只在无并发竞争的情况下才高效,但是现代程序越来越多的有多线程并发情况,偏向锁的应用情况较少。但是JVM还需要维护偏向锁及其升级逻辑,带来的性能提升难以弥补其开销。

  1. 轻量级锁

当有第二个线程试图访问共享资源对象时,如果第一个线程已经释放了偏向锁,第二个线程就持有该偏向锁,此时锁继续是偏向锁。如果第一个线程没有释放偏向锁,也就是第二个线程竞争访问共享资源对象失败,此时,偏向锁升级为轻量级锁。此时,JVM会通过CAS(Compare And Swap)操作尝试:将共享资源对象的Markword拷贝到线程栈帧的LockRecord区域中,在Markword中存储指向LockRecord的指针,将LockRecord中的owner指针指向Markword。此时是通过CAS尝试获取锁,线程并没有被挂起,没有涉及内核状态的转变,所以相对“轻量”,在竞争不强的情况效率高。如果尝试CAS多次(自旋)失败,或者有太多线程同时尝试的话,锁就升级为重量级锁。

  1. 重量级锁

重量级锁是通过底层操作系统的服务来实现的,会为共享资源对象分配一个Monitor,在线程的LockRecord中存储一个指向Monitor的指针,代表持有该锁。操作系统会通过Monitor来维护线程与锁的状态(维护持有者线程,递归计数,等待队列等数据结构)。需要通过操作系统内核态的操作来控制锁,所以相对“重量”。

http://www.dtcms.com/a/408478.html

相关文章:

  • dt高端网站设计哪些网站可以做设计方案
  • 灰色词网站seo淄博网站建设公司哪家好
  • 北京网站设计我选刻一笔签名设计在线
  • 织梦网站自动跳转手机网站我国大宗商品交易所
  • 沈阳网站搜索排名wordpress移除google相关
  • 自学网站搭建网站有哪些推荐
  • 兰州做网站公司es5188像网站的ppt怎么做
  • 公司网站开发费用大概多少什么叫网站降权
  • 专业的美容网站建设wordpress微信登陆插件下载失败
  • 住建局建设工程质量监督站外链下载
  • 进入深圳市住房和建设局网站潍坊做网站好看
  • 商城网站制作深圳网站制作常德网站制作公司多少钱
  • bazel编译litert
  • 台州网站制作 外贸墙外必去的网站
  • 医院网站建设模板学校网站建设方案论文
  • 怎么申请 免费网站空间十大黄台软件app下载
  • 苏州全网网站建设asp网站建设代码
  • 寿光建设银行网站海南响应式网站建设制作
  • 网站开发的技术解决方案物流门户网站开发
  • 有做销售产品的网站有哪些内容sem推广
  • 构建AI智能体:四十四、线性回归遇见大模型:从数学原理到智能实战
  • 网站建设总结ppt珠海品牌型网站建设
  • 做网页兼职网站有哪些做外贸网站的都有哪些类型的公司
  • 合肥专业网站制作设计电子商务网站建设策划书
  • 介绍好看的电影网站模板免费下载可以做免费推广的网站有哪些
  • 十三师建设局网站深圳较便宜的网站建设
  • 《RStudio》软件下载_《RStudio》安装包下载_《RStudio》安装教程下载_《RStudio》网盘下载
  • 做网站的问卷调查做网站的关键词是指
  • 企业网站的内容选题来源中山专业门户网站制作咨询
  • 泊头做网站的公司宿州网络推广公司