java中要实现多线程的互斥同步访问,最简单的方式就是使用synchronized关键字。被其修饰的代码,相当于加了独占锁,线程只能互斥的访问,即同一时间只有一个线程能够访问这部分代码,其他线程只能在外等待。那么synchronized是如何做到这一点的?使用它还有哪些值得关注的细节?

2. synchronized的原理和实现细节

2.1 synchronized可以用在那些地方

  • 静态方法,锁对象为当前类的class对象,不用显式指定
  • 实例方法,锁对象为当前实例对象,不用显式指定
  • 同步代码块,锁对象为括号中指定的对象,必须显式指定




    被synchronized修饰的方法或者代码块,同一时刻只能有一个线程能够访问它。

2.2 synchronized是如何实现线程互斥访问的

虽然对于同步方法和同步代码块的底层细节略有不同,但都可以这么理解:对于被synchronized关键字修饰的代码区域,java虚拟机会在开始的位置插入monitorenter指令,而在结束和异常处插入monitorexit指令,每一个monitorenter指令必定有一个monitorexit指令与其配对。线程在执行到monitorenter指令时,将会尝试获取锁对象的monitor(监视器),该线程将进入同步方法或同步代码块中,同时monitor被锁定,防止被多个线程同时获取。获取monitor失败的线程将被阻塞在同步块或同步方法的入口处,整个过程如下图所示。

2.3 对象锁的monitor信息存储在哪

正如上面介绍的那样,任何对象都可以作为synchronized代码块的锁,而每个对象锁都有个monitor与之关联,线程通过获取该monitor的所有权来实现对被同步代码的互斥访问。这个monitor信息存储对象的对象头重。

2.4 monitor信息在对象头中的实现细节

对象锁的monitor信息是存储在该对象的对象头中。对象头的基本信息主要包括




由于锁信息是在Mark Word中,我们继续看看Mark Word到底包括哪些信息




内容比较多,不用强行记忆,只要知道大概有些东西就行了。

3. synchronized的内存语义

3.1 synchronized的可见性分析

由JMM的happens-before规则可知,对同步锁的释放happens-before于对同步锁的获取,因此在A线程释放锁之前对同步区域内共享变量作的修改,另一个线程B获取锁后将能够马上看到这种变化。它实现的原理和volatile类似

  • A线程释放锁时,JMM会把该线程对应的工作内存中的共享变量刷新到主存中
  • B线程获取锁后,JMM会把该线程对应的工作内存中的共享变量置为无效,同步代码块中的共享变量必须从主存中获取。

因此,如果共享变量只在同步代码块或者同步方法中被用到,那它是没必要用volatile修饰的!

3.2 synchronized禁止指令重排吗

那同步代码块会禁止指令重排吗?答案是不会。但只要同步块中的共享变量对其他线程不可见,那么我们就不必担心它引起的副作用。下面就是一个典型的例子

  • 静态内部类的懒汉式实现
public class Singleton {

    private Singleton() {

    }

    static class SingletonHolder {
public static final Singleton INSTANCE = new Singleton();
} public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
} }

这是线程安全的实现方式,尽管INSTANCE变量没有用volatile修饰,但我们不用担心指令重排序带来的线程不安全问题。因为

INSTANCE = new Singleton();

是在类初始化的过程中执行的.JVM在Class文件被加载后,被线程使用前会执行类的初始化。这个过程会通过一个初始化锁进行同步(对于每一个类或接口,都有唯一的初始化锁),也就是说只有一个线程能获取该锁并执行这段代码,其他无法线程无法访问到INSTANCE变量,故重排序对其他线程不可见,整个过程是线程安全的。

4.锁的优化

为了减少频繁获得和释放锁的性能开销,JVM做了一系列优化。锁一共分为4种状态,级别从低到高依次为:无锁状态,偏向锁状态,轻量级锁状态,重量级锁状态。锁的状态会随着竞争的加剧逐渐升级,获得和释放锁的性能开销也逐渐加大,且锁只能升级而不能降级。为什么需要这么设置,它们各自又有什么特点,什么时候会出发锁的升级?

  • 偏向锁: 当线程第一次访问同步块时,锁为偏向锁,该线程将一直持有偏向锁,直到有其他线程竞争该锁,该线程才释放锁,偏向锁也升级为轻量级锁。持有偏向锁的线程只在第一次获取时会进行CAS同步,接下来的访问和释放锁将不需要进行任何同步操作。
  • 轻量级锁:轻量级锁在获取锁的时候会使用CAS操作将锁对象头的mark word替换到线程的栈帧中,释放锁则使用CAS替换回来。竞争线程不会阻塞,而是使用自旋的方式等待,同时轻量级锁将升级为重量级锁。
  • 重量级锁:获取和释放锁更消耗性能,且竞争锁失败的线程将被阻塞。

下面是锁升级时的状态图

5. 总结

