锁是什么
锁在现实生活意义在于通过加锁的方式达到隐私保护或者独占的意义。
锁在程序世界里,加锁是方法,目的在于①独占②同步。
多线程锁–锁的源起
1.为了尽可能压榨CPU资源,神奇的码农们发明了轻量级进程LWP,即线程,线程越多抢占CPU机会越大。
2.但同时也带来了隐患,线程上下文切换(保护当前线程案发现场,调度新线程)导致的效率低下,还得考虑线程之间的通信,及与之带来的线程的同步问题。
3.但快速带来的好处远比隐患好的多,所以得考虑如何消除存在的问题。
4.故定义了并发的时候得考虑三个因素,可见性,原子性,有序性。
5.关于线程通信有两种方式:a)通过共享内存(堆与离堆)实现的隐式通信,
b)通过消息传递(wait,notify,notifyall)的显式通信
6.关于线程同步有两种方式:a)通过加锁方式实现的显式同步-->堂而皇之加个锁,自己操作完后将操作完的内容,即工作内存上的内容同步到其他线程可见的共享内存上去
b)通过消息传递(wait,notify,notifyall)的隐式同步
7.总结:多线程存在资源冲突问题,加锁是一种手段,目的是独占中解决同步问题从而达到资源不冲突。
多线程–锁的分类
1.多线程存在资源冲突的问题,是铁板的事实,但其实解决这个问题因人而异
2.有些人天生悲观,通过加锁去解决问题,锁的种类主要有:
a)sync:jvm底层实现的,锁是被动释放
b)lock:jdk下juc包实现的,锁可主动释放
c)上面的a)b)都属于时间换空间来解决资源冲突问题,其实还可以通过ThreadLocal这种空间换时间的方法来解决资源冲突问题
ps:volatile关键字可以使得缓存副本(工作内存)失效从而解决资源冲突问题
3.有些人天生乐观,通过Atomic包(底层CAS机制)实现无锁解决资源冲突
分布式锁–锁的源起
1.前面讲了线程级别的通过锁来解决资源冲突的问题,他们都属于进程,所谓进程其实考虑的也就是①资源分配-->共享②执行调度-->cpu调度-->线程宠幸
2.分布式架构中进程分布在各个不同机器中,他们为了抢占某个资源该如何解决冲突呢。
3.其实理念都是差不多的,还是通过锁,我们称之为分布式锁
分布式锁-常见的解决方案
1.传统RDB数据库中增删记录以达到加锁释放锁的功能
2.高速KV数据库Redis中增删字段以达到加锁释放锁的功能
3.Zookeeper中增删临时有序节点以达到加锁释放锁的功能
分布式锁-Redis实现
1. 第一版:用 setnx(set if not exists) 指令加锁,用del指令释放锁
存在的问题:得到锁的进程途中出现异常,锁无法释放
2. 第二版:用set key value ex time nx指令引入过期释放策略,到时间了就释放,解决了出现异常后死锁的问题
存在的问题:得到锁的进程超时出现,释放锁的时候将其他进程的锁错误释放
3. 第三版1:释放锁的时候加入进程ID的判断或者随机数的判断后再释放锁
4. 第三版2: 因判断与删除非原子操作,使用Lua脚本将这段代码封装称原子操作
5. 第四版:第三版只解决了误删的问题,并没有解决乱象问题,故获得锁的线程开启一个守护线程,用来给快要过期的锁“续航”。
分布式锁-ZK实现
1. 回顾:Znode分为四种类型:持久节点,持久顺序节点,临时节点,临时顺序节点
2. ZK分布式锁的实现通过 1个持久节点+n个临时顺序节点
3. 加锁:创建持久节点ParentLock,客户端进程进来,在ParentLock这个节点下面创建一个临时顺序节点,排序后顺序在最前面的那个进程为抢到锁的进程,其余则依次注册为Watcher
就如这样形成了锁队列,箭头方向为watch方向:lock1<---lock2<---lock3<---lock4
4. 释放锁:获得锁的客户端任务完成或者挂掉了后会删除Znode,Watcher收到通知后获得锁。