关键字 synchronized 简单介绍_synchronized关键字是为了解决共享资源竞争的问题-程序员宅基地

技术标签: synchronized  java  多线程  # 并发知识体系  

本文概述

讲解下 Java 中和并发相关的个关键字:synchronizedvolatile

因为关键字已经是除了 JVM 外最底层的应用了。。。

所以除了分析在 JVM 中关键字的作用,其他的只能通过代码举例说明,这样就会显得比较得…枯燥。


synchronized

synchronized 关键字是为了解决共享资源竞争的问题,共享资源一般是以对象形式存在的内存片段。

所以,只有共享资源的读写访问才需要同步化,如果不是共享资源那么根本就没有必要同步。

对 synchronized 的基础使用其实比较简单:

  • 一把锁只能同时被一个线程获取,没有获得锁的线程只能等待;
  • 每个实例都对应有自己的一把锁(this),不同实例之间互不影响;例外:锁对象是*.class以及synchronized修饰的是static方法的时候,所有对象公用同一把锁;
  • synchronized 修饰的方法,无论方法正常执行完毕还是抛出异常,都会释放锁。

使用方法

synchronized 可修饰的对象如下:

修饰目标
实例方法 当前实例对象(即方法调用者)
静态方法 类对象
this 当前实例对象(即方法调用者)
class 对象 类对象
任意 Object 对象 任意示例对象

简单的使用就不详细讲了。


原理分析

举个例子,如下 mian 方法代码:

   public static void main(String[] args) {
    
        synchronized (new Object()) {
    
            System.out.println(1);
        }
    }

使用 jclasslib 查看字节码,main 方法字节码如下:

 0 new #2 <java/lang/Object>
 3 dup
 4 invokespecial #1 <java/lang/Object.<init>>
 7 dup
 8 astore_1
 9 monitorenter
10 getstatic #3 <java/lang/System.out>
13 iconst_1
14 invokevirtual #4 <java/io/PrintStream.println>
17 aload_1
18 monitorexit
19 goto 27 (+8)
22 astore_2
23 aload_1
24 monitorexit
25 aload_2
26 athrow
27 return

可以发现,有关监视器的几行命令:

9 monitorenter
......
18 monitorexit
......
24 monitorexit

MonitorenterMonitorexit 指令,会让对象在执行,使其锁计数器加1或者减1。

每一个对象在同一时间只与一个 monitor(锁) 相关联,而一个 monitor 在同一时间只能被一个线程获得,一个对象在尝试获得与这个对象相关联的 Monitor 锁的所有权的时候,monitorenter 指令会发生如下3中情况之一:

  • monitor 计数器为 0,意味着目前还没有被获得,那这个线程就会立刻获得然后把锁计数器+1,一旦+1,别的线程再想获取,就需要等待
  • 如果这个 monitor 已经拿到了这个锁的所有权,又重入了这把锁,那锁计数器就会累加,变成2,并且随着重入的次数,会一直累加
  • 这把锁已经被别的线程获取了,等待锁释放

monitorexit指令:释放对于 monitor 的所有权,释放过程很简单,就是讲 monitor 的计数器减1,如果减完以后,计数器不是0,则代表刚才是重入进来的,当前线程还继续持有这把锁的所有权,如果计数器变成0,则代表当前线程不再拥有该 monitor 的所有权,即释放锁。

下图表现了对象,对象监视器,同步队列以及执行线程状态之间的关系:

线程监视器示意图


特性描述

  1. 可重入特性:一个线程可以多次执行 synchronized ,重复获取同一把锁。

Synchronized 先天具有重入性。每个对象拥有一个计数器,当线程获取该对象锁后,计数器就会加一,释放锁后就会将计数器减一。


  1. 不可中断特性:一个锁后,另一个锁想要获得锁,必须处于阻塞或者等待状态。如果第一个线程不释放锁,那么第二个线程会一直阻塞或者等待,不可被中断。

JDK6 的优化

JDK 1.5 之前都只有 监视器 这一个重量级锁。JDK和开发都会大量使用。

JDK 1.6 的时候进行大量的改进,锁升级的过程为:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁


