JAVA的锁(一)

什么是JAVA的锁, 为什么要用锁

字面意思, 锁就是要锁住一些东西. 不让别人动, 或者不希望除了有钥匙的之外的人了解锁里面的内容.

那么在什么情况下需要用到锁呢?

日常生活里, 需要用到锁的地方就是你的柜子啊, 你的箱子啊, 你的... 发现了么, 都是你的. 当然你也可以给别人的东西上锁, 但是一旦你锁上了, 不考虑道德问题, 那么这个东西暂时就属于你了.
所以, 在我们的程序里, 什么时候需要用锁呢?

线程同时访问某个东西, 但是我们不希望他们一起来, 或者无法承受他们一起来从而可能产生的问题

所以这个时候我们就提到了线程. 现在随着多计算核心的机器的发展, 为了解决某一并行分支对相同资源的同步访问, 操作系统层面提供了一个叫做互斥信号量的概念, 对应到JAVA里, 就是我们要接触到的

说起来JAVA的锁, 大部分人第一反应就是synchronized. 这是作为JAVA的关键字来修饰方法或者代码块的. 用来标识使用某个对象来同步这个方法或者代码块. 其实在JAVA的Concurrent包下还有一个Lock, 它也是锁. 接下来, 我们来简要的了解一下Locksynchronized

有什么锁, 分别是什么, 有什么用

前面我们说了, 大部分人的第一反应synchronized, 那么我们来看下它.

我们知道, synchronized是JAVA内置的关键字. 在JAVA里所有的非NULL对象都可以作为一把锁给它用, 这就是我们叫它内置锁的原因?哈哈哈哈.

其实更多的我们在书上看到的是说synchronized监视锁, 为什么叫监视锁? 那是因为JVM内部是通过monitorentermonitorexit这两个字节指令来获取和释放锁的, 它的JAVA层实现类的名字是ObjectMonitor, 所以我们叫它监视锁

synchronized使用起来非常的简单, 从前面的介绍我们可以知道, 只需要给这个关键字一个对象作为锁就可以了.

// 修饰静态方法, 监视这个类对象
public static synchronized void foo(){...}
// 修饰方法, 监视这个实例对象, 一般就是我们说的`this`
public synchronized void foo(){...}
/*
 * 修饰代码块, 括号里传入的是需要监视的对象
 * 当一个类里面存在多个临界区需要同步的话, 
 * 一个this对象已经无法满足需求了, 可以新new一个对象
 * 比如这样
 * private Object lock = new Object();
 * public void foo(){
 *     synchronized (lock){...}
 * }
 */
public void foo(){
    synchronized (this){...}
}

只有多个线程同时去访问这一个监视锁保护的临界区的时候才会发生竞争, 锁的竞争我们后面继续介绍.

synchronized 我们用的话就是上面的几种方式, 那么它都有什么特点呢?

它是可重入

什么叫做可重入? 就是说当一个线程已经持有了一把监视锁的时候, 如果这个线程需要再次获取这个锁的话, 就不需要竞争了, 直接可以拿到.

/*
 * 需要 this 对象上面的监视锁, 
 * 在获取锁之后, 调用 reentrant() 的时候需要再次获取锁
 * 但是由于 可重入 性, 这个方法是没有问题的
 */
public synchronized void foo(){ reentrant(); }

public synchronized void reentrant(){...}

它是阻塞的

如果线程A持有了当前对象的监视锁之后, 在释放之前, 会阻塞其他线程对该对象的访问. 这时候如果线程B来试图获取, 失败后就会加入到阻塞队列中等待的释放.
什么时候释放, 阻塞怎么办? 先拿个小本本记下来

它是非公平锁

  • 什么叫公平锁?
    公平锁就是先来后到. 如果线程A持有某对象的监视锁期间, 有其他线程BCD...来试图获取锁, 在失败之后会加入阻塞队列. 当线程A释放锁之后, 如果BCD...是按照加入队列的先后顺序来排队被JVM唤醒的话, 就是公平锁. 因为对阻塞队列中所有等待的线程都是公平的.
  • 什么叫非公平锁?
    上面的例子里, JVM唤醒线程的顺序不按照阻塞队列里面的排队顺序来.

为什么它是非公平锁?
因为他在每次获取锁的时候, 都会自旋等待一段时间, 当超过超时时间之后, 才进入阻塞队列. 如果在自选的过程里恰好线程A释放了锁, 这个时候自旋线程就可以直接拿到了, 而不需要去排队等候.

