Java中的锁

本文介绍了Java中锁的分类,包括公平锁/非公平锁、悲观锁/乐观锁等。还讲解了ReentrantLock和synchronized可重入独占锁的特点与使用场景。此外,阐述了分布式锁的实现方式,如基于zookeeper和Redis,分析了各自特点与原理。

Java中的锁

一、锁的分类

公平锁/非公平锁
公平锁是指多个线程按照申请锁的顺序来获取锁。非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。“Synchronized”就是一种非公平锁。

悲观锁/乐观锁
悲观锁认为对于同一个数据的并发操作,一定是会发生修改,因此会对数据访问时对数据进行锁定。悲观的认为,不加锁的并发操作一定会出问题。
乐观锁则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断重试的方式更新数据。

乐观锁的实现:
1. CAS 实现:Java 中java.util.concurrent.atomic包下面的原子变量使用了乐观锁的一种 CAS(Compare and swap) 实现方式。
2. 版本号控制:是在数据表中加上一个数据版本号(version字段)。数据被修改时,version 值加1。线程更新数据之前先查询要更新的数据(包括version值),提交更新时,读取到的version值与当前数据库中的version值相等时才更新,否则重试,直到更新成功。

独享锁/共享锁
独享锁是指该锁一次只能被一个线程所持有。共享锁是指该锁可被多个线程所持有。对于 “Synchronized”、ReentrantLock“”而言,其是独享锁。但是对于Lock的另一个实现类ReentrantReadWriteLock,其读锁是共享锁,其写锁是独享锁。读锁的共享锁可保证并发读是非常高效的,读写,写读 ,写写的过程是互斥的。独享锁与共享锁是通过AQS(AbstractQueuedSynchronized)来实现的,通过实现不同的方法,来实现独享或者共享。

可重入锁
可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。比如一个递归函数里有加锁操作,递归过程中这个锁不会阻塞自己,那么这个锁就是可重入锁(所以可重入锁也叫做递归锁)。Java里只要以Reentrant开头命名的锁都是可重入锁,而且JDK提供的所有现成的Lock实现类,包括synchronized关键字锁都是可重入的。

自旋锁
是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的尝试获取锁,直到成才会退出循环。

四、锁

ReentrantLock可重入锁的使用

synchronized

ReentrantLock

 

ReentrantLock和synchronized都是可重入的独占锁,synchronized易于操作,但不够灵活。ReentrantLock加锁和解锁的过程需要手动进行,使用起来非常灵活,也更适合复杂的场景。synchronized是JVM级别的锁,即Java语言内置的锁,ReentrantLock是实现了Lock接口的锁。ReentrantLock、ReadLock、WriteLock 是Lock接口最重要的三个实现类

可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。

根据加锁策略可分为“悲观锁”和“乐观锁”

悲观锁:每次去拿数据的时候都认为别人会修改。所以每次在拿数据的时候都会上锁。这样别人想拿数据就被挡住,直到悲观锁被释放。

乐观锁:每次去拿数据的时候都认为别人不会修改。所以不会上锁。但是如果想要更新数据,则会在更新前检查在读取至更新这段时间别人有没有修改过这个数据。如果修改过,则重新读取,再次尝试更新,循环上述步骤直到更新成功

 

三、分布式锁

分布式锁的实现方式

基于zookeeper

特点:可靠性较redis强,但读写性能不如redis

临时节点:客户端建立一个临时节点后,在会话结束或者会话超时后,zookeeper会自动删除该节点。

原理:利用zk的临时节点的有序性,每个线程获取锁就是在zk创建一个临时有序节点,再判断当前线程创建的节点是否为所有的节点中序号最小的。如果是,则认为获取锁成功。

基于Redis

读写性能强,适用于高并发环境

使用Redisson客户端实现分布式锁,通过Redis命令:SET anyLock unique_value NX PX 30000

设置一个超时时间为30秒的key,如果业务在超时时间内还没有完成,就要延长key的超时时间,比较麻烦。

Config config = new Config();
config.useClusterServers()
.addNodeAddress("redis://192.168.1.1:6379")
.addNodeAddress("redis://192.168.1.2:6379");
RedissonClient redisson = Redisson.create(config);
RLock lock = redisson.getLock("anyLock");
lock.lock();
lock.unlock();

只需要通过lock和unlock就可以实现分布式锁,redisson实现了很多细节:

  • redisson所有指令都通过lua脚本执行,redis支持lua脚本原子性执行
  • redisson中有一个概念watchdog(“看门狗”),会在获取锁之后每隔10秒把key的超时时间设为30s。
  • “看门狗”逻辑保证了不会发生死锁。
     

Distributed locks and synchronizers:
https://github.com/redisson/redisson/wiki/8.-分布式锁和同步器

 

 

 

 

 

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值