重入锁
顾名思义,就是支持重进入的锁,他表示该锁能够支持一个线程对资源的重复加锁,并且支持获取锁的公平和非公平性选择。synchronized 关键字隐私支持重进入,ReentrantLock在调用lock 方法时候,已经获取到锁的线程能够再次调用lock()方法时而不被阻塞。
公平的获取锁,也就是等待时间最长的线程最优先获取锁,也可以说获取是顺序的。ReentrantLock提供一个构造函数,能够控制锁是否是公平的
以上的设计主要基于tryacquire()方法上的修改,判断是否能够获取同步状态,而之前超时获取则是在acqurie()方法上进行逻辑修改。
- 实现重进入
线程再次获取锁:获取同步状态,如果发现c==0,表明目前该锁无线程使用,则通过CAS算法进行获取。当同步状态c!=0,则判断当前的线程是不是目前获取该锁的线程,如果是获取成功,并且计数++,表明重进入次数。
锁的释放:在第n次释放该锁后,如果计数等于0 表示锁成功释放。 - 公平和非公平获取锁的区别
如果一个锁是公平的,则获取锁的顺序是FIFO,非公平锁中,只要CAS设置同步状态成功,则表明获取成功。公平锁中tryAcuires()方法中判断条件多了hasQueuedProdecessor()方法,即同步对列当前结点是否有前驱节点。
实验表明,非公平锁相对公平锁的有更高的效率,因为只要获取同步状态即成功获取锁,在这个前提下,刚刚释放锁的线程再次获取到同步状态的几率非常大,极大的减小了线程切换,保证更大吞吐量。
实例:
public class LockUtil {
private static Map<String, ReentrantLock> lockMap = new ConcurrentHashMap<>();
private static final String DEFAULT_CATEGORY = "default";
public static ReentrantLock get(String key) {
return get(DEFAULT_CATEGORY, key);
}
public static ReentrantLock get(String category, String key) {
return lockMap.computeIfAbsent(category + "_" + key, k -> new ReentrantLock());
}
}
ReentrantLock lock = LockUtil.get("catalog", catalog);
lock.lock();
try {
if (!factoryRepo.containsKey(catalog)) {
ConnectorServiceFactory originFactory =
connectorPlugin.getServiceFactory(catalogInfo);
ConnectorServiceFactoryProxy proxyFactory = new ConnectorServiceFactoryProxy(lastUseTime, originFactory);
factoryRepo.putIfAbsent(catalog, proxyFactory);
proxyFactory.open();
}
} finally {
lock.unlock();
}
###读写锁
ReentrantLock等基本都是排它锁,这些锁在同意时候只允许一个线程进行访问,而读写锁同一时刻可以允许多个读线程访问,在写进程访问时候,所有的读线程和其他写进程被阻塞。
读写锁的性能比排它锁好,因为大多数场景读是多余写的,因此提供更好的并发性和吞吐量。Java 5以后提供读写锁,目前java包中提供读写锁的是现实ReentrantReadWriteLock,支持公平性选择,重进入,锁降级【获取写锁、获取读锁、再释放写锁,写锁就降级成为读锁】等。
ReadWriteLock定义读锁和写锁两个方法readLock()方法和writeLock()方法和一些获取内部状态的方法,getReadlockCount()…读书一共被获取次数,getReadHoldCount()…当前线程获取读数的次数.
读写锁的实现分析
读写状态设计:在一个整形变量上维护多种状态,就需要”按位切割使用“,高16位表示读,低16位表示写。
1.写锁的获取
写锁是***支持重进入排它锁***,当前线程在获取写锁时候,读锁被获取或则该线程不是获取写锁的线程,tryAcquire(){…if(c!=0)【//存在锁//】if(w0 || currrent!=getExclusiveOwnerThread())【//w0,没写锁则存在读锁,当前线程不是获取写锁的进程},返回false 则进入等待状态。
2.读锁的获取
读锁是***支持重进入的共享锁***,在没有其他写现场访问时候,读书总会被成功的获取,所做的是增加读状态,若被其他写进程获取,则返回-1,进入等待状态。
实例:写锁的获取与释放
protected final boolean tryAcquire(int acquires) {
// 获取当前线程
Thread current = Thread.currentThread();
// 获取ReentrantReadWriteLock锁整体同步状态
int c = getState();
// 获取写锁同步状态
int w = exclusiveCount(c);
// 存在读锁或写锁
if (c != 0) {
// c != 0 && w == 0 即若存在读锁或写锁持有线程不是当前线程,获取写锁失败
if (w == 0 || current != getExclusiveOwnerThread())
return false;
// 最多65535次重入,若超过报错
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// 可重入,设置同步状态
setState(c + acquires);
return true;
}
// 公平与非公平,同步队列是否有节点,同时cas设置状态
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
// 设置获取锁的线程为当前线程
setExclusiveOwnerThread(current);
return true;
}