synchronized可以起到独占锁的作用,使多线程互斥的访问被其修饰的代码。虽然这种串行化会极大影响执行速度,但是能很好的保证线程安全,是开发中最经常使用的多线程技术之一。synchronized和volatile一样保证了多线程之间的可见性,又由于线程访问时的独占性对其他线程屏蔽了指令重排的细节,故而可以说是比volatile更重量级也更可靠的同步工具。

简单的互斥同步方式——synchronized关键字详解的更多相关文章

  1. “全栈2019”Java多线程第十六章:同步synchronized关键字详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  2. Java多线程(三)—— synchronized关键字详解

    一.多线程的同步 1.为什么要引入同步机制 在多线程环境中,可能会有两个甚至更多的线程试图同时访问一个有限的资源.必须对这种潜在资源冲突进行预防. 解决方法:在线程使用一个资源时为其加锁即可. 访问资 ...

  3. Java synchronized 关键字详解

    Java synchronized 关键字详解 前置技能点 进程和线程的概念 线程创建方式 线程的状态状态转换 线程安全的概念 synchronized 关键字的几种用法 修饰非静态成员方法 sync ...

  4. 从线程池到synchronized关键字详解

    线程池 BlockingQueue synchronized volatile 前段时间看了一篇关于"一名3年工作经验的程序员应该具备的技能"文章,倍受打击.很多熟悉而又陌生的知识 ...

  5. synchronized关键字详解(一)

    synchronized官方定义: 同步方法支持一种简单的策略防止线程干扰和内存一致性错误,如果一个对象对多个线程可见,则对该对象变量的所有读取或写入都是通过同步方法完成的(这一个synchroniz ...

  6. Java 多线程(六) synchronized关键字详解

    多线程的同步机制对资源进行加锁,使得在同一个时间,只有一个线程可以进行操作,同步用以解决多个线程同时访问时可能出现的问题. 同步机制可以使用synchronized关键字实现. 当synchroniz ...

  7. [java] java synchronized 关键字详解

    Java语言的关键字,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码.当两个并发线程访问同一个对象object中的这个加锁同步代码块时,一 ...

  8. synchronized关键字详解(二)

    synchronized关键字的性质 1.可重入:同一线程的外层函数获得锁之后,内层函数可直接再次获得该锁,好处:避免死锁,提升封装性 证明可重入粒度:1.同一个方法是可重入的 2.可重入不要求是同一 ...

  9. java关键字(详解)

    目录 1. 基本类型 1) boolean 布尔型 2) byte 字节型 3) char 字符型 4) double 双精度 5) float 浮点 6) int 整型 7) long 长整型 8) ...

随机推荐

  1. BaseCommand

    import java.io.Serializable; import android.util.Log; public class BaseCommand implements Serializab ...

  2. numpy 矩阵相关函数

    我们 知道,矩阵在python里面用的不少,所以记载下关于矩阵的操作 numpy.zeros():可以用来构造全零矩阵 >>> zeros(3) array([ 0.,  0.,   ...

  3. gatsbyjs 了解

    1.  模型 2. 总结&&资料 从模型上可以看出和jamstack 提出的架构模型比较相似,可以看成是一个具体的实现,功能还是比较强大的 https://www.gatsbyjs.o ...

  4. Oracle相关数据库操作

    1.进入oracle后台操作 su - oracle 2.数据库备份的指定位置 Oracle用sys用户登录查询数据库 select * from dba_directories a where a. ...

  5. Linux 驱动层实现阻塞和非阻塞

    linux应用层的函数默认是阻塞型的,但是要想真正实现阻塞,还需要驱动的支持才行. 例:open().scanf().fgets().read().accept() 等 1.默认情形,驱动层不实现阻塞 ...

  6. https配置指导

    为了使Apache支持https访问,系统需要安有apache.openssl.mod_ssl.so 证书申请 https://ninghao.net/blog/4449 安装证书时 安装编译open ...

  7. Navicat for MySQL使用手记(下)--实现自动备份数据库

    五.备份和还原MySQL数据库 在数据库的管理中,备份和还原是必须做认真做的事情,如果疏忽或者做粗糙了,那么一旦数据库故障后果不堪设想,所以Navicat同样也有备份和还原的功能,相比较创建功能,其备 ...

  8. Qt WebRTC demo

    This is a very simple demonstration of how to stream from a native application to the browser using ...

  9. window.open()打开新窗口被拦截

    window.open( url )是常用的打开新页面的方法,一般都没有问题,但是如果在ajax回调方法里面使用就会被浏览器拦截,因为在浏览器安全机制中,页面弹窗必须是由用户触发的才是安全弹窗,比如说 ...

  10. explain解析

    MySQL执行计划调用方式执行计划包含的信息执行计划显示内容解读MySQL执行计划的局限MySQL5.6支持OPTIMIZER_TRACE 1.什么是归并排序?将已有序的子序列合并,得到完全有序的序列 ...