JUC(5): 各种锁重点汇总

六、各种锁

1、公平锁、非公平锁(默认)

  • 公平锁:非常公平,不能插队,必须先来后到
  • 非公平锁:非常不公平,可以插队,(默认都是非公平锁)
public ReentrantLock() {
    sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

2、可重入锁

Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁

可重入锁

获取外部锁的同时,内部锁也会被获取!

class Phone2{
    Lock lock = new ReentrantLock();
    public void sms(){
        lock.lock(); //细节问题!两把锁,进到call(),也会拿到一把锁!
        //lock.lock(); 死锁
        //lock锁必须配对
        try{
            System.out.println(Thread.currentThread().getName() + " sms");
            call();
        }catch(Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }

    }

    public void call(){
        lock.lock();
        try{
            System.out.println(Thread.currentThread().getName() + " call");
        }catch(Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

3、自旋锁

//自旋锁
public class SpinLockDemo {
    AtomicReference<Thread> atomicReference = new AtomicReference<>();
    //加锁
    public void myLock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"==> myLock");
        //自旋锁
        while(!atomicReference.compareAndSet(null,thread)){		//获取不到锁就自旋等待!!!

        }
    }

    //解锁
    public void myUnlock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"==> myUnlock");
        atomicReference.compareAndSet(thread,null);
    }
}

4、死锁

死锁是什么? 两个线程持有自己的锁,并试图获取对方的锁!

在这里插入图片描述

	public static void main(String[] args) {
     Object object1 = new Object();
     Object object2 = new Object();

     new Thread(()->{
         synchronized (object1){
             System.out.println(Thread.currentThread().getName()+"\t 持有a锁,想获得b锁");
             try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}//使得线程b也启动
             synchronized (object2){
                 System.out.println(Thread.currentThread().getName()+"\t 成功获得b锁");
             }
         }
     },"A").start();

     new Thread(()->{
         synchronized (object2){
             System.out.println(Thread.currentThread().getName()+"\t 持有b锁,想获得a锁");
             synchronized (object1){
                 System.out.println(Thread.currentThread().getName()+"\t 成功获得a锁");
             }
         }
     },"B").start();
 }
  • 四要素:互斥、占有等待、循环等待、不可抢占

如何排除死锁,解决问题

1 使用 **jps -l **定位进程号

img

2 使用jstack 进程号查看进程信息

jstack

5、乐观锁,悲观锁

特点:

  • 悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。(适合写多的场景)

  • 乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作。(适合读多的场景)

实现方式:

  • 悲观锁的实现方式

    • synchronized关键字
    • Lock的实现类都是悲观锁
  • 乐观锁的实现方式

    • 最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的。
    • 版本号机制Version。(只要有人提交了就会修改版本号,可以解决ABA问题)
      • ABA问题:再CAS中想读取一个值A,想把值A变为C,不能保证读取时的A就是赋值时的A,中间可能有个线程将A变为B再变为A。
      • 解决方法:Juc包提供了一个AtomicStampedReference,原子更新带有版本号的引用类型,通过控制版本值的变化来解决ABA问题。

6、syn锁 和 lock 锁

底层实现方式:

  • synchronized同步代码块,实现使用的是moniterentermoniterexit指令(moniterexit可能有两个)。

    • Monitor(监视器),也就是我们平时说的锁。监视器锁
    • 任何一个对象都可以成为一个锁,每个对象天生都带着一个对象监视器
  • Synchronized锁对象是存在哪里的呢?答案是存在锁对象的对象头的MarkWord中。

  • lock 锁,加锁lock,解锁unlock成对

7、syn锁升级

(1) 偏向锁

JDK6中引入的锁优化,通常锁总是由同一线程多次获得,偏向锁会偏向于第一个获得它的线程:

  • 如果在运行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要同步。
  • 如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会消除它身上的偏向锁,将锁升级为标准的轻量级锁。
(2)轻量级锁

轻量级锁考虑的是竞争锁对象的线程不多,而且线程持有锁的时间也不长的情景。因为阻塞线程需要CPU从用户态转到内核态,代价较大,如果刚刚阻塞不久这个锁就被释放了,那这个代价就有点得不偿失了,因此这个时候就干脆不阻塞这个线程,让它自旋这等待锁释放。

  • 如果在线程1复制对象头的同时(在线程1CAS之前),线程2也准备获取锁,复制了对象头到线程2的锁记录空间中,但是在线程2CAS的时候,发现线程1已经把对象头换了,线程2的CAS失败,那么线程2就尝试使用自旋锁来等待线程1释放锁
  • 自旋的时间太长也不行会消耗CPU资源,轻量级锁在以下情况膨胀为重量级锁:
    • 自旋次数到了线程1还没有释放锁
    • 线程1还在执行,线程2还在自旋等待,这时又有一个线程3过来竞争这个锁对象