CAS
  • CAS 的全名是:Compare And Swap ,比较再交换。它是现在 CPU 广泛支持的一种对内存中的共享数据进行操作的一种特殊指令。
  • CAS 可以将比较和交换转化为原子操作,这个原子操作直接由 CPU 保证。
  • JAVA 中的 CAS 实现例如:AtomicInteger

悲观锁

总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞。

因此 synchronized 我们也将其称之为 悲观锁,JDK中的ReentrantLock也是一 种悲观锁。 性能较差!


乐观锁

总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,就算改了也没关系,再重试即可。所以不会上锁。

但是在更新的时候会判断一下在此期间别人有没有去修改这个数据,如何没有人修改则更新,如果有人修改则重试。

CAS这种机制我们也可以将其称之为乐观锁。 综合性能较好!


CAS获取共享变量时,为了保证该变量的可见性,需要使用volatile修饰。 结合 CAS 和 volatile 可以实现无锁并
发,适用于竞争不激烈、多核CPU的场景下。

  1. 因为没有使用 synchronized,所以线程不会陷入阻塞,这是效率提升的因素之一。

  2. 但如果竞争激烈,可以想到重试必然频繁发生,反而效率会受影响。(可以阅读 AtomicInteger 源码获得解释)


偏向锁

在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低,引进了偏向锁。

它的意思是这个锁会偏向于第一个获得它的线程, 会在对象头存储锁偏向的线程ID,以后该线程进入和退出同步块时只需要检查是否为偏向锁、锁标志位以及ThreadID即可。

偏向锁,仅限在不存在竞争的情况下。


轻量级锁

轻量级锁是 JDK1.6 之中加入的新型锁机制。

它名字中的"轻量级”是相对于使用 monitor 的传统锁而言的,因此传统的锁机制就称为 “重量级” 锁。

首先需要强调一点的是,轻量级锁并不是用来代替重量级锁的。

引入轻量级锁的目的:在多线程交替执行同步块的情况下,尽量避免重量级锁引起的性能消耗,但是如果多个线程在同一时刻进入临界区,会导致轻量级锁膨胀升级重量级锁,所以轻量级锁的出现并非是要替代重量级锁。


自旋锁

自旋锁(spinlock):是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。

获取锁的线程一直处于活跃状态,但是并没有执行任何有效的任务,使用这种锁会造成 busy-waiting。

  • 使用自旋锁会有以下一个问题:
  1. 如果某个线程持有锁的时间过长,就会导致其它等待获取锁的线程进入循环等待,消耗CPU。使用不当会造成CPU使用率极高。

  2. 上面Java实现的自旋锁不是公平的,即无法满足等待时间最长的线程优先获取锁。不公平的锁就会存在“线程饥饿”问题。

  • 自旋锁的优点
  1. 自旋锁不会使线程状态发生切换,一直处于用户态,即线程一直都是active的;不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快。
  2. 非自旋锁在获取不到锁的时候会进入阻塞状态,从而进入内核态,当获取到锁的时候需要从内核态恢复,需要线程上下文切换。 (线程被阻塞后便进入内核(Linux)调度状态,这个会导致系统在用户态与内核态之间来回切换,严重影响锁的性能)

锁消除

锁消除理解起来很简单,它指的就是虚拟机即使编译器在运行时,如果检测到那些共享数据不可能存在竞争,那么就执行锁消除。

锁消除可以节省毫无意义的请求锁的时间。

举个例子:

public static void main(String[] args) {
    
    appendStr("1", "2", "3");
}

public static String appendStr(String str1, String str2, String str3) {
    
    return new StringBuffer().append(str1).append(str2).append(str3).toString();
}

StringBuffer 的 append 方法是一个同步方法:

@Override
public synchronized StringBuffer append(String str) {
    
    toStringCache = null;
    super.append(str);
    return this;
}

appendStr 方法中,锁头是 this(new StringBuffer) ,同时,它是局部变量,不存在竞争。

所以,JVM 会自动消除锁。

再详细点,这个消除步骤是,即时编译器(JIT)在进行逃逸分析时,进行的优化。


锁粗化

原则上,我们在编写代码的时候,总是推荐将同步块的作用范围限制得尽量小,—直在共享数据的实际作用域才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,那等待线程也能尽快拿到锁。

