Java“锁”事

目录


前言

        Java提供了多种类的锁,每种锁因其特性的不同,在适当的场景下能够展现出非常高的效率 

1、乐观锁 VS 悲观锁

               

        对于同一个数据的并发操作,悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。Java中,synchronized关键字和Lock的实现类都是悲观锁。

        而乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。乐观锁在Java中是通过使用无锁编程来实现,最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的。

根据从上面的概念描述我们可以发现:

        悲观锁:

                ①适用于写操作多的场景,加锁的特点可以保证写操作的正确性

                ②因为加锁的缘故会导致阻塞,降低吞吐量,可能引发死锁

        乐观锁:

                ①适用于多操作多的场景,不加锁的特点能够使读操作性能大幅提升

                ②CAS的方式会出现:ABA问题,循环时间长开销大等问题

2、自旋锁 VS 自适应自旋锁

        阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态转换需要耗费处理器时间。如果同步代码块中的内容过于简单,状态转换消耗的时间有可能比用户代码执行的时间还要长。

        在许多场景中,同步资源的锁定时间很短,为了这一小段时间去切换线程,线程挂起和恢复现场的花费可能会让系统得不偿失。我们就可以让后面那个请求锁的线程不放弃CPU的执行时间,看看持有锁的线程是否很快就会释放锁。而为了让当前线程“稍等一下”,我们需让当前线程进行自旋,如果在自旋完成后前面锁定同步资源的线程已经释放了锁,那么当前线程就可以不必阻塞而是直接获取同步资源,从而避免切换线程的开销。这就是自旋锁。

        而自适应自旋锁意味着自旋的时间(次数)不再固定,就是在自旋锁的基础上,​根据历史自旋结果动态调整自旋策略:

        ​        成功经验:若历史自旋后成功获取锁,下次可能延长自旋时间

​                失败经验:若历史多次自旋失败,则减少自旋次数或直接放弃自旋,转为阻塞

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

 Synchronized底层实现原理:


①锁对象在加了synchronized之后,对象头中的Mark Word中就存了一个Monitor的地址指针。

②当一个线程获取到锁之后,Monitor中的Owner属性指向了该获得锁的线程。

③当锁还没释放时,其他的线程来获得锁,就会进入EntryList等待队列中等待。

④当线程2释放锁之后,通知Monitor中的等待队列中的线程,通过一些策略进行选择一个线程拿出来并且获得锁,把Owner指向该获得锁的线程。

⑤当一个线程获取到锁后,发现自身任不满足一些条件,就会调用wait()方法进入Wait_Set中等待(此时线程是进入了Waiting状态),当另一个线程获得锁并且把条件送过来了(即调用notify()唤醒Wait_Set方法中的一个线程或者使用notifyAll()唤醒所有的线程),然后线程就可以再次进入EntryList中去竞争获得锁。

锁升级的过程:

        ①无锁:无锁没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功。

        ②偏向锁:指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价,并且会在对象头里存储锁偏向的线程ID,在线程进入和退出同步块时不再通过CAS操作来加锁和解锁,而是检测Mark Word里是否存储着指向当前线程的偏向锁

        ③轻量级锁:当有多个线程对资源进行竞争,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能

        ④重量级锁:但是当自旋超过一定的次数(默认是10次),或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁升级为重量级锁,重量级锁除了获取到资源的线程可以操作外,其他的线程都会进行阻塞等待

4、公平锁 VS 非公平锁

        公平锁是指多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁。公平锁的优点是等待锁的线程不会饿死。缺点是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞

        非公平锁是多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待。但如果此时锁刚好可用,那么这个线程可以无需阻塞直接获取到锁。非公平锁的优点是可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程。缺点是处于等待队列中的线程可能会饿死,或者等很久才会获得锁。

        以AQS为例,其维护了一个等待队列(CLH队列),当是公平锁FairSync的时候,线程来竞争锁的时候,会先判断同步队列中是否为空,如果为空则获取到锁,否则就进入队列中等待。当如果是非公平锁NonfairSync的时候,线程来竞争锁的时候会直接尝试获取锁,如果竞争失败则进入队列。

5、可重入锁 VS 非可重入锁

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

         还是以AQS为例,其维护了一个同步状态Status用于记录重入的次数。

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

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

6、共享锁 VS 独享锁

        

         独享锁是指该锁一次只能被一个线程所持有。如果线程T对数据A加上排它锁后,则其他线程不能再对A加任何类型的锁。获得排它锁的线程即能读数据又能修改数据。

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

         以AQS为例,当是独享锁的时候,Status为0/1,表示是否占有锁(可重入锁则表示重入次数),当是共享锁的时候,Status则是记录持有锁的数量。

        但是这边再以ReentrantReadWriteLock读写锁为例,它允许多个线程同时读取共享资源,但在写入时独占资源,从而在高并发读多写少的场景中提高性能。

        我们看到ReentrantReadWriteLock有两把锁:ReadLock和WriteLock,由词知意,一个读锁一个写锁,合称“读写锁” 。有两把锁我们只有一个Status怎么表示出各所代表的意思呢?于是将state变量“按位切割”切分成了两个部分,高16位表示读锁状态(读锁个数),低16位表示写锁状态(写锁个数)

1. 读锁的实现

  • 获取读锁
    • 检查是否有线程持有写锁(state & 0xFFFF != 0),若有则等待。
    • 若无写锁,则通过 CAS 增加读锁的持有次数(state += 1 << 16)。
  • 释放读锁
    • 通过 CAS 减少读锁的持有次数(state -= 1 << 16)。

2. 写锁的实现

  • 获取写锁
    • 检查是否有线程持有读锁或写锁(state != 0),若有则等待。
    • 若无其他锁,则通过 CAS 设置写锁的持有次数(state += 1)。
  • 释放写锁
    • 通过 CAS 减少写锁的持有次数(state -= 1)。

 ​3. 锁降级

        锁降级是指 ​在持有写锁的同时获取读锁,然后释放写锁,从而将写锁降级为读锁。锁降级的目的是在保证数据一致性的同时,允许其他线程读取数据。        

内容参考:  美团技术团队的高质量文章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值