Java多线程编程笔记9:ReentrantReadWriteLock

171 阅读3分钟

ReentrantLock具有完全互斥排他的效果,也即同一时间只有一个线程在执行ReentrantLock.lock()方法后边的任务。这样做保证了实例变量的线程安全性,但是效率低下。

ReentrantReadWriteLock类是ReadWriteLock接口的实现类,这是一个读写锁。读写锁维护了两个锁,一个是读相关的,也称之为共享锁;另外一个是写相关的,也叫排他锁。多个读锁之间不排斥,读锁与写锁互斥,写锁与写锁互斥。具体而言,就是多个线程可以同时进行读取操作,但是同一时刻只允许一个线程进行写入操作。

ReentarntReadWriteLock特性

  1. 公平性:默认是公平锁,同时支持非公平锁,吞吐量上来看是非公平优于公平
  2. 重入:支持重进入。读线程在获取读锁后,能再次获取读锁。写线程获取了写锁后能再次获取写锁,也能同时获取读锁。
  3. 锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能降级成为读锁

ReentarntReadWriteLock的使用

读读共享示例代码:

class Service{
    private ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
    public void read(){
        try {
            lock.readLock().lock();
            System.out.println("获得读锁 "+Thread.currentThread().getName()+" "+new Date(System.currentTimeMillis()));
            Thread.sleep(50000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.readLock().unlock();
        }
    }
}

class MyThread extends Thread{
    private Service service;
    public MyThread(Service service){
        this.service=service;
    }

    @Override
    public void run() {
        service.read();
    }
}

public class Run {
    public static void main(String[] args) {
        Service service=new Service();
        MyThread a=new MyThread(service);
        a.setName("A");
        MyThread b=new MyThread(service);
        b.setName("B");
        a.start();
        b.start();
    }
}

运行结果:

获得读锁 B Thu Dec 13 16:09:29 CST 2018
获得读锁 A Thu Dec 13 16:09:29 CST 2018

可以发现,二者是近乎同时进行读取的,也就是读读是共享的。也就是说允许了多个线程同时执行lock()方法后面的代码。

对于写存在的情况,修改代码如下:

class Service{
    private ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
    public void read(){
        try {
            lock.readLock().lock();
            System.out.println("获得读锁 "+Thread.currentThread().getName()+" "+new Date(System.currentTimeMillis()));
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.readLock().unlock();
        }
    }

    public void write(){
        try {
            lock.writeLock().lock();
            System.out.println("获得写锁 "+Thread.currentThread().getName()+" "+new Date(System.currentTimeMillis()));
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.writeLock().unlock();
        }
    }
}

class MyReadThread extends Thread{
    private Service service;
    public MyReadThread(Service service){
        this.service=service;
    }

    @Override
    public void run() {
        service.read();
    }
}

class MyWriteThread extends Thread{
    private Service service;
    public MyWriteThread(Service service){
        this.service=service;
    }

    @Override
    public void run() {
        service.write();
    }
}

public class Run {
    public static void main(String[] args) {
        Service service=new Service();
        MyReadThread read1=new MyReadThread(service);
        read1.setName("read1");
        MyReadThread read2=new MyReadThread(service);
        read2.setName("read2");
        MyWriteThread write1=new MyWriteThread(service);
        write1.setName("write1");
        MyWriteThread write2=new MyWriteThread(service);
        write2.setName("write2");
        read1.start();
        write1.start();
        write2.start();
        read2.start();
    }
}

运行结果:

获得写锁 write1 Thu Dec 13 16:16:24 CST 2018
获得读锁 read1 Thu Dec 13 16:16:29 CST 2018
获得写锁 write2 Thu Dec 13 16:16:34 CST 2018
获得读锁 read2 Thu Dec 13 16:16:39 CST 2018

可以看到,四个线程是互斥的执行的,每个线程都是等待了5秒才执行。也就是说,只要出现写操作的过程就是互斥的。

参考资料