读写锁同样存在着重入问题。简单的读写锁见读写锁浅析。这里我们拿这个简单的写锁来做一个重入测试:
@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 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,这次输出结果就对了:
我是写锁.
我还是写锁.
解放写锁.
解放写锁.
最后我们测试一下实际的应用:
@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\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:人们对杭州的了解,更多地源自曾风靡一时的电视剧《新白娘子传奇》,还有鲁迅笔下那倒掉的雷峰塔。