Java中的Synchronized

Synchronized 实现原理与应用

在多线程并发编程中synchronized一直都是比较重要的角色,很多人都把他称之为重量级锁。但是随着Java SE 1.6对synchronized进行了各种优化以后,有些情况下它就不是那么重了。

以下为大家介绍Java SE 1.6中为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁,以及锁的存储结构和升级过程。

Java中的每个对象都可以作为锁。具体表现为以下几种形式:

  • 对于普通同步方法,锁是当前对象。
  • 对于静态同步方法,锁是当前类的Class对象。
  • 对于同步方法块,锁是Synchonized括号里面配置的对象

当一个线程视图去访问同步代码块时,它首先必须获得锁,退出或者抛出异常时必须释放锁。

锁到底存在哪里,锁里面存储了什么对象呢

从JVM规范中可以看到Synchronized在JVM里的实现原理,JVM基于进入和退出Monitor对象来进行实现方法同步和代码块同步,但是两者的实现细节不是一样的。代码块同步是使用monitorenter和monitorexit指令实现的,而方法同步是使用另外一种实现方式实现的,细节在JVM规范里并没有详细说明。但是方法的同步同样可以使用这两个指令来实现。

monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处,JVM要保证每个monitorenter必须有对应的monitorexit与之配对。任何对象都有一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter指令的时候,将会尝试获取对象所对应的monitor的所有权,即尝试获取对象的锁。

一个简单的Java代码:
多线程操作一个变量进行累加操作

/**
 * Created by gongyan on 2018/4/16.
 */
public class SynchronizedClass {

    int i = 0;

    public static void main(String[] args) {
        new SynchronizedClass().addNumber();
    }

    public void addNumber() {
        synchronized (SynchronizedClass.class) {
            i++;
        }
    }

}

我们来看一下它解析完是什么样子的呢?
使用javap -c xxx 去进行反编译

public void addNumber();
    Code:
       0: ldc_w         #3                  // class com/wsqandgy/SynchronizedClass
       3: dup
       4: astore_1
       5: monitorenter
       6: aload_0
       7: dup
       8: getfield      #2                  // Field i:I
      11: iconst_1
      12: iadd
      13: putfield      #2                  // Field i:I
      16: aload_1
      17: monitorexit
      18: goto          26
      21: astore_2
      22: aload_1
      23: monitorexit
      24: aload_2
      25: athrow
      26: return

我们可以看到在进行操作的时候会进行设置 5: monitorenter 进入监听器 在操作完以后 17: monitorexit 异常的时候会进行一次 23: monitorexit

Java 头对象

Synchronized用的锁是存在Java对象头里面的。如果对象是数组类型,则虚拟机用3个字宽(Word)存储对象头,如果对象是非数组类型,则用两个字宽存储对象头。在32位虚拟机中,1字宽等于4字节,32bit。

对象头的长度

Java对象头里面的Mark Word里面默认存储对象的HashCode、分代年龄和锁标记位。32位JVM的Mark Word的默认存储结构如下所示:

image.png

在运行期间,Mark Word里面存储的数据会随着锁标志位的变化而变化。Mark Word可能变化为存储以下4种数据:

image.png

64位虚拟机下,Mark Word是64bit大小的,存储结构如下:


image.png

锁的升级与对比

Java SE 1.6为了减少获得锁和释放锁带来的性能损耗,引入了偏向锁轻量级锁,在Java SE 1.6中,锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争状态逐渐升级。锁可以升级但是不能降级,意味着偏向锁升级为轻量级锁以后不能降级为偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。

偏向锁

HotSpot的作者经过研究发现,大多时候,锁不仅不存在多线程的竞争,并且总是由同一个线程多次获得,为了让线程获得锁的代价更低所以引入了偏向锁。当一个线程访问同步块并获取锁的时候,会在对象头和栈针中的锁记录里面存储偏向锁的线程ID,以后该线程在进入和退出同步块的时候不需要进行CAS操作来进行加锁和解锁,只需简单的测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。如果测试成功,表示线程已经获取了锁。如果测试失败了的话,则需要再测试一下Mark Word中偏向锁的标识是不是被设置成了1(表示当前是偏向锁):如果没有设置,则使用CAS竞争锁;如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。

偏向锁的撤销

偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试去竞争偏向锁的时候,持有偏向锁的线程才会释放锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有正在执行的字节码)。他会首先暂停拥有偏向锁的线程,然后去检查偏向锁的线程是否存活,如果线程不在活动状态,则将对象头设置为无锁状态;如果线程仍然存活,拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的Mark word要么重新偏向其他的线程,要么恢复到无锁或者标记对象不适合偏向锁,最后唤醒暂停的线程。

下图展示了偏向锁初始化和撤销的流程:

偏向锁的获得和撤销流程

偏向锁的关闭

偏向锁在Java 6和Java 7 是默认启用的,但是需要在启动后几秒钟之后才会被激活,如果有必要可以使用JVM参数来关闭延迟 :-XX:BiasedLockingStartupDelay=0。如果确定应用程序里所有的锁通常情况下处于竞争状态,可以通过JVM参数关闭偏向锁:-XX:-UseBiasedLocking=false,那么程序默认会进入轻量级锁。

Biased 偏向的,结果偏移的,有偏见的。意思为偏向于一个线程使用的锁,偏向锁

轻量级锁

  • 轻量级锁加锁

线程在执行同步块之前,JVM会现在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,官方成为Displaced Mark Word。然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,则表示其他线程竞争所,当前线程便尝试使用自旋来获取锁。

  • 轻量级锁解锁

轻量解锁时,会使用院子的CAS操作将Displaced Mark Word替换回到对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。

两个线程同事争夺锁,导致锁膨胀的流程图。

两个线程争夺锁流程

因为自旋会消耗CPU,为了避免无用的自旋(比如获得锁的线程被阻塞住了),一旦锁升级成重量级锁,就不会再恢复到轻量级锁的状态了。当锁处于这个状态下,其他线程视图获取锁时候,都会被阻塞住,当持有锁的线程释放锁之后就会唤醒这些线程,被唤醒的线程就会进行新一轮的夺锁之战。

锁的优缺点对比

锁的优缺点
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容