1 为什么读写分离
对于共享资源的读取是没有必要串行化,读取不会涉及对资源的修改,而写入操作是必须要保证线程安全的,
所以多线程下,如果读写分离,则可以提高效率,读数据是的是可以并行的,只有写的时候加锁串行
我们更多需求更多的时候就是读取的时候,允许多个线程去读取,但是在写如数据的时候,只能有一个线程去写入,其他线程不能进行读取和写入
2 实现
package study.wyy.concurrency.readWriteLock;
public class ReadWriteLock {
/**
* @Description: 当前正在读取数据线程数量
*/
private int readingThreads = 0;
/**
* @Description: 等待去读取数据的线程的数量, 但是读取不了,放到了wait队列中
*/
private int waitReadThreads = 0;
/**
* @Description: 正在写入数据的线程数量 只有一个
*/
private int writingThreads = 0;
/**
* @Description: 记录当前有多少个线程想要写入,但是写入不了,放到了wait队列中,在等待其他释放锁
*/
private int waitWriteThreads = 0;
/**
* @author: wyaoyao
* @Date: 2020/9/6 6:10 下午
* @Description: 加读锁
*/
public synchronized void readLock() throws InterruptedException {
// 获取锁的时候,有可能获取不到锁,进入到等待队列(waitSet)中, 所以waitReadThreads可能需要加1的
this.waitReadThreads++;
try {
while (writingThreads > 0) {
// 当前有线程正在进行写入的时候,肯定是不能这个读取数据的线程肯定是不能进行读取数据的,进入等待
// this.waitReadThreads++; 这个放在这里不合适,需要立马进入等待,就放到外面,但是可能会记录的数量不对
this.wait();
}
// 此时没有线程进行写入数据,就可以读取数据了, 正在读取数据的线程数加1
this.readingThreads++;
} finally {
// 最终释放锁的时候,要减减waitingReaders
this.waitReadThreads--;
}
}
/**
* @author: wyaoyao
* @Date: 2020/9/6 6:10 下午
* @Description: 释放读锁
*/
public synchronized void readUnLock() throws InterruptedException {
// 1 读取数据的线程的数量减1
this.readingThreads--;
// 2 释放
this.notifyAll();
}
/**
* @author: wyaoyao
* @Date: 2020/9/6 6:10 下午
* @Description: 加写锁
*/
public synchronized void writeLock() throws InterruptedException {
// 获取锁的时候,有可能获取不到锁,进入到等待队列(waitSet)中, 所以waitWriteThreads可能需要加1的
this.waitWriteThreads++;
try {
while (readingThreads > 0 || writingThreads > 0){
// 当有其他线程在进行读取或者写入的时候,则当前线程不能进行写操作,只能等待
this.wait();
}
// 没有其他线程在进行读取或者写入的时候,那就可以进行写入
this.writingThreads++;
} finally {
// 最终释放锁的时候,要减减waitWriteThreads
this.waitWriteThreads--;
}
}
/**
* @author: wyaoyao
* @Date: 2020/9/6 6:10 下午
* @Description: 释放写锁
*/
public synchronized void unWriteLock(){
this.writingThreads--;
this.notifyAll();
}
}
3 测试
- 定义数据
package study.wyy.concurrency.readWriteLock;
/**
* @Description: 共享数据
* @Author wyaoyao
* @Date 2020/9/6 5:22 下午
* @Param
* @Return
* @Exception
*/
public class ShareData {
// 定义一个buffer,从里面读取数据和写入数据
private final char[] buffer;
// 定义一个锁
private final ReadWriteLock lock = new ReadWriteLock();
public ShareData(int size) {
this.buffer = new char[size];
// 初始化数据
for (int i = 0; i < size; i++) {
this.buffer[i] = '*';
}
}
/**
* @author: wyaoyao
* @Date: 2020/9/6 5:27 下午
* @Description: 读取数据的方法
*/
public char[] read() throws InterruptedException {
try {
// 1 加读锁
lock.readLock();
// 2 读取数据
return doRead();
}finally {
// 释放锁
lock.readUnLock();
}
}
private char[] doRead() {
char[] temp = new char[buffer.length];
for (int i =0; i<buffer.length;i++){
temp[i] = buffer[i];
}
// 读取结束之后,休眠一会
sleep(50);
return temp;
}
private void sleep(int mills) {
try {
Thread.sleep(mills);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void write(char c) throws InterruptedException {
try {
// 1 加写锁
lock.writeLock();
// 2 写数据
doWrite(c);
}finally {
// 释放锁
lock.unWriteLock();
}
}
private void doWrite(char c) {
for (int i =0; i<buffer.length;i++){
buffer[i] = c;
sleep(10);
}
}
}
- 定义读取数据的线程
package study.wyy.concurrency.readWriteLock;
/**
* @Description : 数据写入线程
* @Author wyaoyao
* @Date 2020/9/6 6:05 下午
* @Param
* @Return
* @Exception
*/
public class ReadThread extends Thread{
private final ShareData shareData;
public ReadThread(ShareData shareData) {
this.shareData = shareData;
}
@Override
public void run() {
try{
while (true){
char[] data = shareData.read();
System.out.println(Thread.currentThread().getName() + " read " + String.valueOf(data));
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
- 定义写入数据的线程
package study.wyy.concurrency.readWriteLock;
import java.util.Random;
/**
* @Description : 数据读取线程
* @Author wyaoyao
* @Date 2020/9/6 6:05 下午
* @Param
* @Return
* @Exception
*/
public class WriterThread extends Thread{
private static final Random random = new Random(System.currentTimeMillis());
private final ShareData shareData;
// 写入内容
private final String content;
private int index = 0;
public WriterThread(ShareData shareData, String content) {
this.shareData = shareData;
this.content = content;
}
/**
* @author: wyaoyao
* @Date: 2020/9/6 6:45 下午
* @Description:
* 这么写有问题
*/
// @Override
// public void run() {
// while (true){
// char c = nextChar();
// try {
// shareData.write(c);
// // 如果在里中断,下次醒过来还可能从这里进行
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
// }
@Override
public void run() {
// 在这里,如果中断,就会进入到catch,不用自己break,
// 因为run方法已经结束了
try{
while (true){
char c = nextChar();
shareData.write(c);
Thread.sleep(random.nextInt(1000));
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
/**
* @author: wyaoyao
* @Date: 2020/9/6 6:41 下午
* @Description: 获取content中的下一个字符
*/
private char nextChar(){
char c = content.charAt(index);
index++;
if(index>=content.length()){
// 如果index大于content的长度,就重置一下
index = 0;
}
return c;
}
}
- 测试类
package study.wyy.concurrency.readWriteLock;
/**
* @Description 测试
* @Author wyaoyao
* @Date 2020/9/6 6:52 下午
* @Param
* @Return
* @Exception
*/
public class ReadWriteLockTest {
public static void main(String[] args) {
final ShareData shareData = new ShareData(10);
new ReadThread(shareData).start();
new ReadThread(shareData).start();
new ReadThread(shareData).start();
new ReadThread(shareData).start();
new WriterThread(shareData,"qwertyuiop").start();
new WriterThread(shareData,"QWERTYUIOP").start();
}
}
运行结果发现,大部分是读取数据的线程抢到执行权,这也很正常,我们声明了4个读取数据的线程
4 改进
package study.wyy.concurrency.readWriteLock;
public class ReadWriteLock {
/**
* @Description: 当前正在读取数据线程数量
*/
private int readingThreads = 0;
/**
* @Description: 等待去读取数据的线程的数量, 但是读取不了,放到了wait队列中
*/
private int waitReadThreads = 0;
/**
* @Description: 正在写入数据的线程数量 只有一个
*/
private int writingThreads = 0;
/**
* @Description: 记录当前有多少个线程想要写入,但是写入不了,放到了wait队列中,在等待其他释放锁
*/
private int waitWriteThreads = 0;
/**
* @Description: 是否写入优先级更高
*/
private boolean preferWrite;
public ReadWriteLock(boolean preferWrite) {
this.preferWrite = preferWrite;
}
public ReadWriteLock() {
// 默认 true
this(true);
}
/**
* @author: wyaoyao
* @Date: 2020/9/6 6:10 下午
* @Description: 加读锁
*/
public synchronized void readLock() throws InterruptedException {
// 获取锁的时候,有可能获取不到锁,进入到等待队列(waitSet)中, 所以waitReadThreads可能需要加1的
this.waitReadThreads++;
try {
while (writingThreads > 0 || (preferWrite && this.waitWriteThreads>0)) {
// 当前有线程正在进行写入的时候,肯定是不能这个读取数据的线程肯定是不能进行读取数据的,进入等待
// 如果更偏爱写入,并且有写入的线程数量大于0,也让读取的线程进入等待
// this.waitReadThreads++; 这个放在这里不合适,需要立马进入等待,就放到外面,但是可能会记录的数量不对
this.wait();
}
// 此时没有线程进行写入数据,就可以读取数据了, 正在读取数据的线程数加1
this.readingThreads++;
} finally {
// 最终释放锁的时候,要减减waitingReaders
this.waitReadThreads--;
}
}
/**
* @author: wyaoyao
* @Date: 2020/9/6 6:10 下午
* @Description: 释放读锁
*/
public synchronized void readUnLock() throws InterruptedException {
// 1 读取数据的线程的数量减1
this.readingThreads--;
// 2 释放
this.notifyAll();
}
/**
* @author: wyaoyao
* @Date: 2020/9/6 6:10 下午
* @Description: 加写锁
*/
public synchronized void writeLock() throws InterruptedException {
// 获取锁的时候,有可能获取不到锁,进入到等待队列(waitSet)中, 所以waitWriteThreads可能需要加1的
this.waitWriteThreads++;
try {
while (readingThreads > 0 || writingThreads > 0){
// 当有其他线程在进行读取或者写入的时候,则当前线程不能进行写操作,只能等待
this.wait();
}
// 没有其他线程在进行读取或者写入的时候,那就可以进行写入
this.writingThreads++;
} finally {
// 最终释放锁的时候,要减减waitWriteThreads
this.waitWriteThreads--;
}
}
/**
* @author: wyaoyao
* @Date: 2020/9/6 6:10 下午
* @Description: 释放写锁
*/
public synchronized void unWriteLock(){
this.writingThreads--;
this.notifyAll();
}
}