当前位置:   article > 正文

java中实现线程互斥的关键词_简单的互斥同步方式——synchronized关键字详解

用来实现多线程的互斥的关键字

2. synchronized的原理和实现细节

2.1 synchronized可以用在那些地方

静态方法,锁对象为当前类的class对象,不用显式指定

实例方法,锁对象为当前实例对象,不用显式指定

同步代码块,锁对象为括号中指定的对象,必须显式指定

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

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

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

785a417b7fe6c4b0ae4be485fe0561b8.png

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

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

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

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

01997ca12a78f5524ca0ea0db36dd04a.png

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

c1aff596a1438104de4a13a173d8a914.png

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

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替换回来。竞争线程不会阻塞,而是使用自旋的方式等待,同时轻量级锁将升级为重量级锁。

重量级锁:获取和释放锁更消耗性能,且竞争锁失败的线程将被阻塞。

下面是锁升级时的状态图

eb0cfc68ee6e29f8d4599f90569e5a87.png

5. 总结

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/292150?site
推荐阅读
相关标签
  

闽ICP备14008679号