读写锁同样存在着重入问题。简单的读写锁见读写锁浅析。这里我们拿这个简单的写锁来做一个重入测试:
@Test public void testNonReentrantLock() { MyReadWriteLock lock = new MyReadWriteLock(); new Thread(() -> { { try { lock.writeLock(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("我是写锁."); // 我又来拿读锁了 try { lock.writeLock(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("我还是写锁."); lock.writeUnlock(); System.out.println("解放写锁."); lock.writeUnlock(); System.out.println("解放写锁."); } }).start(); }
输出结果:
我是写锁.
后续的写锁没法加了,说明并不支持重入。要支持重入,就先得认得之前加的锁,要认得之前加的锁也简单,先认得加锁所在的线程就行了。看实现:
package com.wulinfeng.test.testpilling.util; import java.util.HashMap; import java.util.Map; /** * 可重入的读写锁 * * @author wulinfeng * @version C10 2019年1月14日 * @since SDP V300R003C10 */ public class MyReentrantReadWriteLock { // 读线程映射 private Map<Thread, Integer> readThreads = new HashMap<>(); // 写线程 private Thread writeThread = null; // 写线程 private int write = 0; // 写请求线程 private int writeRequest = 0; public synchronized void readLock() throws InterruptedException { // 当前读线程 Thread readThread = Thread.currentThread(); // 加读锁 while (!accessRead(readThread)) { wait(); } // 取到锁,重入次数自增 readThreads.put(readThread, getReadCount(readThread) + 1); } public synchronized void readUnlock() { // 当前线程 Thread currentThread = Thread.currentThread(); // 是否为读线程,不是则报错 if (!isRead(currentThread)) { throw new IllegalMonitorStateException("Calling Thread does not hold a read lock on this ReadWriteLock"); } // 获取读线程的重入次数 int readCount = getReadCount(currentThread); // 解放读锁 if (readCount == 1) { readThreads.remove(currentThread); } else { // 还存在重入次数则自减 readThreads.put(currentThread, readCount - 1); } notifyAll(); } public synchronized void writeLock() throws InterruptedException { // 写请求累加 writeRequest++; // 获取当前写进程 Thread currentThread = Thread.currentThread(); // 加写锁 while (!accessWrite(writeThread)) { wait(); } // 写请求自减 writeRequest--; // 写线程自增 write++; writeThread = currentThread; } public synchronized void writeUnlock() { if (!isWrite(Thread.currentThread())) { throw new IllegalMonitorStateException("Calling Thread does not hold a write lock on this ReadWriteLock"); } // 写线程自减 write--; // 解放写线程 if (write == 0) { writeThread = null; } notifyAll(); } /** * 是否允许加读锁 * * @author wulinfeng * @param readThread * @return */ private boolean accessRead(Thread currentThread) { // 当前线程为写线程,则允许读 if (isWrite(currentThread)) { return true; } // 存在写线程则不允许读 if (hasWrite()) { return false; } // 当前为读线程则允许读 if (isRead(currentThread)) { return true; } // 存在写请求则不允许读 if (hasWriteRequest()) { return false; } return true; } /** * 是否允许加写锁 * * @author wulinfeng * @param writeThread * @return */ private boolean accessWrite(Thread writeThread) { // 只有一个读锁,允许写 if (isOnlyRead(writeThread)) { return true; } // 存在读锁,不允许写 if (hasRead()) { return false; } // 写线程为空,允许写 if (writeThread == null) { return true; } // 当前线程不为写线程(那就是读线程了),不允许写 if (!isWrite(writeThread)) { return false; } return true; } /** * 获取读线程的重入次数 * * @author wulinfeng * @param readThread * @return */ private int getReadCount(Thread readThread) { Integer readCount = readThreads.get(readThread); if (readCount == null) { return 0; } return readCount.intValue(); } /** * 读线程不为空 * * @author wulinfeng * @return */ private boolean hasRead() { return readThreads.size() > 0; } /** * 当前线程是否读线程 * * @author wulinfeng * @param currentThread * @return */ private boolean isRead(Thread currentThread) { return readThreads.get(currentThread) != null; } /** * 是否只有一个读线程 * * @author wulinfeng * @param readThread * @return */ private boolean isOnlyRead(Thread readThread) { return readThreads.size() == 1 && readThreads.get(readThread) != null; } /** * 是否存在写线程 * * @author wulinfeng * @return */ private boolean hasWrite() { return writeThread != null; } /** * 当前线程是否为写线程 * * @author wulinfeng * @param currentThread * @return */ private boolean isWrite(Thread currentThread) { return writeThread == currentThread; } /** * 是否存在写请求 * * @author wulinfeng * @return */ private boolean hasWriteRequest() { return this.writeRequest > 0; } }
我们把之前的测试代码中MyReadWriteLock改为MyReentrantReadWriteLock,这次输出结果就对了:
我是写锁.
我还是写锁.
解放写锁.
解放写锁.
最后我们测试一下实际的应用:
@Test public void TestMyReentrantReadWriteLock() { MyReentrantReadWriteLock lock = new MyReentrantReadWriteLock(); new Thread(() -> { { BufferedReader br = null; // 加锁 try { lock.readLock(); } catch (InterruptedException e) { e.printStackTrace(); } // 读文件 try { br = new BufferedReader(new InputStreamReader(new FileInputStream(FILE_PATH), "GBK")); int lineNo = 0; String lineContent = null; while ((lineContent = br.readLine()) != null) { System.out.printf("行号:%d:%s ", lineNo, lineContent); lineNo++; } } catch (IOException e) { e.printStackTrace(); } finally { try { br.close(); } catch (IOException e) { e.printStackTrace(); } } // 解锁 lock.readUnlock(); } }).start(); latch.countDown(); // 起个线程写,写的内容可以多一点 new Thread(() -> { { String content = "人们对杭州的了解,更多地源自曾风靡一时的电视剧《新白娘子传奇》,还有鲁迅笔下那倒掉的雷峰塔。"; BufferedWriter bw = null; // 加锁 try { lock.writeLock(); } catch (InterruptedException e) { e.printStackTrace(); } // 写入 try { System.out.println("开始写...."); bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(FILE_PATH, true), "GBK")); bw.write(content); } catch (IOException e) { e.printStackTrace(); } finally { try { bw.close(); } catch (IOException e3) { e3.printStackTrace(); } } // 解锁 lock.writeUnlock(); } }).start(); latch.countDown(); new Thread(() -> { { BufferedReader br = null; // 加锁 try { lock.readLock(); } catch (InterruptedException e) { e.printStackTrace(); } // 读文件 try { br = new BufferedReader(new InputStreamReader(new FileInputStream(FILE_PATH), "GBK")); int lineNo = 0; String lineContent = null; while ((lineContent = br.readLine()) != null) { System.out.printf("行号:%d:%s ", lineNo, lineContent); lineNo++; } } catch (IOException e) { e.printStackTrace(); } finally { try { br.close(); } catch (IOException e) { e.printStackTrace(); } } // 解锁 lock.readUnlock(); } }).start(); latch.countDown(); // 先休息一会儿 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // 主线程等待 try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } }
输出结果:
文件不存在。 行号:0:细雨之中的西湖、盛开的荷花、一座断桥,淡淡几笔,足以勾勒出淡妆浓抹总相宜的杭州。 行号:1:杭州之美,在于留给旁人对美的无尽想象空间。对我们大多数人来说,杭州的美,犹如一幅盛满故事的山水画,诗意、神秘、动情;对于航天之父钱学森来说,杭州于他也是这般美丽。 行号:2:只是直到19岁,他才能借养病之机认识自己家乡的美丽,到底有点迟了。 行号:3:但钱学森一触摸到杭州这幅美卷,便充满不舍和一生的惦念。 行号:4:虽然钱学森年少时因父亲工作调动而辗转生活于北京、上海,长大后为了学业穿梭于北京和大洋彼岸的美国,但杭州,终究是钱学森生命开始的地方,这里听到过他的第一声啼哭,雕刻过他的第一个脚印——他是踏莲而生的,他先着地的双脚,让杭州望族钱家越发枝叶繁茂。 开始写.... 行号:0:细雨之中的西湖、盛开的荷花、一座断桥,淡淡几笔,足以勾勒出淡妆浓抹总相宜的杭州。 行号:1:杭州之美,在于留给旁人对美的无尽想象空间。对我们大多数人来说,杭州的美,犹如一幅盛满故事的山水画,诗意、神秘、动情;对于航天之父钱学森来说,杭州于他也是这般美丽。 行号:2:只是直到19岁,他才能借养病之机认识自己家乡的美丽,到底有点迟了。 行号:3:但钱学森一触摸到杭州这幅美卷,便充满不舍和一生的惦念。 行号:4:虽然钱学森年少时因父亲工作调动而辗转生活于北京、上海,长大后为了学业穿梭于北京和大洋彼岸的美国,但杭州,终究是钱学森生命开始的地方,这里听到过他的第一声啼哭,雕刻过他的第一个脚印——他是踏莲而生的,他先着地的双脚,让杭州望族钱家越发枝叶繁茂。 行号:5:人们对杭州的了解,更多地源自曾风靡一时的电视剧《新白娘子传奇》,还有鲁迅笔下那倒掉的雷峰塔。