Java锁相关问题

1.Java中有那些锁,区别是什么

1.java中的锁主要分为乐观锁和悲观锁,其中CAS是乐观锁,synchronized、Lock是悲观锁。
2.乐观锁认为我在操作的时候不会有其余的线程竞争,如果有竞争就自旋等待(CAS)或异常结束(数据库多版本机制)
3.悲观锁认为我操作的时候一定会有其余的线程竞争,如果我未竞争到锁就结束或阻塞等待(tryLock)。

 

2.CAS实现原理

1.CAS是乐观锁思想实现的,主要解决线程并发问题,其原理是新值、旧值、预期值的比较与交换,当且仅当旧址和预期值相同的情况才会把旧址改为新值,JAVA中是基于unsafe类提供的方法实现的,CAS是CPU基本的原子操作 。
2.java中Atomic类的方法是基于CAS实现的,MySql数据的多版本控制也是基于CAS实现的。
3.CAS存在ABA问题。当共享变量经历A→B→A的值变化时,CAS仅检查最终值是否与预期值相同(均为A),而忽略中间被修改为B的过程,导致逻辑误判。JAVA中提供了解决ABA的方案,可以使用AtomicstampedReference<V>来解决。

 

3.synchronized实现原理和锁升级

v2-2141a3ddf65d446898152fab397cace4_r

 

1.synchronized是一共互斥锁,只能有个线程持有这把锁,synchronized中有个owner属性,owner存放的是持有当前锁的线程,当owner为null的时候其余线程可以通过CAS进行owner修改,如果修改成功则当前线程持有锁,如果修改失败则进行几次CAS,没有拿到就信息park挂起线程。

4.synchronized锁升级过程。
1.无锁:当前对象没有被任何锁资源占用&&在JDK1.8后会有个4S的偏向锁延迟,JVM刚刚启动的4S中,不存在偏向锁状态。
2.偏向锁:

1.撇去4S的延迟,刚创建出来的都行都是偏向锁(匿名偏向)
2.当前对象被作为锁资源占用,且指向了某一线程此时就是偏向锁(普通偏向)
3.如果一个线程执行任务后,反复的获取同一把锁,这个时候偏向锁的优势就体现出来了,无需进行CAS操作,直接比较线程指向是否相同,相同则直接获取锁。

 

3.轻量级锁:在偏向锁的状态下,锁资源出现了竞争,就会升级为轻量锁。轻量锁状态下会进行几次CAS操作将owner(monitor中的owner)修改为自己的线程,默认是10次,这里的CAS操作走的自适应自旋锁。
4.重量级锁:在轻量级锁状态下,轻量级锁竞争失败,就会升级为重量级锁,重量级锁也会进行几次CAS操作将owner(monitor中的owner)修改为自己的线程,如果成功就直接拿锁执行任务,如果失败则挂起线程,等起锁释放后被唤醒。

    private void synchronizedTest1(){
        //1.无锁状态    没有被任何锁资源占用且在JVM启动4S内
        Object o = new Object();

        synchronized (o){
            //2.轻量级锁
        }
    }


    private void synchronizedTest2(){
        try {
            //模拟JVM启动耗时
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //3.偏向锁的匿名偏向
        Object o = new Object();
        synchronized (o){
            //4.偏向锁的普通偏向
        }
    }
}

 

4.Synchronized和ReentrantLock的区别

