文章目录

  • Java多线程相关知识【12】--设计模式--读写锁模式(读写模式)
  • 1.问题的引入
  • 产生问题的代码
  • 2.解决方案
  • 解决的原理
  • 代码实现
  • 读写锁的实现
  • 3.进阶解决方案
  • 问题的引入
  • 解决的原理
  • 代码实现
  • 读写锁
  • 共享数据
  • 读线程
  • 写线程
  • 读写锁的缺陷
  • 缺陷的分析
  • 缺陷的解决
  • 解决的实现代码
  • 读写锁改进


Java多线程相关知识【12】–设计模式–读写锁模式(读写模式)

1.问题的引入

假设现在有一个景点,而这个景点也只有一个验票员,他的工作就是拿到客户的票,撕掉门票的副票,把除了副票的部分还给游客。在平常的时候,客流量并不是很大,所以,他并不会出错。

然而某天,这个景点突然受到了特别高的宣传,一下子有很多人都蜂拥到了这个景点。

随着客流量的加大,和长时间的疲劳工作,这个验票员终于没有办法顶住客流量的考验,有的游客直接把票交给了他,而他并没有及时的把票还给游客,而后来的游客又将他的票递了上来,然后,接下来,他的工作就乱了套了,一下子就手忙脚乱了起来。

由于出现了工作的失误,他被老板扣了工资。

而在Java多线程中,这样的问题可能是由于多个线程同时操作共享数据时,产生的问题。

产生问题的代码

/**
 * 未加锁而多线程同时操作共享变量
 */
public class NoLockToWork {
    private String mainData;
    private String lastData;
    private int i=0;
    public  void check(String d,String f) throws InterruptedException {
        mainData=d;
        lastData=f;
        i++;
        verify();
    }

    private void verify() throws InterruptedException {
        if(!mainData.equals(lastData)){
            System.out.print("***********error*************");
            System.out.println("NO."+i+"  "+mainData+"  is  "+lastData);
            wait();
        }


    }

