Java中的一些相关锁

目录

1. 乐观锁 VS 悲观锁

2. 自旋锁 VS 适应性自旋锁

3. 无锁 VS 偏向锁 VS 轻量级锁 VS 重量级锁

4. 公平锁 VS 非公平锁

5. 可重入锁 VS 非可重入锁

6. 独享锁(排他锁) VS 共享锁


1. 乐观锁 VS 悲观锁

悲观锁:适合写操作多的场景

认为在使用数据的时候一定有别的线程来修改数据,所以会在取数据的时候先加锁,确保数据不会被别的线程修改

乐观锁:适合读操作多的场景

认为在使用数据时不会有别的线程来修改数据,所以不会添加锁,只是在更新数据时去判断是否有其他线程更新了这个数据,如果没有更新,当前线程将自己修改的数据成功写入,如果已经被更新,会根据不同的实现方式执行不同的操作(报错

2. 自旋锁 VS 适应性自旋锁

        当某个线程尝试获取同步资源的锁失败,资源被占用时,使用自旋锁会不放弃CPU时间片,通过自旋等待锁的释放,等待一段时间后,通过自旋操作减少CPU切换以及恢复线程导致的消耗。而非自旋锁则会去通过CPU切换状态,使当前线程休眠,CPU切换线程执行其他操作,当占用同步资源的线程释放了锁,恢复线程,再次去尝试获取锁。

缺点:不能代替阻塞。自旋等待时间虽然避免了线程切换的开销,但是会占用处理器时间,如果锁占用时间很短,自旋等待的效果会非常好,反之就会白白浪费处理器资源(所以默认自旋次数为10次)

自适应自旋锁:

​        自旋的时间不在固定,而是由前一次在同一个锁上的 自旋时间及锁拥有者的状态来决定,如果在同一个锁对象上,自选等待刚刚成功获取过锁,并且持有的锁正在运行中,JVM就会认为该锁自旋获取锁的可能性很大,就会自动增加等待时间。相反,如果某个锁通过自旋很少就成功获取了锁,那么以后获取将可能省略掉自旋过程,以避免浪费处理器资源。

3. 无锁 VS 偏向锁 VS 轻量级锁 VS 重量级锁

        第一部分用于存储对象自身的运行时数据,HashCodeGC Age锁标记位是否为偏向锁等。一般为32位或者64位(视操作系统位数定)。官方称之为Mark Word,它是实现轻量级锁和偏向锁的关键。 另外一部分存储的是指向方法区对象类型数据的指针(Klass Point),如果对象是数组的话,还会有一个额外的部分用于存储数据的长度

轻量级锁:可以减少重量级锁对线程的阻塞带来的线程开销

轻量级锁加锁:

        在线程执行同步块之前,JVM会先在当前线程的栈帧中创建一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝(JVM会将对象头中的Mark Word拷贝到锁记录中,

 

        如果当前对象没有被锁定,那么锁标志位为01状态,JVM在执行当前线程时,首先会在当前线程栈帧中创建锁记录Lock Record的空间用于存储锁对象目前的Mark Word的拷贝。

        然后,虚拟机使用CAS操作将标记字段Mark Word拷贝到锁记录中,并且将Mark Word更新为指向Lock Record的指针。如果更新成功了,那么这个线程就有用了该对象的锁,并且对象Mark Word的锁标志位更新为(Mark Word中最后的2bit)00,即表示此对象处于轻量级锁定状态。

 

如果更新失败:

        JVM会检查当前的Mark Word中是否存在指向当前线程的栈帧的指针,如果有,说明该锁已经被获取,可以直接调用。如果没有,则说明该锁被其他线程抢占了,如果有两条以上的线程竞争同一个锁,那轻量级锁就不再有效,直接膨胀位重量级锁,没有获得锁的线程会被阻塞。此时,锁的标志位为10.Mark Word中存储的时指向重量级锁的指针。

轻量级解锁:

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

偏向锁:

        当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和推出同步块时不需要进行CAS操作来加锁和解锁。

 

对比

优点缺点使用场景
偏向锁加锁和解锁不需要CAS操作,没有额外的性能消耗,和执行非同步方法相比仅存在纳秒级的差距如果线程间存在锁竞争,会带来额外的锁撤销的消耗适用于只有一个线程访问同步块的场景
轻量级锁竞争的线程不会阻塞,提高了响应速度如线程成始终得不到锁竞争的线程,使用自旋会消耗CPU性能追求响应时间,同步块执行速度非常快
重量级锁线程竞争不适用自旋,不会消耗CPU线程阻塞,响应时间缓慢,在多线程下,频繁的获取释放锁,会带来巨大的性能消耗追求吞吐量,同步块执行速度较长

总结: 偏向锁通过对比Mark Word解决加锁问题,避免执行CAS操作。而轻量级锁是通过用CAS操作和自旋来解决加锁问题,避免线程阻塞和唤醒而影响性能。重量级锁是将除了拥有锁的线程以外的线程都阻塞。

4. 公平锁 VS 非公平锁

公平锁:

指多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁。

优点:等待锁的线程不会饿死。

缺点:整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大。

非公平锁:

多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待。

优点:可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程。

缺点:处于等待队列中的线程可能会饿死,或者等很久才会获得锁。

5. 可重入锁 VS 非可重入锁

可重入锁:

        又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),不会因为之前已经获取过还没释放而阻塞。Java中ReentrantLock和synchronized都是可重入锁。

 

 

当线程尝试获取锁时,可重入锁先尝试获取并更新status值,如果status == 0表示没有其他线程在执行同步代码,则把status置为1,当前线程开始执行。如果status != 0,则判断当前线程是否是获取到这个锁的线程,如果是的话执行status+1,且当前线程可以再次获取锁。而非可重入锁是直接去获取并尝试更新当前status的值,如果status != 0的话会导致其获取锁失败,当前线程阻塞。

释放锁时,可重入锁同样先获取当前status的值,在当前线程是持有锁的线程的前提下。如果status-1 == 0,则表示当前线程所有重复获取锁的操作都已经执行完毕,然后该线程才会真正释放锁。而非可重入锁则是在确定当前线程是持有锁的线程之后,直接将status置为0,将锁释放。

6. 独享锁(排他锁) VS 共享锁

独享锁也叫排他锁:

指该锁一次只能被一个线程所持有。如果线程T对数据A加上排它锁后,则其他线程不能再对A加任何类型的锁。获得排它锁的线程即能读数据又能修改数据。JDK中的synchronized和JUC中Lock的实现类就是互斥锁。

共享锁

指该锁可被多个线程所持有。如果线程T对数据A加上共享锁后,则其他线程只能对A再加共享锁,不能加排它锁。获得共享锁的线程只能读数据,不能修改数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值