频道栏目
首页 > 资讯 > 其他 > 正文

简单的互斥同步方式之synchronized关键字详解

18-07-24        来源:[db:作者]  
收藏   我要投稿

1. 关于synchronized关键字

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更重量级也更可靠的同步工具。

相关TAG标签
上一篇:Spring Cloud云架构 - SSO单点登录之OAuth2.0 登出流程
下一篇:SpringMVC的流程解析
相关文章
图文推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站