    public static void main(String[] args) {
        NoLockToWork work=new NoLockToWork();
        new Thread(()->{
            while (true){
                try {
                    work.check("10000","10000");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(()->{
            while (true){
                try {
                    work.check("20000","20000");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(()->{
            while (true){
                try {
                    work.check("30000","30000");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

}

2.解决方案

解决的原理

就上面问题的出现,主管人员想到了一个解决办法,即主管拿着扣下验票员的工资,又请了多个警卫,在园区的各个位置,同时,安排了两个警卫,帮助验票员进行验票的秩序维护。

在java中,我们就可以对相应的需要读写的进程进行加锁,从而进行保护。

代码实现

读写锁的实现
/**
 * 加线程锁后代码将不会出错
 */
public class AddLockToWork {
    private String mainData;
    private String lastData;
    private int i=0;
    public  void check(String d,String f) throws InterruptedException {
        mainData=d;
        lastData=f;
        i++;
        verify();
    }

    //在此位置加锁(对函数加锁)
    private synchronized void verify() throws InterruptedException {
        if(!mainData.equals(lastData)){
            System.out.print("***********error*************");
            System.out.println("NO."+i+"  "+mainData+"  is  "+lastData);
            wait();
        }


    }

    public static void main(String[] args) {
        NoLockToWork work=new NoLockToWork();
        new Thread(()->{
            while (true){
                try {
                    work.check("10000","10000");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(()->{
            while (true){
                try {
                    work.check("20000","20000");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(()->{
            while (true){
                try {
                    work.check("30000","30000");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

3.进阶解决方案

问题的引入

就景点事件继续说起,由于进入园区后,每个人参观一个景点时,警卫都会严格的维护秩序,这也就使得了园区内部每个景点每天的游客量急剧减少,这也就愁坏了经理。

解决的原理

经理仔细的分析了产生这种情况的问题,由于园区内部,人们参观景点时,并不需要再次查票,而使用大量的保安继续维护秩序将会降低效率,固经理减少了园区内景点前的保安工作。

在java中,这种情况的产生就相当于有读数据的程序,也有写数据的程序,而多个人是可以同时读的,然而,同时只能有一个人进行数据的写操作。

具体可进行的操作如下:

正在运行的程序性质

将要运行的程序性质

可读

是否要加读锁

可写

是否要加写锁

读操作

读操作

-

-

写操作

-

-

写操作

读操作

-

-

写操作

-

-

代码实现

读写锁
/**
 * 读写锁核心
 */
public class ReadWriteLock {
    /**
     * 等待读的进程数
     */
    private int waitReading = 0;
    /**
     * 工作读的进程数
     */
    private int workReading = 0;
    /**
     * 等待写的进程数
     */
    private int waitWriting = 0;
    /**
     * 工作写的进程数(最多只有一个)
     */
    private int workWriting = 0;

    /**
     * 读锁,允许同时读
     *
     * @throws InterruptedException 中断异常
     */
    public synchronized void readLock() throws InterruptedException {
        try {
            waitReading++;
            while (workWriting > 0)
                this.wait();
            workReading++;
        } finally {
            waitReading--;
        }

    }

    /**
     * 解除读锁
     */
    public synchronized void readUnLock() {
        workReading--;
        this.notifyAll();
    }

    /**
     * 写锁,允许单一写
     * @throws InterruptedException
     */
    public synchronized void writLock() throws InterruptedException {
        try {
            waitWriting++;
            while (workWriting > 0 || workReading > 0)
                this.wait();
            workWriting++;
        } finally {
            waitWriting--;
        }
    }

    /**
     * 解除写锁
     */
    public synchronized void writUnLock() {
        workWriting--;
        this.notifyAll();
    }

}
共享数据
public class ReadWriteShareData {
    private final ReadWriteLock LOCK = new ReadWriteLock();


    private int data = 0;

    public void read() throws InterruptedException {
        LOCK.readLock();
        IntStream.rangeClosed(1, 10).forEach(i -> {
            if (i == 10)
                System.out.println();
            else
                System.out.print(data);
        });
        Thread.sleep(100);
        LOCK.readUnLock();

    }

    public void write(int data) throws InterruptedException {
        LOCK.writLock();
        this.data=data;
        LOCK.writUnLock();
    }

}
读线程
public class UsingReadWriteLockReader <T> extends Thread {
    private T data = null;

    public UsingReadWriteLockReader(T data) {
        this.data = data;
    }

    @Override
    public void run() {
        try {
            while (true){
                if(data.getClass()==ReadWriteShareData.class)
                    ((ReadWriteShareData)data).read();
                else if (data.getClass()==ReadWriteShareDataWriteFirst.class)
                    ((ReadWriteShareDataWriteFirst)data).read();
                else
                    System.out.println("error");
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
写线程
public class UsingReadWriteLockWriter<T> extends Thread {

    private T data = null;
    private int writeData = 0;


    public UsingReadWriteLockWriter(T data, int writeData) {
        this.data = data;
        this.writeData = writeData;
    }


    @Override
    public void run() {
        try {
            while (true)
                if(data.getClass()==ReadWriteShareData.class)
                    ((ReadWriteShareData)data).write(writeData);
                else if (data.getClass()==ReadWriteShareDataWriteFirst.class)
                    ((ReadWriteShareDataWriteFirst)data).write(writeData);
                else
                    System.out.println("error");
        } catch (InterruptedException e) {
            // e.printStackTrace();
        }
    }
}

读写锁的缺陷

缺陷的分析

由于读线程的有限级别较高,造成了写线程总是无法进行线程的修改,这将会导致读线程一直占用文件,而无法进行写,导致无法更新数据。

缺陷的解决

为了解决以上问题,可为写线程添加一个优先标识位,这样即可解决相关问题。

解决的实现代码
读写锁改进
/**
 * 读写锁核心,添加写优先
 */
public class ReadWriteLockWriteFirst {
    /**
     * 等待读的进程数
     */
    private int waitReading = 0;
    /**
     * 工作读的进程数
     */
    private int workReading = 0;
    /**
     * 等待写的进程数
     */
    private int waitWriting = 0;
    /**
     * 工作写的进程数(最多只有一个)
     */
    private int workWriting = 0;
    /**
     * 读写优先级
     */
    private boolean writerFirst=false;

    public ReadWriteLockWriteFirst() {
        this(true);
    }

    public ReadWriteLockWriteFirst(boolean writerFirst) {
        this.writerFirst = writerFirst;
    }

    /**
     * 读锁,允许同时读
     *
     * @throws InterruptedException 中断异常
     */
    public synchronized void readLock() throws InterruptedException {
        try {
            waitReading++;
            //写锁修改点
            while (workWriting > 0||(writerFirst&&waitWriting>0))
                this.wait();
            workReading++;
        } finally {
            waitReading--;
        }

    }

    /**
     * 解除读锁
     */
    public synchronized void readUnLock() {
        workReading--;
        this.notifyAll();
    }

    /**
     * 写锁,允许单一写
     * @throws InterruptedException
     */
    public synchronized void writLock() throws InterruptedException {
        try {
            waitWriting++;
            while (workWriting > 0 || workReading > 0)
                this.wait();
            workWriting++;
        } finally {
            waitWriting--;
        }
    }

    /**
     * 解除写锁
     */
    public synchronized void writUnLock() {
        workWriting--;
        this.notifyAll();
    }

}