大部分情况下,上面的原则都是没有问题的,但是如果一系列的连续操作都对同一个对象反复加锁和解锁,那么会带来很多不必要的性能消耗。

public static void main(String[] args) {
    
    StringBuffer sb = new StringBuffer();
    for (int i = 0; i < 100; i++) {
    
        sb.append(i);
    }
    System.out.println(sb.toString());
}

简单讲就是:即时编译器将 append 内的锁消除,在 for 循环上加一个锁


代码优化

优化注意点 原因
同步代码块内代码尽量少 执行越快,单位时间内等待时间越短,竞争越少。
自旋锁或者轻量级锁就能满足要求,不需要升级。
将一个锁拆分为多个锁 例如,HashTable 和 ConcurrenHashMap ,原理和上一条相似。
读写分离 读取不加锁,写入和删除加锁

参考文章

https://www.pdai.tech/md/java/thread/java-thread-x-key-synchronized.html

https://www.pdai.tech/md/java/thread/java-thread-x-key-volatile.html

https://www.pdai.tech/md/java/thread/java-thread-x-key-final.html

https://www.bilibili.com/video/BV1QC4y1H7qd?p=11

https://blog.csdn.net/topdeveloperr/article/details/80485900

https://blog.csdn.net/qq_38011415/article/details/89047812

https://blog.csdn.net/weixin_42762133/article/details/103241439

https://blog.csdn.net/javazejian/article/details/72828483

Java面试热点问题,synchronized原理剖析与优化_哔哩哔哩 (゜-゜)つロ 干杯

https://www.jianshu.com/p/9d3660ad4358?utm_source=oschina-app

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m0_46144826/article/details/117407164

智能推荐

matlab rx算法,精通MATLAB智能算法(2015代码)-程序员宅基地

文章浏览阅读363次。文件名大小更新时间Intelligent algorithm\10\s10_1\1.jpg535472014-04-26Intelligent algorithm\10\s10_1\s10_1.m87752014-04-26Intelligent algorithm\10\s10_2\1.jpg535472014-04-26Intelligent algorithm\10\s10_2\bys.m1..._rx-algorithm及其变体代码实现

Vue使用AntV/G2例子_vue2中使用antv中的水波图基础用法-程序员宅基地

文章浏览阅读780次。Vue项目中使用AntV/G2图表_vue2中使用antv中的水波图基础用法

Matlab GUI图像融合实现与优化_matlab 如何把两个图片融合-程序员宅基地

文章浏览阅读158次。图像融合是图像处理中的重要应用领域之一,其主要目的是将多幅图像信息融合为一张更加清晰、更具信息含量的图像。同时,在优化图像融合算法的过程中,Matlab工具箱已经提供了许多有用的函数和工具,帮助我们更好地进行图像处理和分析。本文介绍了基于Matlab GUI环境实现图像融合的方法,并提供了几种常用的图像融合算法。同时,在实现图像融合时,需要根据具体应用场景以及图像特征来选择和调整不同的融合方法和参数。在实现图像融合时,需要先将待融合的图像进行对齐。根据用户所选的融合方法,我们可以实现相应的图像融合算法。_matlab 如何把两个图片融合

Cache-主存地址映射_直接映射的主存地址格式-程序员宅基地

文章浏览阅读8k次,点赞27次,收藏132次。Cache-主存地址映射这几天十分懒惰,都没有复习计组,现在勉强把第四章看完了,最后一个内容Cache-主存地址映射一开始看不懂,现在终于差不多懂了,再把做题的步骤写下来供下次参考题目一假设主存容量为512K,Cache容量为4KB,每个子块为16个字,每个字32位。(1)Cache地址有多少位?可容纳多少块?题目没有说是按字访存,所以默认就是按照字节来访存。所以Cache的地址有多少位呢,我们就先看看它有多少个字节。Cache的容量是4KB,所以一共有4K个字节。所以Cache地址的位数就_直接映射的主存地址格式

超叼的一套知识要点,6位阿里大师共同编写的高可用分布式架构设计核心_robertleepeak-程序员宅基地