公平锁在实际的操作过程里, 是需要记录加入阻塞队列的先后顺序的. 而且即使有线程来的时候刚好线程A释放, 但是由于要实现公平原则, 那么这个线程依然要老老实实的去阻塞队列里排队, 然后JVM再唤醒队列头的等待线程. 这些都是需要额外消耗性能的.

我们一开始的时候提到, 多计算核心的发展, 在操作系统层面提供了一个互斥信号量的东西, 它在JVM里就是我们所说的锁, 由于synchronized是通过JVM里的monitorentermonitorexit指令实现的, 而monitor*这两个指令又比较严重的依赖操作系统的互斥信号量, 这就存在了一个问题. 使用系统的互斥信号量需要将线程从用户态切换到内核态, 这种转换肯定会存在性能问题, 所以我们之前都谈synchronized色变, 上学的时候, 老师警告⚠️我们, synchronized影响性能.

其实从jdk6开始, JAVA就针对这个问题进行了优化.首先是对锁的状态进行了区分, 监视锁不再是简单的一把锁, 它还有各种状态. 锁的状态流转, 其实就是对应多个线程对锁的竞争程度, 如果这个锁的竞争度很低, 那么JVM 就不会通过系统互斥信号量来实现同步, 随着竞争的加剧, 获取锁的代价越来越大, 才会开始以来系统的互斥信号量

那么针对锁的状态区分, JVM是怎么做的呢?

前面我们知道了监视锁可以加在任何一个非NULL对象上, 那么对象怎么表示自己当前被监视呢?

现行的HotSpot VM中, 每个对象在内存中分为三个部分

  • 对象头✔️
  • 实例数据
  • 对齐填充

锁信息就放在对象头中. 但是由于对象头的长度和具体的机器字长相关. 一般来说, 目前分为32位64位. 其中对象头的长度分别为32bit64bit, 它最后的2bit是锁的状态标志位, 用来标记当前对象的状态. [现在32位机器应该已经不多了]

对象所处的状态, 决定了对象头存储的内容

状态 标识位 储存内容
未锁定 01 对象哈希码、对象分代年龄
轻量级锁 00 指向锁记录的指针
膨胀 10 执行重量级锁定的指针
GC标记 11 空(不需要记录信息)
可偏向 01 偏向线程ID、偏向时间戳、对象分代年龄

我们看到表格中, 未锁定和可偏向的标识位都是01, 那么怎么区分呢? 这里使用了倒数第三位, 标识了是否偏向 0未加锁 1偏向锁. 对象头里还有很多其余的JAVA核心内容, 比如GC分代年龄/ 线程ID/是否被GC标记等等. 那些复杂的东西, 我们回头再整理.现在我们只需要记得这几个标识位和对应的状态就好, 然后我们一个一个看.

11 可被GC标记的

对象头的后两位如果被设置为 11 , 则表示这个类是可GC的. 对象头里的其余信息则不需要关注了, 因为这个类即将被GC.

01 无锁

跟偏向多了一位来区分, 表示当前对象禁止偏向

01 偏向锁

这个对象是可偏向的, 它存在三种状况

  • 匿名偏向 Anonymously biased
    表示当前还没有线程偏向这个对象, 第一个试图获取锁的线程可以使用CAS指令去改变锁对象的对象头指向自己. 这个状态是可偏向对象所的初始状态
  • 可重偏向 Rebiasable
    epoch字段无效, 可以理解为之前这个锁对象偏向于某个线程, 但是这个线程已经退出了临界区, 这个时候如果另外一个线程来获取锁, 可以使用CAS指令去改变锁对象的对象头来指向自己
  • 已偏向 Biased
    epoch字段有效,表示锁对象当前已经偏向某一个线程.

00 轻量级锁

偏向锁存在竞争时, 进入轻量级锁的状态, 此时获取锁的线程开始自旋等待.

10 重量级锁

竞争锁的各个线程开始使用系统的互斥信号量做同步, 回到最原始的状态.

我们知道了锁是怎么标记的之后, 来看一下被标记的锁状态之间是怎么流转的

从前面我们应该可以很容易知道, 锁的状态流转是
无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁
偏向锁是默认打开[?]的, 所以基本上一个对象被线程持有之后, 就直接获取了偏向锁, 随着锁竞争的升级, 逐步变为轻量级锁, 以致到最后膨胀为重量级锁

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