锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能够防止多个线程同时访问共享资源,在 Lock 接口出现之前,Java 应用程序只能依靠 synchronized 关键字来实现同步锁的功能,在 Java 5 以后,增加了 JUC 的并发包且提供了 Lock 接口用来实现锁的功能,它提供了与 synchroinzed 关键字类似的同步功能,只是它比 synchronized 更灵活,能够显示的获取和释放锁。
Lock 实现类
Lock 是一个接口,有两个核心方法 lock() 和 unlock(),它有很多实现类,这里介绍两个常用的实现类的使用,ReentrantLock 和 ReentrantReadWriteLock。
Lock 和 ReentrantLock 的类图如下所示。
AbstractQueuedSynchronizer 同步器是实现 Lock 锁机制的核心,后面会详细讲解
Sync、NonfairSync、FairSync 是 ReentrantLock 的内部类,继承了 AbstractQueuedSynchronizer 类
接下来介绍 ReentrantLock 和 ReentrantReadWriteLock 的基本使用
Lock 使用
ReentrantLock
重入锁,表示支持重新进入的锁,也就是说,如果当前线程 t1 通过调用 lock 方法获取了锁之后,再次调用 lock,是不会再阻塞去获取锁的,直接增加重试次数就行了。
public class AtomicDemo {
private static int count;
private static Lock lock = new ReentrantLock();
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
new Thread(new Runner()).start();
}
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("运行结果:" + count);
}
private static class Runner implements Runnable {
@Override
public void run() {
// 获取锁
lock.lock();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
// 释放锁
lock.unlock();
}
}
}
ReentrantReadWriteLock
可重入读写锁,表示支持重新进入的锁,并且读写锁维护了一对锁,读锁和写锁,具有如下特征。
- 读锁与读锁可以共享
- 读锁与写锁不可以共享(排他)
- 写锁与写锁不可以共享(排他)
适用于读多写少的场景,在该场景下读写锁能够提供比排它锁更好的并发性和吞吐量。
在执行写操作是,线程必须要获取写锁,当已经有线程持有写锁的情况下,当前线程会被阻塞,只有当写锁释放以后,读写操作才能继续执行。使用读写锁提升读操作的并发性,也保证每次写操作对所有的读写操作的可见性。
public class ReadWriteLockDemo {
private static Map<String, Object> cacheMap = new HashMap<>();
private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private static Lock readLock = readWriteLock.readLock();
private static Lock writeLock = readWriteLock.writeLock();
/**
* 获取缓存数据
*
* @param key
* @return
*/
public static Object get(String key) {
readLock.lock();
try {
return cacheMap.get(key);
} finally {
readLock.unlock();
}
}
/**
* 保存缓存数据
*
* @param key
* @param value
* @return
*/
public static Object put(String key, Object value) {
writeLock.lock();
try {
return cacheMap.put(key, value);
} finally {
writeLock.unlock();
}
}
}
synchronized 和 Lock 的区别
- 从层次上,一个是关键字、一个是类,这个是最直观的的差异
- 从使用上,Lock 具备更大的灵活性,可以控制锁的获取和释放,而 synchronized 锁的释放是被动的,当出现异常或者同步代码块执行完成后才会释放锁
- Lock 可以判断锁的状态,而 synchronized 不可以
- Lock 可以实现公平锁、非公平锁,而 synchronized 只有非公平锁
作者:殷建卫 链接:https://www.yuque.com/yinjianwei/vyrvkf/gsq47i 来源:殷建卫 - 架构笔记 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。