可重入的读写锁

时间: 2023-12-21 admin IT培训

可重入的读写锁

可重入的读写锁

  读写锁同样存在着重入问题。简单的读写锁见读写锁浅析。这里我们拿这个简单的写锁来做一个重入测试:

    @Testpublic 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 wulf* @since 2019年1月14日*/
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();}/*** 是否允许加读锁** @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;}/*** 是否允许加写锁** @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;}/*** 获取读线程的重入次数** @param readThread* @return*/private int getReadCount(Thread readThread){Integer readCount = readThreads.get(readThread);if (readCount == null){return 0;}return readCount.intValue();}/*** 读线程不为空** @return*/private boolean hasRead(){return readThreads.size() > 0;}/*** 当前线程是否读线程** @param currentThread* @return*/private boolean isRead(Thread currentThread){return readThreads.get(currentThread) != null;}/*** 是否只有一个读线程** @param readThread* @return*/private boolean isOnlyRead(Thread readThread){return readThreads.size() == 1 && readThreads.get(readThread) != null;}/*** 是否存在写线程** @return*/private boolean hasWrite(){return writeThread != null;}/*** 当前线程是否为写线程* @param currentThread* @return*/private boolean isWrite(Thread currentThread){return writeThread == currentThread;}/*** 是否存在写请求** @return*/private boolean hasWriteRequest(){return this.writeRequest > 0;}
}

  我们把之前的测试代码中MyReadWriteLock改为MyReentrantReadWriteLock,这次输出结果就对了:

我是写锁.
我还是写锁.
解放写锁.
解放写锁.

  最后我们测试一下实际的应用:

    @Testpublic 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\n", 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\n", 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:人们对杭州的了解,更多地源自曾风靡一时的电视剧《新白娘子传奇》,还有鲁迅笔下那倒掉的雷峰塔。