【多线程与并发】锁的种类以及锁的优化

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/Soldier49Zed/article/details/101425829

多线程中锁的种类

 

一、可重入锁

ReentrantLock和synchronized都是可重入锁

如果当前线程已经获得了某个监视器对象所持有的锁,那么该线程在该方法中调用另外一个同步方法也同样持有该锁。

比如:

public synchronized void test(){
    xxxxxx;
    test2();
}

public synchronized void test2(){
    yyyyyy;
}

在上面代码段中,执行test方法需要获得对象作为监视器的对象锁,但方法中又调用了test2的同步方法。

如果锁时具有可重入性的话,那么线程在调用test2时并不需要再次获得当前对象的锁,可以直接进入test2方法进行操作。

可重入锁最大的作用是避免死锁。如果锁时不具有可重入性的话,那么该线程在调用test2前会等待当前对象锁的释放,实际上该对象锁已被当前线程所持有,不可能再次获得,那么线程在调用同步方法、含有锁的方法时就会产生死锁

二、可中断锁

顾名思义,就是可以相应中断的锁。

在Java中,synchronized是不可中断锁,而Lock是可中断锁lockInterruptibly()的用法已经体现了Lock的可中断性。如果某一线程A正在执行锁的中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中断它,这就是可中断锁。

三、公平锁

在Java中,synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。而对于ReentrantLock和ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。

公平锁即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该锁,这种就是公平锁。

四、读写锁

正因为有了读写锁,才使得多个线程之间的读操作不会发生冲突。ReentrantLock就是读写锁,它是一个接口,ReentrantReadWrite实现了这个接口。可以通过readLock()获取读锁,通过writeLock获取写锁()。

锁优化

一、自旋锁

为了让线程实现等待,然线程执行一个忙循环(自旋)。需要物理机器上有一个以上的处理器。自旋等待虽然避免了线程切换的开销,但是它是要占用处理器时间的,所以如果锁被占用的时间很多,自旋等待的效果就会非常好,反之自旋的线程只会拜拜消耗处理器资源。自旋次数的默认值是10次,可以使用参数-XX:PreBlockSpin来更改。

自适应自旋锁:自旋的时间不在固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定的。

二、锁清除

指虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行清除(逃逸技术分析:在堆上的所有数据都不会逃逸出去被其他线程访问到,可以把他们当做栈上数据对待)。

三、锁粗化

如果虚拟机探测到有一串零碎的操作都对同一个对象加锁,将会把加锁同步的范围扩展到整个操作序列的外部。

四、轻量级锁

在代码中进入同步块时,如果此同步对象没有被锁定,虚拟机首先将在当前线程的桟帧中建立一个名为锁记录(Lock Record)的空间,用于存储所对象目前的Mark Word的拷贝。然后虚拟机将使用CAS操作尝试将对象的Mark Word更新为执行Lock Record的指针。如果成功,那么这个线程就拥有了该对象的锁。如果更新操作失败,虚拟机首先会检查对象的Mark Work是否指向当前线程的桟帧,如果是就说明当前线程已经拥有了这个对象的锁,否则说明这个对象已经被其他线程抢占。如果有两条以上的线程争用同一个锁,那轻量级锁就不再有效,要膨胀为重量级锁。

解锁过程:如果对象的Mark Word仍然指向着线程的锁记录,那就用CAS操作把对象当前的Mark Word和线程中复制的Displaced Mark Word替换回来,如果替换成功,整个过程就会完成。如果失败,说明有其他线程尝试过获取该锁,那就要在释放锁的同时,唤醒被挂起的线程。

轻量级锁的依据:对于绝大部分的锁,在整个同步周期内都是不存在竞争的。
传统锁(重量级锁)使用操作系统互斥量来实现。

HotSpot虚拟机的对象的内存布局:对象头(Object Header)分为两部分信息吗,第一部分(Mark Word)用于存储对象自身的运行时数据,另一个部分用于存储指向方法区对象数据类型的指针,如果是数组的话,还会由一个额外的部分用于存储数组的长度。

32位HotSpot虚拟机中对象被锁定的状态下,Mark Word的32个Bits空间中25位用于存储对象哈希码,4位存储对象分代 年龄,2位存储锁标志位,1位固定位0.

HotSpot虚拟机对象头Mark Word

存储内容 标志位 状态
对象哈希码、对象、分代年龄 01 未锁定
指向锁记录的指针 00 轻量级锁定
指向重量级锁的指针 10 膨胀(重量级锁)

                      空,不记录信息

11 GC标记
偏向线程ID,偏向时间戳、对象分代年龄 01 可偏向

五、偏向锁

目的是消除在无竞争情况下的同步原语,进一步提高程序的运行性能。锁会偏向第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他线程获取,则持有锁的线程将永远不会需要在进行同步。

当锁第一次被线程获取的时候,虚拟机将会把对象头中的标记位设为01,同时使用CAS操作把获取到这个锁的线程ID记录在对象的Mark Word之中,如果成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,都可以不进行任何同步操作。

当有另一个线程去尝试获取这个锁时,偏向模式就宣告结束。根据锁对象目前是否处于被锁定的状态,撤销偏向后恢复到未锁定或轻量级锁定状态。

猜你喜欢

转载自blog.csdn.net/Soldier49Zed/article/details/101425829