(3)重量级锁

重量级锁Synchronized,它可以把任意一个非NULL的对象当作锁。

  • 作用于方法时,锁住的是对象的实例(this);
  • 当作用于静态方法时,锁住的是Class实例,又因为Class的相关数据存储在永久带PermGen(jdk1.8则是metaspace),永久带是全局共享的,因此静态方法锁相当于类的一个全局锁,会锁所有调用该方法的线程;
  • synchronized作用于一个对象实例时,锁住的是所有以该对象为锁的代码块
  • Synchronized锁对象是存在哪里的呢?答案是存在锁对象的对象头的MarkWord中。

  • synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。

Synchronized锁对象是存在哪里的呢?答案是存在锁对象的对象头的MarkWord中。

img

8、锁消除与锁粗化

  • 锁消除:给每个线程new一个锁,相当于没起作用。
  • 锁粗化:大部分情况下我们是要让锁的粒度最小化,锁的粗化则是要增大锁的粒度;在以下场景下需要粗化锁的粒度:
    假如有一个循环,循环内的操作需要加锁,我们应该把锁放到循环外面,否则每次进出循环,都进出一次临界区,效率是非常差的;

9、读写锁

ReentrantReadWriteLock 是读写锁,和ReentrantLock会有所不同,

  • 对于读多写少的场景使用ReentrantReadWriteLock 性能会比ReentrantLock高出不少。在多线程读时互不影响,不像ReentrantLock即使是多线程读也需要每个线程获取锁。

  • 不过任何一个线程在写的时候就和ReentrantLock类似,其他线程无论读还是写都必须获取锁。

  • 需要注意的是同一个线程可以拥有 writeLock 与 readLock (但必须先获取 writeLock 再获取 readLock, 反过来进行获取会导致死锁)

问题:读写互斥,读读共享,但是读没完成的时候其他线程写锁无法获得!

锁降级

锁降级 : 是指保持住当前的写锁(已拥有),再获取读锁,随后释放写锁的过程。(写锁能降级为读锁,读锁不能升级为写锁!

image.png

好处:使用锁降级可以在释放写锁前获取读锁,这样其他的线程就只能获取读锁,对这个数据进行读取,但是不能获取写锁进行修改,只有当前线程释放了读锁之后才可以进行修改。

总结:同一个线程自己持有写锁时再拿读锁,相当于重入,完事了再释放写锁,防止其他线程乱修改刚写入的数据。(缓存一致性)

10、StampedLock邮戳锁

StampedLock是对ReentrantReadWriteLock读写锁的优化,在其基础上增加了乐观读—tryOptimisticRead() 方法

总结:读的过程中也允许获取写锁!

image-20210913222746190
锁饥饿问题:

虽然ReentrantReadWriteLock实现了读写分离提升了速度,但是一旦读操作比较多的时候,想要获取写锁就变得比较困难了:

  • 假如当前1000个线程,999个读,1个写,有可能999个读取线程长时间抢到了锁,那1个写线程就悲剧了
  • 因为当前有可能会一直存在读锁,而无法获得写锁,根本没机会写。
StampedLock的特点
  • 所有获取锁的方法,都返回一个邮戳(Stamp),Stamp为零表示获取失败,其余都表示成功;
  • 所有释放锁的方法,都需要一个邮戳(Stamp),这个Stamp必须是和成功获取锁时得到的Stamp一致;
  • StampedLock是不可重入的,危险(如果一个线程已经持有了写锁,再去获取写锁的话就会造成死锁)

StampedLock有三种访问模式

  • Reading(读模式readLock()):功能和ReentrantReadWriteLock的读锁类似
  • Writing(写模式writeLock()):功能和ReentrantReadWriteLock的写锁类似
  • Optimistic reading(乐观读模式tryOptimisticRead()),支持读写并发,很乐观认为读取时没人修改。若读的过程被修改了(通过validate(stamp)方法获得通知),再进行重新读等操作。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值