锁降级指的是写锁降级为读锁。如果当前线程拥有写锁,然后将其释放,最后再获取读锁,这种并不能称之为锁降级,锁降级指的是把持住(当前拥有的)写锁,再获取到读锁,随后释放(先前有用的)写锁的过程。
下面给出一个锁降级的示例,当数据变动时,isUpdate变量被设置为false,此时所有所有readwrite()方法的线程都能感知到变化,但是只有一个线程能够获取到写锁,其他线程会被阻塞在读锁和写锁的lock()方法上。当前线程获取写锁完成数据准备之后,再获取读锁,随后释放写锁,完成锁降级。
为了保证数据的可见性,如果当前线程不获取读锁而是直接释放写锁,假设此时有另一个线程T获取了写锁并修改了数据,那么当前线程无法感知线程T的数据更新,如果当前线程获取读锁,则线程T会被阻塞,直到当前线程使用数据并施放读锁之后,线程T才能获取写锁进行数据更新。
用下面的代码来解释,若在第2行和第6行上并不加读锁的获取和释放(即//2和//6被注释掉),当前线程完成第1行的put操作后,进行第3行的写锁释放操作,若此时有另一个线程获得写锁并进行修改(即进行第1行操作),那么当前获得读锁的线程无法感知,在进行第4行和第5行操作时就会发生线程安全性问题。若加上第2行和第6行,由于读锁和写锁之间是互斥的,当写锁释放的时候,由于第2行读锁的存在,新的线程并不能获得写锁,此时就保证了线程安全性。
private Map<String, Object> map = new HashMap<>();
private ReadWriteLock rwl = new ReentrantReadWriteLock();
private Lock r = rwl.readLock();
private Lock w = rwl.writeLock();
private volatile boolean isUpdate;
public void readWrite() {
r.lock(); // 为了保证isUpdate能够拿到最新的值
if (!isUpdate) {
r.unlock();
w.lock();
map.put("xxx", "xxx");//1
r.lock();//2
w.unlock();//3
}
Object obj = map.get("xxx");//4
System.out.println(obj);//5
r.unlock();//6
}