java 锁
提到锁 想到Synchronized Lock
Synchronized解决线程安全问题 : 原子性 ,有序性,可见性。
比如 主内存中i=0 然后有Thread1、Thread2分别同时i++。 i++指令是非原子性指令,在装化为汇编指令是是三条指令,第一步先从内存加载i的值,第二步是对i进行一个递增,第三步把i的值写回到内存里。总结一下是Get、Modify、Set。
在Java并发编程里,从功能层面锁里面包含两类,一类是共享锁,另一类是排它锁
共享锁也叫读锁,允许同一时刻多个线程抢占到锁
排它锁也叫写锁,只允许同一时刻一个线程访问共享资源
加锁会存在性能问题 —— 性能和安全性都照顾到 方法1:锁粒度的优化--锁的范围缩小(锁的竞争范围在我的目标需求范围内) 方法2:无锁化编程 --(乐观锁):CAS-->原子性/可见性
方法三:偏向锁/轻量级锁/重量级锁
Synchronized通过操作系统层面的Mutex机制,实现互斥。
性能的体现:1.竞争同步状态的时候,涉及到上下文切换 2. 线程的阻塞和唤醒,也涉及到切换 3. 并行和串行的改变
轻量级锁(自旋锁,通过CAS不断的自旋) 重试:线程1第一次尝试加锁,发现其他线程已经获得了锁,进入下一次循环再重试 此时线程1称为自旋锁(在阻塞等待之前,通过一定的自旋的尝试去竞争锁资源),也叫轻量级锁
偏向锁:如果当前不存在竞争,那么就把锁偏向某个线程(如线程1),等到线程1下次进入的时候,他就不再需要竞争锁。单线程访问同步线程。
重量级索适用于高并发竞争,依赖操作系统互斥量(Mutex)实现线程阻塞与唤醒。
方法四:偏向锁/轻量级锁/重量级锁(减少缩短竞争){锁升级三种锁}
Java JDK里还引入了一个优化,锁消除/锁膨胀(编译器层面的优化) 锁的消除是写的代码本身没有线程安全问题,但加了锁,JVM编译的时候发现加锁导致了无效竞争,于是消锁。锁膨胀是指控制的锁的粒度太小了导致频繁的加锁,频繁的释放锁,频繁竞争,于是把锁的范围扩大,叫锁的膨胀 锁消除/锁膨胀属于优化
方法五:读写锁(读多写少的情况下)
读和读不互斥,不竞争锁。读和写,写和写实现互斥。
方法六:公平锁/非公平锁(性能优化的特性) 默认情况下,锁都时非公平的-->非公平锁的性能要比公平锁的性能更好-->非公平锁减少了锁的等待,减少了线程的阻塞唤醒
方法七:悲观锁:认为所有的访问都是非安全的,必须通过加锁来进行访问
锁的特性:
1.重入锁(避免死锁的一个设计,一个线程抢占到了锁,在释放锁之前,再次去竞争同一把锁的时候,不需要阻塞等待,直接进入重试)
2.分布式锁:解决分布式架构下的粒度的问题,解决进程维度,而Synchronized锁是解决Java并发里面的线程维度
1.分布式锁学后感:什么是锁?一个线程持有锁,另一个线程没有拿到锁,这就是锁,这就是锁的互斥性。
2.怎么保证锁的互斥性?通过redis setnx来保证。
3.加锁之后要解锁,解锁怎么判断?分两步,先判断再释放,判断是不是当前线程的锁。
4.怎么保证解锁过程的原子性?使用LUA脚本保证原子性。
5.为什么setnx命令不用写入LUA脚本?因为setnx是单个命令,redis执行命令都是单线程的,setnx能天然保证原子性。
6.一个线程拿到锁后,这个线程挂了怎么办?给锁设置过期时间
7.线程执行业务,锁超时了,线程自动释放了怎么办?设置看门狗线程,定时给锁续时间。
8.如果业务线程挂了,看门狗线程一直给业务线程续期,怎么办?把看门狗线程设置为守护线程,守护线程的生命周期依赖于业务线程,业务线程挂了守护线程也挂。
9.然后是可重入锁,解释一下?在写递归和方法调用时,同一个锁被同一线程调用。
10.Synchronized怎么实现可重入的?它给每个对象都关联了一个锁监视器,锁监视器中有一个字段为锁计数器,每重入一次就加一,每释放一次就减一。
11.reentrantlock怎么实现可重入的?它是基于AQS(Java 并发包(java.util.concurrent.locks)中构建锁和同步器的底层框架)实现的,AQS中有一个state字段,重入一次就加一。
12.分布式锁的可重入怎么搞?
方法一:使用redis的hash结构,以要锁的东西为key,以线程id拼uuid为filed,以可重入次数为value,重入一次加一次,value充当了锁计数器。这也是redission的实现方案,用hsetnx
方法二:使用redis的string结构,在服务内部维护一个concurrenthashmap,map中的value作为锁计数器,重入一次加一次。
13.方法一为什么要拼接uuid?因为如果在集群环境下线程id可能相同,要保证唯一性。
14.什么是阻塞锁?当线程没有拿到锁,选择等待一会再抢锁,而不是直接返回失败。
15.reentrantlock是怎么实现阻塞锁的呢?它会让没有抢到锁的线程去自旋,不断自旋,直到获取了锁,去执行任务。
16.分步式锁怎么实现锁阻塞?
方法一:不断的自旋,直到获取锁,部分公司就是这样做的。
方法二:使用redission的底层原理,使用redis的发布订阅机制。没抢到锁的线程就订阅一下,然后阻塞住,抢到锁的线程执行完任务后发布一下,之前订阅的线程收到消息等待唤醒。
17.解释一下一主多从架构带来的锁丢失问题?redis是一主多从,当setnx写入到主节点时,主节点没来得及同步到从节点就挂了,就重新选主,将一个从节点转化为主节点,但这个主节点没有锁的数据,也就是锁丢失问题。
18.一主多从架构带来的锁丢失问题怎么解决?redis有一个连锁机制,要求部署多主或多主多从节点,当某一个主节点挂了...... 新线程想要加锁,由于无法给所有主节点加锁,就加锁失败。
19假设一个主节点网络延迟很大,加锁很慢,或者它挂了一直加锁失败导致整体的失败,而且失败之后要回滚其他的redis主节点的数据,性能很差,怎么解决?
使用red redis红锁,红锁也要求部署多个主节点但是只需要部署半数以上的成功即可,半数以上可借助分布式的一致性算法来了解。某个线程加锁半数以上的线程成功了,其他线程就不可能有半数以上的线程加锁成功,这就满足了互斥性。而且红锁对加锁时间也有要求,如果某个节点加锁一段时间加不上了就不等了,反正只要半数以上成功就可以了。
但是红锁也有一些天然的缺陷导致不常用:* 不同节点的系统时钟可能出现不一致* java有gc,gc垃圾回收会导致线程暂停,看门狗线程线程无法锁续期,然后锁就过期了。* 红锁需要部署多个redis的主节点,运维很复杂* 多主节点的数据一致性需要得到保证

浙公网安备 33010602011771号