锁(一)

11.png

乐观锁与悲观锁

乐观锁和悲观锁是一个宏观上的分类,它并不是指特定的哪个锁。而是在并发情况下两种不同的策略。

乐观锁
就是很乐观,每次去使用数据的时候都认为不会有其他线程修改数据,所以不会上锁。在更新数据的时候,则会在更新之前先判断有没有别的线程更新了这个数据,如果有,则重新读取,再次尝试更新,直到更新成功,或者报错哦放弃更新。如果没有,则当前线程将自己修改的数据成功写入。

悲观锁
就是很悲观,认为每次去使用数据的时候一定有其他线程来修改数据,所以在获取数据的时候会先加锁,确保数据不被修改。这样其他线程拿数据的时候就会被挡住,直到锁释放。

乐观锁在 Java 中是通过无锁编程实现,通常采用 CAS 算法,在 Java 中,synchronized 和 Lock 的实现类都是悲观锁。

乐观锁基础--CAS
CAS 全称 Compare-and-Swa,即 比较并替换。
比较:读取到一个值 A,在将其更新为 B 之前,检查原来的值是否为 A(未被其他线程修改过)
替换:如果是 A,则把 A 更新为 B,结束,否则不会更新。
比较和替换都是原子操作,可以理解为瞬间完成,下面模仿写一个乐观锁的逻辑伪代码:

public void test(){
int data = 123; //数据
//更新数据的线程会进行如下操作
while(true){
int oldData = data;
int newData = doSomething(oldData);
//模拟CAS操作
if(data==oldData){ //比较,检查 data 有没有被改变,没有的话更新数据,否则一直循环判断比较
data = newData;
break;
}
}
}

Java 是通过 native 方法实现的 CAS。Android 中原子类就是使用 CAS 乐观锁,比如 AtomicInteger 等。

乐观锁的特点是回滚重试,悲观锁的特点是阻塞事务,在写操作比较少的情况下,即冲突很少发生的场景中,使用乐观锁可以省去锁的开销,加大了系统的吞吐量。但是如果在冲突经常发生的情况下,乐观锁会不断进行重试,反而降低了性能,所以这时候悲观锁比较合适。

所以总结:

● 悲观锁适合写操作多的场景,先加锁可以保证写操作时数据正确。
● 乐观锁适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。

CAS 虽然高效,但是也存在三大问题:

  1. ABA 问题:即内存值原来是 A,后来被修改成 B,然后又被修改成 A,那么在 CAS 检查时会发现值没有变化,但实际上是变化了的。解决版本是在变量前面加版本号,每次更新时版本号加一,变化过程就变成了:
    A-B-A >> 1A-2B-3A
  2. 循环时间长开销大:CAS 如果长时间不成功,会导致其一直自旋,给 CUP 带来开销
  3. 只能保证一个共享变量的原子操作:对多个共享变量操作时,CAS 无法保证操作的原子性。

synchronized(读:星隔来) 与 Lock interface
Java 的两种加锁方式:一种是使用 synchronized 关键字,一种是实现 Lock 接口。

synchronized :
● 修饰普通方法
● 修饰静态方法
● 修饰代码块
synchronized 的锁升级过程

class Test{
private static final Object object = new Object();
public void test(){
synchronized(object) { // do something
}
}
}

当使用 synchronized 锁住某个代码块的时候,一开始锁对象(即上面 object)并不是重量级锁。而是偏向锁。

偏向锁的字面意思就是 “偏向于第一个获取它的线程”的锁,线程执行完同步代码块之后,并不会主动释放偏向锁。

当第二次到达同步代码块时,线程会判断此时持有锁的线程是否就是自己,如果是则正常往下执行。由于之前没有释放,这里就不需要再重新加锁,如果重头到尾都是一个线程在使用锁,很明显偏向锁几乎没有额外开销,性能极高。

一旦第二个线程加入来锁竞争,偏向锁会转换为轻量级锁。

锁竞争:如果多个线程轮流获取一个锁,但是每次获取的时候都很顺利,没有发生阻塞,就不存在锁竞争,只有当某个线程获取锁的时候,发现锁已经被占用,需要等待释放,则说明发生了锁竞争。

在轻量锁状态上如果继续发生锁竞争,没有抢到锁写得线程会进行自旋操作,即在一个循环中不停地判断释放可以获取锁。获取锁的操作,就是通过 CAS 操作修改对象头里面的锁标志位。先比较当前锁标记位是否为释放状态,如果是,将其设置为锁定状态,当前线程就算持有了锁,然后线程将当前锁的持有者信息改为自己。

当获取锁的线程操作时间很长时,比如进行复杂计算,那么其他等待锁的线程就会进入长时间的自旋操作,其实这时候相当于只有一个线程在工作,其他线程什么都做不了,这种现象称为 忙等。

忙等 是有限度的,JVM 有一个计数器来记录自旋次数,默认允许循环 10 次。

如果锁竞争严重,当某个线程的自旋次数达到最大时,会将轻量级锁升级为重量级锁(修改方法依然是通过CAS修改锁标志位,但不修改持有锁的线程 ID),当后续线程尝试获取锁时,会发现被占用的锁是重量级锁,则直接将自己挂起,等待释放锁的线程去唤醒。

可重入锁(递归锁)
可重入锁的意思是“可以重新进入的锁”,即允许同一个线程多次获取同一把锁。
比如在一个递归函数里有加锁操作,那么递归函数会自己阻塞自己吗?如果不会,则这么锁就是可重入锁。

Java 中以 Reentrant 开头命名的锁都是可重入锁,而且 JDK 提供的所有现成 Lock 的实现类,包括 synchronized 关键字锁都是可重入的。

公平锁和非公屏锁
如果多线程申请一把公平锁,那么获得锁的线程在释放锁的时候,先申请的先得到,很公平。
如果是非公平锁,后申请的线程可能先获取锁,是随机获取还是其他方式,根据实现的算法而定。

如果没有特殊要求,优先考虑使用非公平锁。而对于 synchronized 锁而言,它只能是一种非公平锁,没有任何方式使其变成公平锁。

可中断锁
意思是可以响应中断的锁。
Java 中没有提供任何可以直接中断线程的方法,只提供中断机制。

当线程A 向线程B 发出停止运行请求时,就是调用 Thread.interrupt() 方法。但线程B 不会立即停止运行,而是自行选择在合适的时间点以自己的方式响应中断,也可以直接忽略此中断。
也就是说,Java 不能直接中断线程,只是设置了状态为响应中断的状态,需要被中断的线程自己决定怎么处理。

如果线程 A 持有锁,线程 B 等待持获取该锁。由于线程 A 持有锁的时间过长,线程 B 不想继续等了,我们可以让线程 B 中断自己或者在别的线程里面中断 B,这种就是 可中断锁。

synchronized 锁是不可中断锁,而 Lock 的实现类都是 可中断锁。

共享锁

字面意思是多个线程可以共享一个锁。一般用共享锁都是在读数据的时候,比如我们可以允许 10 个线程同时读取一份共享数据,这时候我们可以设置一个有 10 个凭证的共享锁。

互斥锁

字面意思是线程之间互相排斥的锁,也就是表明锁只能被一个线程拥有。在 Java 中, ReentrantLock、synchronized 锁都是互斥锁。

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

推荐阅读更多精彩内容