zoukankan      html  css  js  c++  java
  • 【转】java并发编程系列之ReadWriteLock读写锁的使用

    前面我们讲解了Lock的使用,下面我们来讲解一下ReadWriteLock锁的使用,顾明思义,读写锁在读的时候,上读锁,在写的时候,上写锁,这样就很巧妙的解决synchronized的一个性能问题:读与读之间互斥。

    ReadWriteLock也是一个接口,原型如下:

    public interface ReadWriteLock {
        Lock readLock();
        Lock writeLock();
    }

    该接口只有两个方法,读锁和写锁。也就是说,我们在写文件的时候,可以将读和写分开,分成2个锁来分配给线程,从而可以做到读和读互不影响,读和写互斥,写和写互斥,提高读写文件的效率。该接口也有一个实现类ReentrantReadWriteLock,下面我们就来学习下这个类。

    我们先看一下,多线程同时读取文件时,用synchronized实现的效果,代码如下:

    public class ReadAndWriteLock {
        public synchronized void get(Thread thread) {
            System.out.println("start time:"+System.currentTimeMillis());
            for(int i=0; i<5; i++){
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(thread.getName() + ":正在进行读操作……");
            }
            System.out.println(thread.getName() + ":读操作完毕!");
            System.out.println("end time:"+System.currentTimeMillis());
        }
        
        public static void main(String[] args) {
            final ReadAndWriteLock lock = new ReadAndWriteLock();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    lock.get(Thread.currentThread());
                }
            }).start();
            
            new Thread(new Runnable() {
                @Override
                public void run() {
                    lock.get(Thread.currentThread());
                }
            }).start();
        }
    }

    测试结果如下:

    start time:1442459467623
    Thread-0:正在进行读操作……
    Thread-0:正在进行读操作……
    Thread-0:正在进行读操作……
    Thread-0:正在进行读操作……
    Thread-0:正在进行读操作……
    Thread-0:读操作完毕!
    end time:1442459467723
    start time:1442459467723
    Thread-1:正在进行读操作……
    Thread-1:正在进行读操作……
    Thread-1:正在进行读操作……
    Thread-1:正在进行读操作……
    Thread-1:正在进行读操作……
    Thread-1:读操作完毕!
    end time:1442459467823

    整个过程耗时200ms

    我们可以看到,及时是在读取文件,在加了synchronized关键字之后,读与读之间,也是互斥的,也就是说,必须等待Thread-0读完之后,才会轮到Thread-1线程读,而无法做到同时读文件,这种情况在大量线程同时都需要读文件的时候,读写锁的效率,明显要高于synchronized关键字的实现。下面我们来测试一下,代码如下:

    public class ReadAndWriteLock {
        ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
        public void get(Thread thread) {
            lock.readLock().lock();
            try{
                System.out.println("start time:"+System.currentTimeMillis());
                for(int i=0; i<5; i++){
                    try {
                        Thread.sleep(20);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(thread.getName() + ":正在进行读操作……");
                }
                System.out.println(thread.getName() + ":读操作完毕!");
                System.out.println("end time:"+System.currentTimeMillis());
            }finally{
                lock.readLock().unlock();
            }
        }
        
        public static void main(String[] args) {
            final ReadAndWriteLock lock = new ReadAndWriteLock();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    lock.get(Thread.currentThread());
                }
            }).start();
            
            new Thread(new Runnable() {
                @Override
                public void run() {
                    lock.get(Thread.currentThread());
                }
            }).start();
        }
    }

    测试结果如下:

    start time:1442460030593
    start time:1442460030593
    Thread-1:正在进行读操作……
    Thread-0:正在进行读操作……
    Thread-1:正在进行读操作……
    Thread-0:正在进行读操作……
    Thread-1:正在进行读操作……
    Thread-0:正在进行读操作……
    Thread-1:正在进行读操作……
    Thread-0:正在进行读操作……
    Thread-1:正在进行读操作……
    Thread-0:正在进行读操作……
    Thread-0:读操作完毕!
    Thread-1:读操作完毕!
    end time:1442460030693
    end time:1442460030693

    整个过程耗时:100ms

    从测试结果来看,Thread-0和Thread-1是在同时读取文件。

    通过两次实验的对比,我们可以看出来,读写锁的效率明显高于synchronized关键字

    不过要注意的是,如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁。如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁。读锁和写锁是互斥的。

    下面我们来验证下读写锁的互斥关系,代码如下:

    public class ReadAndWriteLock {
       ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
        public static void main(String[] args) {
            final ReadAndWriteLock lock = new ReadAndWriteLock();
        // 建N个线程,同时读
            ExecutorService service = Executors.newCachedThreadPool();
            service.execute(new Runnable() {
                @Override
                public void run() {
                    lock.readFile(Thread.currentThread());
                }
            });
            // 建N个线程,同时写
            ExecutorService service1 = Executors.newCachedThreadPool();
            service1.execute(new Runnable() {
                @Override
                public void run() {
                    lock.writeFile(Thread.currentThread());
                }
            });
        }
        // 读操作
        public void readFile(Thread thread){
            lock.readLock().lock();
            boolean readLock = lock.isWriteLocked();
            if(!readLock){
                System.out.println("当前为读锁!");
            }
            try{
                for(int i=0; i<5; i++){
                    try {
                        Thread.sleep(20);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(thread.getName() + ":正在进行读操作……");
                }
                System.out.println(thread.getName() + ":读操作完毕!");
            }finally{
             System.out.println("释放读锁!");
                lock.readLock().unlock();
            }
        }
        // 写操作
        public void writeFile(Thread thread){
            lock.writeLock().lock();
            boolean writeLock = lock.isWriteLocked();
            if(writeLock){
                System.out.println("当前为写锁!");
            }
            try{
                for(int i=0; i<5; i++){
                    try {
                        Thread.sleep(20);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(thread.getName() + ":正在进行写操作……");
                }
                System.out.println(thread.getName() + ":写操作完毕!");
            }finally{
             System.out.println("释放写锁!");
                lock.writeLock().unlock();
            }
        }
    }

    测试结果如下:

    // 读锁和读锁测试结果:
    当前为读锁!
    当前为读锁!
    pool-2-thread-1:正在进行读操作……
    pool-1-thread-1:正在进行读操作……
    pool-2-thread-1:正在进行读操作……
    pool-1-thread-1:正在进行读操作……
    pool-2-thread-1:正在进行读操作……
    pool-1-thread-1:正在进行读操作……
    pool-2-thread-1:正在进行读操作……
    pool-1-thread-1:正在进行读操作……
    pool-1-thread-1:正在进行读操作……
    pool-2-thread-1:正在进行读操作……
    pool-1-thread-1:读操作完毕!
    pool-2-thread-1:读操作完毕!
    释放读锁!
    释放读锁!
    // 测试结果不互斥
     
    // 读锁和写锁,测试结果如下:
    当前为读锁!
    pool-1-thread-1:正在进行读操作……
    pool-1-thread-1:正在进行读操作……
    pool-1-thread-1:正在进行读操作……
    pool-1-thread-1:正在进行读操作……
    pool-1-thread-1:正在进行读操作……
    pool-1-thread-1:读操作完毕!
    释放读锁!
    当前为写锁!
    pool-2-thread-1:正在进行写操作……
    pool-2-thread-1:正在进行写操作……
    pool-2-thread-1:正在进行写操作……
    pool-2-thread-1:正在进行写操作……
    pool-2-thread-1:正在进行写操作……
    pool-2-thread-1:写操作完毕!
    释放写锁!
    // 测试结果互斥
     
    // 写锁和写锁,测试结果如下:
    当前为写锁!
    pool-1-thread-1:正在进行写操作……
    pool-1-thread-1:正在进行写操作……
    pool-1-thread-1:正在进行写操作……
    pool-1-thread-1:正在进行写操作……
    pool-1-thread-1:正在进行写操作……
    pool-1-thread-1:写操作完毕!
    释放写锁!
    当前为写锁!
    pool-2-thread-1:正在进行写操作……
    pool-2-thread-1:正在进行写操作……
    pool-2-thread-1:正在进行写操作……
    pool-2-thread-1:正在进行写操作……
    pool-2-thread-1:正在进行写操作……
    pool-2-thread-1:写操作完毕!
    释放写锁!
    // 测试结果互斥

    关于锁的内容我们就讲到这里,下面对锁的相关概念做一个介绍:

    1.可重入(Reentrant)锁

    如果锁具备可重入性,则称作为可重入锁。像synchronized和 ReentrantLock都是可重入锁,可重入性在我看来实际上表明了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。举个简单的例子,当一 个线程执行到某个synchronized方法时,比如说method1,而在method1中会调用另外一个synchronized方法 method2,此时线程不必重新去申请锁,而是可以直接执行方法method2。

    看下面这段代码就明白了:

    classMyClass {
        public synchronized void method1() {
            method2();
        }
        public synchronized void method2() {
        }
    }

    上述代码中的两个方法method1和method2都用synchronized修饰了,假如某一时刻,线程A执行到了method1,此时线程 A获取了这个对象的锁,而由于method2也是synchronized方法,假如synchronized不具备可重入性,此时线程A需要重新申请 锁。但是这就会造成一个问题,因为线程A已经持有了该对象的锁,而又在申请获取该对象的锁,这样就会线程A一直等待永远不会获取到的锁。  而由于synchronized和Lock都具备可重入性,所以不会发生上述现象。

    2.可中断锁

      可中断锁:顾名思义,就是可以相应中断的锁。

      在Java中,synchronized就不是可中断锁,而Lock是可中断锁。

      如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。

      3.公平锁

      公平锁即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。

      非公平锁即无法保证锁的获取是按照请求锁的顺序进行的。这样就可能导致某个或者一些线程永远获取不到锁。

      在Java中,synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。

    而对于ReentrantLock和ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。设置方法如下:ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);

    4、读写锁

    前面已经介绍,这里不做赘述

    --------------------- 本文来自 牛奋lch 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/liuchuanhong1/article/details/53539341?utm_source=copy 

  • 相关阅读:
    C++ 类
    百练3383:Cell Phone Network
    百练4092:牛仔裤
    百练4091:最近餐馆
    百练4090:超级备忘录
    百练2868:牛顿迭代
    百练2812:恼人的青蛙
    百练2805:正方形
    bzoj1856 [Scoi2010]字符串
    bzoj2096 [Poi2010]Pilots
  • 原文地址:https://www.cnblogs.com/modou/p/9755406.html
Copyright © 2011-2022 走看看