大家好我是霜华!!!
我是真的真的想回学校了。
想去图书馆边写代码边看来来往往的小姐姐们。
真就迷迷糊糊的大二就这样过去了。。。。
分布式在如今的大数据高流量的情况下是非常必要有的一个环境
而分布式锁也是重中之重
多个操作同时进行会参数并发问题,因为多个操作不是原子操作
(原子操作:不会被线程调度机制打断的操作,这种机制操作一旦开始就一直运行到结束,中间不会有任何线程切换)
在单机开发中,锁经常见到,简单暴力的synchronize 以及可重入锁ReenttrantLock ,
用ReenttrantLock实现单机锁
public class RedisText {
private static Integer inventory=1001;//存货
private static final int NUM=1000;//执行次数
private static LinkedBlockingQueue linkedBlockingQueue=new LinkedBlockingQueue();//单向列表的阻塞队列
static ReentrantLock reentrantLock =new ReentrantLock();//重入锁,实现公平锁机制
public static void main(String []args){
ThreadPoolExecutor threadPoolExecutor=//线程池
new ThreadPoolExecutor(inventory,inventory,10L, SECONDS,linkedBlockingQueue);
//设置线程数、最大的线程数,最大活跃市场
final CountDownLatch countDownLatch = new CountDownLatch(NUM);//让一一个线程等待其他线程各自执行完毕后再执行//计数器
for(int i=0;i<NUM;i++){
threadPoolExecutor.execute(new Runnable() {//执行线程
public void run() {
reentrantLock.lock();//java提供的可重入锁,
inventory--;//处理存货
reentrantLock.unlock();
System.out.println("线程执行"+Thread.currentThread().getName());
countDownLatch.countDown();//计数器减1
}
});
}
threadPoolExecutor.shutdown();//关掉计数器
try {
countDownLatch.await();//主动关掉计数器
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在多线程环境中控制对资源的并发访问,但是这并适合于多机分布式环境中的对信息安全的需求,这时候分布式锁就很有必要。
分布式的使用场景:
效率:避免不同节点(可以理解成就是不同服务器实例)重复相同的工作,可能有多个服务器接到了同一个指令去做同样的是
正确性:避免多个节点在同一个数据上进行操作,让操作失真
一个优秀方分布式锁应该具备如下特点:
1.互斥性:就是你占了坑别人不能抢走
2.可重入性:一个锁支持同一个锁多次加锁
3.锁超时:一定要设置锁的实现,避免因为特殊原因没执行解锁指令形成死锁
4.高效、高可用
5.支持公平锁和非公平锁:公平锁:按照请求顺序加锁,非公平:无序加锁
每次去拿数据的时候都认为别⼈会修改,所以每⼀次拿数据的时候都会上锁,这样别⼈想拿数据就会被挡住
乐观锁策略也被称为⽆锁编程,换句话说,乐观锁其实不是锁,它仅仅只是⼀个循环重试了的CAS
算法⽽已
每次去拿数据的时候都认为别⼈不会修改,所以不会上锁,但是如果想要更新数据,则会在更新前
检查在读取⾄更新这段时间别⼈有没有修改过这个数据,如果修改过,则重新读取,再次尝试更
新,循环上述步骤指导更新成功
乐观锁的缺点—ABA问题
如果⼀个变量V初次读取的时候是A值,并且在准备复制的时候检查到它仍然是a值那我们就能说明它
的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然
后有改回A,那CAS操作就会误认为它从来没有被修改过,这个问题被称为CAS操作的“ABA”问题
1)悲观锁阻塞事务,乐观锁回滚重试
2)乐观锁适⽤于写⽐较少的情况下,即冲突很少发⽣时,可以省去锁的开销,加⼤了系统吞吐量
3)悲观锁适⽤于写⽐较多的情况,因为如果乐观锁经常冲突,应⽤要不断进⾏重试,反倒降低性能
Compare-and-Swap 即⽐较并替换,也有叫做Compare-and-Set的,⽐较并设置
1、⽐较:读取到了⼀个值A,在将其更新为B之前,检查原值是否仍为A(未被其他线程改动)
2、设置:如果是,将A更新为B,结束,如果不是,则什么都不做
允许多个线程同时读取(因为根本没有加锁操作),但是只有⼀个线程可以成功更新数据,并导致
其他要更新的线程回滚重试,也叫⾮阻塞同步(Non-blocking Synchronization)
在很多场景中,我们为了保证保证数据的最终的⼀致性,需要很多的技术⽅案来⽀持,⽐如分布
式,分布式锁
有时,我们需要保证⼀个⽅法在同⼀时间只能被同⼀个线程执⾏,在单机环境中,java其实提供了很
多并发处理相关的API,但是这些API在分布式场景中就⽆能为⼒,也就是说单纯的javaAPI并不能提
供分布式锁的能⼒,所以针对分布式锁,实现⽬前有多种⽅案
分布式锁是控制分布式系统之间同步访问共享资源的⼀种⽅式
分布式锁⼀般有三种实现⽅式:
1.数据库乐观锁
2.基于Redis 的分布式锁,
3.基于Zookeeper的分布式锁
1.互斥性,在任意时刻,只有⼀个客户端能持有锁
2.不会发⽣死锁,即使有⼀个客户端在持有锁的期间崩溃⽽没有主动解锁,也能保证后续其他户端能加锁
3.具有容错性,只要⼤部分Redis 节点正常运⾏,客户端就可以加锁和解锁
4.解铃还需系铃⼈,加锁和解锁必须是同⼀个客户端,客户端⾃⼰不能把别⼈加的锁给解了
setnx指令 实现加锁 (原本是用来:如果key值不存在就赋值)
setnx xxxx true//加锁
实现锁方法一:
setnx xxx true //加锁
xxxxxx//执行代码,不会有其他节点会去执行这套流程
del xxx //删除key释放锁
问题一:代码区出现异常,del不会执行,锁一直被占用,死锁
方法二:
setnx 加锁后 expire xxx 5s//加锁后加一个过期时间,就算del执行不了,5s后依然放锁
问题二:
setnx 后还没来得及执行expire ,服务器出现问题挂了,expire执行不了 同样死锁
因为 setnx 和expire 是两条指令不是原子指令。(事务的原子性要么一起执行要不一起不执行)
方法三:
setnx 与px的结合参数
(redis操作:
set xxx true ex 5 nx//设置xxx加锁,5s后时效xxx删除解锁
)
setnx 和expire的原子结合,设置锁也设置过期
问题三:超时问题
代码的执行超过了设置的过期时间,后续代码就不会严格一系列执行了,中间可能会有其他线程参一脚,改变了数据
方法四:
setnx 指令的value参数设置为一个随机数,释放锁是先匹配随机数是否一致然后在删除key
问题四:
匹配value 和删除key不是原子操作,依然有可能会造成key没删到造成死锁
方法五:
用Lua叫做处理,保证多个指令是原子性执行
redission 是一个开源的基于redis的可重入锁
redission锁的原理分析:
加锁原理:
public boolean getLock(String value){
SetParams params=SetParams.setParams().nx().ex(5);//set xxx ex 5 nx//nx ex命令合计
//还有个px 跟ex是一样的效果 px以毫秒做单位,ex以秒做单位
String answer= jedis.set("redis_lock",value,params);//返回结果是 两种 ok /null
if(answer!=null){
return true;
}
return false;
}
public boolean lock(String value){
Long statrt = System.currentTimeMillis();
try{
for(;;){
if(getLock(value)){
return true;
}
//设置一下找锁的时长吧如果长时间没找到我就不找了
long end =System.currentTimeMillis()-statrt;
if(end>5){
return false;
}
}
}finally {
jedis.close();//上完厕所就关门文明你我他
}
}
解锁原理:原码肯定是用了Lua脚本做了一次拼装
public boolean uvlock(String value){
//一般情况下value 设置一个独立的随机数字,你找到了我我才给你解锁
try {
if(jedis.get("redis_lock").equals(value)){
jedis.del(value);
return true;
}
return false;
}finally {
jedis.close();
}
}
当然redission原码肯定比这个复杂很多很多倍,等霜华有时间了再让大家白嫖。redis分布式锁应该是面试比较常问的吧,希望大家好好理解。
我是霜华一个渴望去西溪园区撩妹的后浪,我们下期见!