1.Synchronized是java内置关键字,是JVM基于内置关键字monitor实现,Synchronized是公平锁,内置自动加锁和释放锁,是可重入锁,发生异常时自动释放锁。
2.ReentrantLock是API级别的,基于AQS(AbstractQueuedSynchronized实现。ReentrantLock根据参数属性控制是公平还所非公平锁,需显示调用lock(),unLock()进行加锁和释放锁,使用ReenTrantLock锁进行线程间的通信需要依赖Condition进行,也是可重入锁,可中断和尝试获取锁,发生异常时需要在finally内释放锁。

 

5.AQS是什么

v2-eca00ffe477259faefc4c28dde4034f7_r

 

1.AQS(AbstractQueuedSynchronized)其实JUC下一个基础类,没有具体是实现并发功能,但是大多数JUC包下的类都继承了AQS,如;ReentrantLock,CountDownLatch、线程池等等、
2.AQS核心的是同步状态管理和线程队列机制

1.AQS中有一个被volitail修饰的state,当state = 0时表示没有线程持有锁资源,当state>0时表现有线程持有资源。
2.Node节点组成的双向队列:当出现锁竞争的时候,未竞争到锁的线程会作为node节点存放到双向队列中。
3.Node对象节点组成的单向链表:当线程持有锁的时候执行了await命令,线程会释放锁,此时当前线程会作为node节点存放到单线链表中,当接受到signal时会从单向链表转移到双向队列参与锁竞争。

 

6.公平锁和非公平锁的区别

v2-1216f0ef40df4ca02005f03df7b4ae76_r

 

1.公平锁支持先进先出,先到的线程先获取锁。
2.非公平锁允许线程执行CAS竞争,谁拿到锁谁就去执行任务,其余的就排队。
3.synchronized是公平锁的代表,ReenTrantLocak可通过属性设置公平非公平。
4.ReentrantLock中有两个方法决定实现公平和非公平之分。

lock:
非公平锁:线程直接执行CAS尝试将state从0修改为1,如果修改成功拿锁走人,修改失败调用tryAcquire。
公平锁:执行执行tryAcqurie。

tryacquire方法:
非公平锁:线程直接执行CAS尝试将state从0修改为1,如果修改成功拿锁走人,失败了就排队。
公平锁:如果state=0且有线程排队则直接给最先进来的线程。

 

7.系统中分布式锁是如何使用的,分布式锁有几种实现方式

1350514-20190625021257274-823428432

 

1.在分布式系统/微服务系统的情况多线程并发竞争同一资源这个时候就需要使用分布式锁。
2.分布式锁有redis、redission,lua脚本、zookeeper
3.redis分布式锁可以使用setnx来进行加锁和释放锁,setnx命令保证我对某个key设置值之后其余命令无法在设置,但是加锁后执行业务逻辑时程序停止或重启了,就会导致当前key长生不老,就会导致次业务因无法获取锁而崩溃。
4.可以使用setnx+expire来做分布锁,这样锁就有过期时间了,在执行业务逻辑时程序崩溃也会释放锁,当时setnx+expire是非原子的,两者之间也存在程序崩溃后锁无法释放的问题。
5.可以使用redis的set(key,value,nx,ex,时间单位)来做分布式做,redis层面保证命令是原子的。但是代码书写时要把加锁操作放到try()cathc()finally()外,避免因当前线程加锁失败删除掉别的线程锁。
6.可以使用redission提高的命令做锁,redission有自动续费看门狗机制、集群之间容错机制,支持可重入机制。

94fa389b20754bc4ad3a1e561ed2693e

 

8. CountDownLatch 和CyclicBarrier和Semaphore 的作用和区别

1.CountDownLatch主要作用是主线程等待所有被调用线程结束,才会执行,每次调用countDown()方法计算器进行减1,当结果减为0时,主线程执行。CountDownLatch内部是基于CAS操作执行的,不可重置
2.CyclicBarrier主要作用是线程直接互相等待,当最慢的线程结束才会一块进行后续操作。每次调用await()方法时计算器进行加1,当结果加到执行数值,所有线程才会执行await()后面的逻辑。 CyclicBarrier是基于reenTrantLock原理实现的,可重置。
3.Semaphore主要作用类似线程之间的凭证,当线程拿到屏障后才会执行,如果没有拿到就需要等待,当别人释放后自己取抢。Semaphore内部是基于CAS操作执行的 计数时可增可见、可动态控制许可数量。
示例:
CountDownLatch相当于在田径接力比赛时所有人员到齐了,活动才开始。
Semaphore相等于运动员手里的接力棒,只有下一个队员拿到接力棒后才可以跑。
CyclicBarrier相当于所有的接力运动员都跑完了,大家一块去吃烧烤。

 

9.锁和@Transactional一起使用导致事务失败是为什么

1.@Transactional有点类似AOP的环绕通知,当线程读取到方法的@Transactional命令会在进入方法前开启事务,当方法结束后才会进行事务提交或回滚。
2.如果@Transactional和分布式锁作用在一个方法,就会出现分布式锁释放了,但是事务没有提交,后来的线程获取到锁对数据进行查询或更新就会出现脏数据问题。
3.解决方案是将分布式操作作用在事务外部,保证事务提交后再进行锁释放。

   //异常情况
    @Transactional
    public void test(){
        synchronized (obj){
            //数据库更新操作
        }
    }
    
//正常操作,锁的作用域应该包含事务的作用域 public void test1(){ synchronized (obj){ update(); } } @Transactional public void update(){ //数据库更新操作 } }

 

posted @ 2025-09-25 18:09  爵士灬  阅读(11)  评论(0)    收藏  举报