Lock特点
Lock接口提供了和synchronized关键字类似的同步功能,synchronized关键字只是隐式低获取锁,而lock拥有了锁获取和释放的可操作性、可中断的获取锁以及超时获取锁等同步特性
Lock使用
Lock lock = new ReentrantLock(); lock.lock(); try{ }finally{ lock.unlock(); }
在finally块中释放锁,目的是保证在获取到锁之后,最终能够被释放
不要将获取锁的过程中写在try块中,因为如果在获取锁(自定义锁的实现)时候发生了异常,异常抛出的同时,也会导致锁无故释放
特性 | 描述 |
1 .尝试非阻塞地获取锁 | 当前线程尝试获取锁,如果这一时刻锁没有被其他线程获取到,则成功获取锁,比如synchronized如果一个线程获取到锁,另一个线程要获取到锁,就必须阻塞等待,而lock不用 |
2. 能够被中断的获取锁 | 和synchronized不同,获取到锁的线程能够响应中断,当获取到lock锁的线程被中断,将抛出异常,并且释放锁 |
3.支持超时获取锁 | lock在指定的时间去获取锁,如果截止时间到了,仍然无法获取锁,则返回 |
重入锁ReentrantLock
能够支持一个线程对资源的重复加锁,这点synchronized也能够做到
public class GetLockAgain { private Lock lock1 = new ReentrantLock(); private Lock lock2 = new ReentrantLock(); public void test (){ try{ lock1.lock(); System.out.println("获取到lock1"); lock2.lock(); System.out.println("获取到lock2"); }finally{ lock1.unlock(); lock2.unlock(); } } public static void main(String[] args) { final GetLockAgain gla = new GetLockAgain(); gla.test(); } }
输出:
获取到lock1 获取到lock2
ReentrantLock支持公平和非公平选择
什么是锁的公平性?
在时间上,先请求锁的线程,能够先获取到锁,就是公平锁,反之,就是非公平锁
ReentrantLock提供了构造函数控制:
boolean fair : true ,公平锁,false ,非公平锁,默认的构造是非公平锁
读写锁
之前提到的锁都是排他锁,这些锁在一个时刻只能被一个线程占用。读写锁维护了一堆锁,读锁和写锁,读锁在同一个时刻
可以允许多个线程访问,写锁只能允许一个线程访问, 写锁是排他锁,读锁是共享锁
实际开发中,读写锁的性能比排他锁性能更好,因为很多场景下都是读线程,另外可以设置读写锁的公平性并且支持锁重入
* 写锁的获取
protected final boolean tryAcquire(int acquires) { /* * Walkthrough: * 1. If read count nonzero or write count nonzero * and owner is a different thread, fail. * 2. If count would saturate, fail. (This can only * happen if count is already nonzero.) * 3. Otherwise, this thread is eligible for lock if * it is either a reentrant acquire or * queue policy allows it. If so, update state * and set owner. */ Thread current = Thread.currentThread(); int c = getState(); int w = exclusiveCount(c); if (c != 0) { // (Note: if c != 0 and w == 0 then shared count != 0) if (w == 0 || current != getExclusiveOwnerThread()) return false; if (w + exclusiveCount(acquires) > MAX_COUNT) throw new Error("Maximum lock count exceeded"); // Reentrant acquire setState(c + acquires); return true; } if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) return false; setExclusiveOwnerThread(current); return true; }
源码注释是当前线程持有的读锁数量不为0 (也就是获取到了读锁就不能在去获取写锁)或者 已经持有写锁的线程不是当前线程,写锁会获取失败,否则能够再次获取
* 读锁的获取
protected final int tryAcquireShared(int unused) { /* * Walkthrough: * 1. If write lock held by another thread, fail. * 2. Otherwise, this thread is eligible for * lock wrt state, so ask if it should block * because of queue policy. If not, try * to grant by CASing state and updating count. * Note that step does not check for reentrant * acquires, which is postponed to full version * to avoid having to check hold count in * the more typical non-reentrant case. * 3. If step 2 fails either because thread * apparently not eligible or CAS fails or count * saturated, chain to version with full retry loop. */ Thread current = Thread.currentThread(); int c = getState(); if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) return -1; int r = sharedCount(c); if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) { if (r == 0) { firstReader = current; firstReaderHoldCount = 1; } else if (firstReader == current) { firstReaderHoldCount++; } else { HoldCounter rh = cachedHoldCounter; if (rh == null || rh.tid != current.getId()) cachedHoldCounter = rh = readHolds.get(); else if (rh.count == 0) readHolds.set(rh); rh.count++; } return 1; } return fullTryAcquireShared(current); }如果 如果当前线程获取到了读锁(CAS更新count)或者没有获取过, 当前线程获取到读锁,并且有 如果写锁被其他线程占用了,读锁会获取失败
例如,将读写锁应用在缓存中
/** * 读写锁应用于缓存 * @author zhouy * */ public class Cache { private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); private static Lock r = lock.readLock(); private static Lock w = lock.writeLock(); private static Map<String, String> map = new HashMap<>(); /** * 向缓存中写入,多个线程需要排队执行 * @param key * @param data */ public final static void put(String key,String data){ try{ w.lock(); map.put(key, data); }finally{ w.unlock(); } } /** * 读取缓冲内容,多个线程可以同时访问 * @param key * @return */ public final static String read(String key){ try{ r.lock(); return map.get(key); }finally{ r.unlock(); } } /** * 清空缓存 */ public final static void clear(){ try{ w.lock(); map.clear(); }finally{ w.unlock(); } } }
读写锁的锁降级
锁升级:从读锁升级到写锁
锁降级: 从写锁降级到读锁,指的是先持有写锁,再去获取读锁,随后释放之前持有的写锁的过程
ReentrantReadWriteLock 支持锁降级(获取到写锁,再去获取读锁),不支持锁升级(前面的源码可以看出,获取到读锁是无法再去获取写锁的)
锁升级死锁
public class ReadWriteLock { private ReentrantReadWriteLock rw = new ReentrantReadWriteLock(); private Lock w = rw.writeLock(); private Lock r = rw.readLock(); public void test(){ try{ r.lock(); System.out.println("获取到读锁"); w.lock(); System.out.println("获取到写锁"); w.unlock(); }finally{ r.unlock(); } } public static void main(String[] args) { ReadWriteLock rwl = new ReadWriteLock(); rwl.test(); } }
输出:
锁降级
public class ReadWriteLock { private ReentrantReadWriteLock rw = new ReentrantReadWriteLock(); private Lock w = rw.writeLock(); private Lock r = rw.readLock(); public void test(){ try{ w.lock(); System.out.println("获取到写锁"); r.lock(); System.out.println("获取到读锁"); r.unlock(); }finally{ w.unlock(); } } public static void main(String[] args) { ReadWriteLock rwl = new ReadWriteLock(); rwl.test(); } }
输出:
为什么使用到锁降级?
在多线程环境中,如果存在一个线程在修改一个共享变量前需要先去读取这个变量的值,如果是先获取写锁,阻塞其他线程,在去读取这个共享变量的值,就能够保证数据的正确。
反之,如果使用读锁先读取,在释放去获取写锁,更新这个值,这个值可能已经被其他线程改过了。