自定义锁:
根据之前AQS的学习,自己要实现一把独占锁:
在AQS的基础上,只需要去实现获取锁、释放锁的逻辑。获取锁失败进入等待队列后堵塞、获取锁成功后唤醒等待线程出队都由AQS实现了。
public class TulingLock extends AbstractQueuedSynchronizer{
@Override
protected boolean tryAcquire(int unused) {
//cas 加锁 state=0
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override
protected boolean tryRelease(int unused) {
//释放锁
setExclusiveOwnerThread(null);
setState(0);
return true;
}
public void lock() {
acquire(1);
}
public boolean tryLock() {
return tryAcquire(1);
}
public void unlock() {
release(1);
}
public boolean isLocked() {
return isHeldExclusively();
}
}
读写锁:
场景:读读、读写、写读、写写
读读:读锁,共享锁
写写:写锁,独占锁
读写:需要判断当前是否有读锁、写锁
这就涉及到state怎么存储读锁、写锁的状态
int 32位,高16位表示读锁,低16位表示写锁
有读锁时,加写锁需要等读锁释放,所以这读锁是悲观锁
用法demo:
class RWDictionary {
* private final Map<String, Data> m = new TreeMap<String, Data>();
* private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
* private final Lock r = rwl.readLock();
* private final Lock w = rwl.writeLock();
*
* public Data get(String key) {
* r.lock();
* try { return m.get(key); }
* finally { r.unlock(); }
* }
* public String[] allKeys() {
* r.lock();
* try { return m.keySet().toArray(); }
* finally { r.unlock(); }
* }
* public Data put(String key, Data value) {
* w.lock();
* try { return m.put(key, value); }
* finally { w.unlock(); }
* }
* public void clear() {
* w.lock();
* try { m.clear(); }
* finally { w.unlock(); }
* }
ReentrantReadWriteLock适合读多写少的场景
锁降级
锁降级是指把持住(当前拥有的)写锁,再获取 到读锁,随后释放(先前拥有的)写锁的过程。
锁降级可以帮助我们拿到当前线程修改后的结果而不被其他线程所破坏,防止更新丢失
* class CachedData {
* Object data;
* volatile boolean cacheValid;
* final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
*
* void processCachedData() {
* rwl.readLock().lock();
* if (!cacheValid) {
* // Must release read lock before acquiring write lock
* rwl.readLock().unlock();
* rwl.writeLock().lock();
* try {
* // Recheck state because another thread might have
* // acquired write lock and changed state before we did.
* if (!cacheValid) {
* data = ...
* cacheValid = true;
* }
* // Downgrade by acquiring read lock before releasing write lock
* rwl.readLock().lock();
* } finally {
* rwl.writeLock().unlock(); // Unlock write, still hold read
* }
* }
*
* try {
* use(data);
* } finally {
* rwl.readLock().unlock();
* }
* }
RentrantReadWriteLock不支持锁升级(把持读锁、获取写锁,最后释放读锁的过程)。
目的也是保证数据可见性,如果读锁已被多个线程获取,其中任意线程成功获取了写锁并更新 了数据,则其更新对其他获取到读锁的线程是不可见的。
ReentrantReadWriteLock存在什么缺陷?如何改进?
ReentrantReadWriteLock有个潜在的问题:如果有线程正在读,写线程需要等待读线程释放锁后才能获取写锁,即读的过程中不允许写,这是一种悲观的读锁。为了进一步提升并发执行效率,Java 8引入了新的读写锁:StampedLock。StampedLock和ReentrantReadWriteLock相比,改进之处在于:读的过程中也允许获取写锁后写入!在原先读写锁的基础上新增了一种叫乐观读(Optimistic Reading)的模式。该模式并不会加锁,所以不会阻塞线程,会有更高的吞吐量和更高的性能。