锁
volatile
volatile是一种稍弱的同步机制,用来确保将变量的更新操作通知到其他线程。当把变量声明为volatile类型之后,编译器与运行时都会注意到这个变量是共享的。访问volatile变量时,不会执行加锁操作,因此不会使线程发送阻塞。
volatile变量只能保证内存中的可见性,而不能保证互斥性。根据这个特性,volatile变量常用来表示状态。
synchronized
修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用的对象是这个类的所有对象。
synchronized锁是可重入的。synchronized既能保证互斥性,也能保证内存可见性。
Lock
Lock是java中的显示锁。
ReentrantLock
ReentrantLock实现了Lock接口,并提供了与synchronized相同的互斥性和内存可见性。ReentrantLock锁也是可重入锁。
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
公平锁与非公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
公平锁:线程按照他们发出请求的顺序获取锁
非公平锁: 当一个线程请求非公平锁时,如果在发出请求的同时该锁变成可用状态,那么这个线程会跳过队列中所有的等待线程而获得锁。
在公平的锁中,如果有另一个线程持有锁或者有其他线程在等待队列中等待这个所,那么新发出的请求的线程将被放入到队列中。而非公平锁上,只有当锁被某个线程持有时,新发出请求的线程才会被放入队列中。 非公平锁的性能要优于公平锁。
读写锁
一个资源可以被多个读操作访问,或者被一个写操作访问,但不能两者同时进行。
public interface ReadWriteLock {
Lock readLock();
Lock writeLock();
}
对于在多处理器系统上被频繁读取饿数据结构,读写锁能提高性能;而在其他场景下,读写锁的性能要差于独占锁,因为读写锁的复杂性更高。
synchronized与Lock的比较
类别
synchronized
Lock
实现机制
悲观锁
乐观锁
操作层次
Java的关键字,JVM层面
Java代码层面
锁的释放
1、以获取锁的线程执行完同步代码,释放锁;2、线程执行发生异常,jvm会让线程释放锁
在finally中必须释放锁,不然容易造成线程死锁
锁的状态
无法判断
可以判断
锁的类型
互斥性 内存可见性 可重入 不可中断 非公平
互斥性 内存可见性 可重入 可判断 可公平 可非公平
分布式锁
redis实现分布式锁
redis通常可以使用setnx来实现分布式锁。setnx来创建一个key,并设置过期时间。如果key不存在则创建成功返回1,如果key已经存在则返回0。依照上述来判定是否获取到了锁获取到锁的执行业务逻辑,完毕后删除lock_key,来实现释放锁其他未获取到锁的则进行不断重试,直到自己获取到了锁
public void lock(){
while(true){
ret = set lock_key identify_value nx ex lock_timeout
if(ret){
//获取到了锁
return;
}
sleep(100);
}
}
public void release(){
value = get lock_key
if(identify_value == value){
del lock_key
}
}
问题
1、 lock timeout的存在也使得失去了锁的意义,即存在并发的现象。一旦出现锁的租约时间,就意味着获取到锁的客户端必须在租约之内执行完毕业务逻辑,一旦业务逻辑执行时间过长,租约到期,就会引发并发问题。所以有lock timeout的可靠性并不是那么的高。
2、 redis单机情况下,还存在redis单点故障的问题。如果为了解决单点故障而使用redis的sentinel或者cluster方案,则更加复杂,引入的问题更多。
ZooKeeper实现分布式锁
public void lock(){
path = 在父节点下创建临时顺序节点
while(true){
children = 获取父节点的所有节点
if(path是children中的最小的){
代表获取了节点
return;
}else{
添加监控前一个节点是否存在的watcher
wait();
}
}
}
watcher中的内容{
notifyAll();
}
public void release(){
删除上述创建的节点
}
锁的占用时间限制:redis就有占用时间限制,而ZooKeeper则没有,最主要的原因是redis目前没有办法知道已经获取锁的客户端的状态,是已经挂了呢还是正在执行耗时较长的业务逻辑。而ZooKeeper通过临时节点就能清晰知道,如果临时节点存在说明还在执行业务逻辑,如果临时节点不存在说明已经执行完毕释放锁或者是挂了。
是否单点故障:redis本身有很多中玩法,如客户端一致性hash,服务器端sentinel方案或者cluster方案,很难做到一种分布式锁方式能应对所有这些方案。而ZooKeeper只有一种玩法,多台机器的节点数据是一致的,没有redis的那么多的麻烦因素要考虑。
总体上来说ZooKeeper实现分布式锁更加的简单,可靠性更高。
Last updated