多线程进阶(1)

作者:爱塔居

专栏:JavaSE

文章简介:介绍锁策略

作者简介:大三学生,希望和大家一起进步~

多线程进阶(1)_第1张图片

文章目录

文章目录

前言

一、乐观锁与悲观锁

二、轻量级锁与重量级锁

三、自旋锁与挂起等待锁

四、互斥锁与读写锁

五、可重入锁与不可重入锁

六、公平锁与非公平锁


前言

以下介绍的锁策略,不只是针对Java。别的语言,别的工具只要涉及到锁,也是同样适用的。


一、乐观锁与悲观锁

锁的实现者,预测接下来锁冲突的概率是大,还是不大。根据这个冲突的概率,来决定接下来该咋做。这里的锁冲突,就是锁竞争。两个线程针对一个对象加锁,产生阻塞等待了。

乐观锁:预测接下来冲突概率不大。

悲观锁:预测接下来冲突概率比较大。

从而,导致最终要做的事情是不一样的。

就比如天气预报说,明天下午可能会下雨。我觉得明天下午不一定会有雨,说不准雨挺小的,不带伞。那就是乐观锁。

如果我明天下午出门,做好准备,就怕明天下午真的有雨,先把晒在阳台的衣服收好了,然后穿上雨衣,带着雨伞,再出门。这就是悲观锁。

二、轻量级锁与重量级锁

轻量级锁,加锁解锁,过程更快更高效。

重量级锁,加锁解锁,过程更慢更低效。

这与悲观乐观,虽然并不是一回事,但确实有一定的重合,

一个乐观锁很有可能也是一个轻量级锁(不绝对)

一个悲观锁很有可能也是一个重量级锁(不绝对)

三、自旋锁与挂起等待锁

自旋锁是轻量级锁的一种经典实现。

挂起等待锁是重量级锁的一种典型实现。

举个例子:

我跟心爱的姑娘表白,表白失败。(加锁失败)

自旋锁:锲而不舍追她,每天送早餐送花。等哪一天,她跟男朋友分手了。这时候,我有很大的机会追上她,加上锁。一旦锁被释放,就能第一时间拿到锁,速度更快。但是每天都要围着她转,相当于忙等,会消耗cpu资源。

挂起等待锁:被拒绝之后,我就好好学习,不联系她了。等哪天,她突然想起来我了,问要不要在一起,然后加上锁。如果锁被释放,不能第一时间拿到锁。可能需要过很长时间才能加上锁。不过,在这个过程,我是在忙自己的事情的。

自旋锁:通常是纯用户态的,不需要经过内核态。(时间相对更短)

挂起等待锁:通过内核的机制来实现挂起等待。

针对上述三组策略,synchronized这把锁属于哪种呢?

synchronized既属于悲观锁,也是乐观锁,既是轻量级锁,也是重量级锁,轻量级锁部分基于自旋锁实现,重量级锁部分基于挂起等待锁实现。

synchronized会根据当前锁竞争的激烈程度,自适应。

如果锁冲突不激烈,以轻量级锁/乐观锁的状态运行。

如果锁冲突激烈,以重量级锁/悲观锁的状态运行。

四、互斥锁与读写锁

synchronized,是互斥锁。除此之外,还有一种锁叫读写锁,能够把读与写两种加锁区分开。

synchronized只有两个操作:

1.进入代码块,加锁。

2.出了代码块,解锁。

读写锁:

1.给读加锁

2.给写加锁

3.解锁

如果多个线程读同一个变量,不会涉及到线程安全问题。

读写锁中,约定:

1.读锁与读锁之间,不会有锁竞争,不会产生阻塞等待。

2.写锁与写锁之间,有锁竞争。

3.读锁与写锁之间,也有锁竞争。

读写锁更适合于一写多读的情况。

标准库提供了两个专门的读写锁(读锁是一个类,写锁是一个类)Reentrant        可重入的

五、可重入锁与不可重入锁

如果一个锁,在一个线程中,连续对该锁加锁两次,不死锁,就叫可重入锁,如果死锁了,就叫不可重入锁。

举个例子,我跟帅哥表白,帅哥答应当我男朋友了。

多线程进阶(1)_第2张图片

 这时,我再用小号加那个帅哥,说:“你当我男朋友吧。”这时,他不同意,说我已经有女朋友了。这时,就是不可重入锁。

如果,帅哥发现这个小号也是我,又答应了。这就是可重入锁。

Object locker=new Object();
synchronized(locker){
    synchronized(locker){
    }   
}

形如这个代码,就是 加锁两次的情况。第二次尝试加锁,需要等待第一个锁释放。第一个锁释放,需要等待第二个锁加锁成功。这就死锁了。

关于死锁的情况:

1.一个线程,一把锁,可重入锁没事,不可重入锁死锁。

2.两个线程两把锁,即使是可重入锁,也会死锁。

比如,我给猫猫喂猫条,这时猫猫才肯让我随便rua。那我又想着rua开心了,再给猫猫喂猫条。这时候,我和猫猫就是两个线程。猫条和rua几下就是两把锁。

如果僵持不下,我rua不到猫猫,猫猫吃不到美味的猫条。

多线程进阶(1)_第3张图片

 

如果是N个线程,M把锁,线程数量和锁数量更多了,就更容易死锁了,就比如哲学家就餐问题。

死锁的四个必要条件:

1.互斥使用,一个线程拿到一把锁之后,另一个线程不能使用。(锁的基本特点)

2.不可抢占。一个线程拿到锁,只能自己主动释放,不能是被其他线程强行占有。(锁的基本特点)

3.请求和保持。“吃着碗里的,看着锅里的。”(代码的特点)

4.循环等待。逻辑依赖循环。“家钥匙锁车里了,车钥匙锁家里了。”(代码的特点)

怎么避免死锁?

约定获取多把锁,先获取锁小的编号,后获取锁大的编号。

只要约定了加锁顺序,循环等待自然破除,死锁不会形成了。

六、公平锁与非公平锁

约定先来后到,就是公平锁。不遵守先来后到,就是非公平锁。

比如去食堂打饭吃。

多线程进阶(1)_第4张图片

如果,一群人在食堂等饭点买饭。A等了一个小时,B等了半小时,C刚来。这时候,A说:“我最早过来等的,我先吃。”等最久,最先吃上饭,那就是公平锁。

如果,到了饭点,A、B、C有同等概率可以第一时间打上饭,那就是非公平锁。 

系统对于线程的调度是随机的。自带的synchronized这个锁是非公平的。要想实现公平锁,需要在synchronized的基础上,加上一个队列来记录这些加锁线程的顺序。

synchronized特点:

1.既是乐观锁,也是悲观锁

2.既是轻量级锁,也是重量级锁

3.轻量级锁基于自旋实现,重量级锁基于挂起等待实现

4.不是读写锁

5.是可重入锁

6.是非公平锁

 

你可能感兴趣的:(JavaEE,java)