文章浏览阅读127次。开篇吹水从传统互联网到移动互联网再到物联网,中国乃至全球的互联网技术在近十年得到了高速发展。作为架构师,我们非常乐意把这些技术传播出去,让更多的人享受互联网技术的红利,让技术拓展商业的边界。阿里巴巴的双11技术已经越来越成熟,因为阿里巴巴已经逐步具备了基于云的计算能力,可以轻松应对各种业务压力。本书的上册《分布式服务架构:原理、设计与实战》详细介绍了如何解决线上高并发服务的一一致性、高性能、高可用、敏捷等痛点。本书延续了高可用服务架构的主题,侧重于讲解高可用架构设计的核心要点:可伸缩和可扩展,从应_robertleepeak

Clion如何创建规范化的代码注释?_cloin的注释怎么改为/*-程序员宅基地

文章浏览阅读447次,点赞8次,收藏9次。Clion如何创建规范化的代码注释?自动化,规范化?_cloin的注释怎么改为/*

随便推点

Android:Json数据转换成Map_android json to hashmap-程序员宅基地

文章浏览阅读1.6k次。本文利用Gson来做实现,先导入:implementation 'com.google.code.gson:gson:2.8.6'主要利用的是JsonObject里的entrySet()方法,相关Demo代码如下:import com.google.gson.Gson;import com.google.gson.JsonElement;import com.google.gson.JsonObject;import java.util.HashMap;import java.ut_android json to hashmap

R语言 quantmod下载股票代码无法访问Yahoo的唯一解决方法_r语言下载不了雅虎数据-程序员宅基地

文章浏览阅读394次。总结而言,当无法访问Yahoo时,使用quantmod包下载股票代码的一个可行解决方法是选择替代数据源,例如Alpha Vantage,并使用其提供的API密钥来获取数据。值得注意的是,使用Alpha Vantage作为替代数据源可能会有一些限制,例如每分钟请求的次数有限制,并且可能无法提供与Yahoo相同的数据范围和质量。getSymbols函数用于从指定的数据源获取股票数据,参数src用于指定数据源,api.key用于传递API密钥。在获得API密钥后,我们可以使用以下代码来获取股票代码。_r语言下载不了雅虎数据

Matlab函数句柄调用图像K均值分类_function varargout = classification_outputfcn(hobj-程序员宅基地

文章浏览阅读1.2k次。function varargout = guideTemp2(varargin)% GUIDETEMP2 MATLAB code for guideTemp2.fig% GUIDETEMP2, by itself, creates a new GUIDETEMP2 or raises the existing% singleton*.%% H = GUID_function varargout = classification_outputfcn(hobject, eventdata, handles)

1688API获得店铺详情_1688获得店铺详情-程序员宅基地

文章浏览阅读271次。seller_info - 获得店铺详情测试网址:http://console.open.onebound.cn/console/?i=eidiResult Object:---------------------------------------{ "user": { "nick": "yylsmould", "city": "浙江 宁波", "good_num": "", "level": "11", "user_num_id": null, "company_i_1688获得店铺详情

解决MATLAB对VS高版本出现“错误使用 mex未找到支持的编译器或 SDK。”的问题_matlab提示mex-程序员宅基地

文章浏览阅读1k次。说明:需要用MATLAB来编译一些C代码,但是总是无法找到编译器,网上很多教程并没有什么用,经过摸索终于成功,现予以总结,供遇到类似问题的童鞋参考。我的MATLAB是2018a,VS是2019版,其他高版本解决方法类似。_matlab提示mex

labview求n阶乘的和_labview常见习题大全-程序员宅基地

文章浏览阅读1.2k次。y2=m*x+bx的范围是0---10。y1和y2用数组显示件显示在前面板。6.10.26编程求Josephus(约瑟夫环)问题:m个小孩子围成一圈,从第一个小孩子开始顺时针方向数数字,到第n个小孩子离开,这样反反复复,最终只剩下一个小孩子,求第几个小孩子留下?7.10.27猴子吃桃子问题,每天吃完全部的桃子一半又一个,到第10天的时候还剩下一个,编程求第一天桃子的总数.8.10.28编程求100..._labviewn的阶乘求和