Lock
Lock是一个接口提供了无条件的、可轮询的、定时的、可中断的获取锁操作
包路径
import java.util.concurrent.locks.Lock
lock() //获取锁,如果线程不可用,将禁用当前线程,并且在获取锁之前,该线程一直处于休眠状态
lockInterruptibly() //如果当前线程未被中断,获取锁;如果锁可用,获取锁
tryLock() //仅在锁为空闲状态获取锁
unlock() //释放锁,防止死锁
newCondition() //返回与Lock一起使用的Condition实例
ReentrantLock
ReentrantLock是Lock的实现类,ReentrantLock的实现比在synchronized实现更具有伸缩性。意味着多个线程竞争相同锁时,比synchronized吞吐量高,jvm将话费较少时间调度线程,而花更多时间执行线程
【缺点】① 需要每次使用的时候,释放锁。②Lock只适用代码块锁
public static void main(String[] args) {
final Count c = new Count();
for (int i = 0; i < 2; i++) {
new Thread(){
@Override
public void run() {
c.get();
}
}.start();
}
for (int i = 0; i < 2; i++) {
new Thread(){
@Override
public void run() {
c.put();
}
}.start();;
}
ReadWriteLock lock = new ReadWriteLock();
System.out.println(lock.readWrite("key"));
}
static class Count{
//使用相同的锁,每次运行结果都是一样的
//如果使用不同的锁,每次运行结果不一样
final ReentrantLock lock = new ReentrantLock();
public void get(){
lock.lock();
System.out.println("{"+Thread.currentThread().getName() + "} get Starting");
try {
Thread.sleep(1000L);
System.out.println("{"+Thread.currentThread().getName() + "} get Ending");
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.unlock();
}
public void put(){
lock.lock();
System.out.println("["+Thread.currentThread().getName() + "] put Starting");
try {
Thread.sleep(1000L);
System.out.println("["+Thread.currentThread().getName() + "] put Ending");
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.unlock();
}
}
console
{Thread-1} get Starting
hello world
{Thread-1} get Ending
[Thread-2] put Starting
[Thread-2] put Ending
[Thread-3] put Starting
[Thread-3] put Ending
{Thread-0} get Starting
{Thread-0} get Ending
ReadWriteLock 和 ReentrantReadWriteLock
提供了一个资源能被多个读线程访问或者被1个写线程访问。但是不能存在读写进程
ReadWriteLock不是Lock的子接口,只是借助Lock实现两个锁并存、互斥的操作机制
ReentrantReadWriteLock时ReadWriteLock唯一实现类
当很多线程从数据结构中获取数据大于写入数据,而很少有线程对其修改。允许读取器线程访问,写入器依然必须为互斥访问
ReadWriteLock的特性
公平性
非公平锁(默认),
读操作不存在锁竞争,所以不存在公平性和非公平性
写操作,可能会立即获取到锁,所以会推迟一个或者多个读操作或写操作。因此非公平锁的吞吐量高于公平锁
公平锁利用AQS和CLH队列,释放当前保持的锁。优先为等待时间最长的写线程分配写入锁。前提是,写线程的等待时间要比所有读线程都长
一个线程持有写入锁或者有一个写线程在等待,那么获取公平锁(飞重入)的所有线程都将被阻塞,直到最先的写线程释放锁
如果读线程等待时间比写线程长,一旦上一写线程释放锁,这一组读线程获取锁重入性
读写锁允许读线程和写线程按照请求锁的顺序重新获取读取锁和写入锁
只有写线程释放锁,读线程才可以获取重入锁
写线程获取锁后,可以再次获取读取锁。反之,则不能。锁升级
读取锁不能直接升级为写入锁。因为获取写入锁需要释放所有读取锁
如果有两个读取锁获取写入锁,且都不释放锁,会造成死锁现象。锁降级
写线程获取写入锁后可以获取读取锁,释放写入锁,写入锁就成了读取锁。
锁获取中断
读取锁和写入锁都可以支持获取期间被中断和独占锁一致
条件变量
写入锁提供条件变量(Condition)的支持,和独占锁一致。但是读取锁不能获取条件变量,否则异常。
重入数
读取锁和写入锁最大数量只能是65535
简单概括
读-读不互斥。读-写互斥。写-写互斥
读写锁方法步骤
public class ReentrantReadWriteLockDemo {
private final Map<String,Object> map = new HashMap<String,Object>();
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public Object readWrite(String key){
Object value = null;
//开启读锁
lock.readLock().lock();
try{
value = map.get(key);
//如果缓存中没有释放读锁,上写锁
if(value == null){
lock.readLock().unlock();
lock.writeLock().lock();
try{
if(value == null){
//可以去数据库中查找
value = "hello world";
}
}finally{
//释放写锁
lock.writeLock().unlock();
}
//开启读锁
lock.readLock().lock();
}
}finally{
//释放读锁
lock.readLock().unlock();
}
return value;
}
}
ReentrantLock和ReentrantReadWriteLock区别
- 相同点:显式锁。手动加锁/解锁。适用高并发场景
- 不同点:①ReentrantReadWriteLock是对ReentrantLock的复杂扩展。能适用更加复杂的业务场景。②ReentrantReadWriteLock可以实现一个方法中读写分离锁机制。而ReentrantLock只有一种机制