Java多线程技术三:锁的使用——使用ReentrantReadWriteLock类

1 概述

        ReentrantLock类具有完全互斥排它的特点,同一时间只有一个线程在执行ReentrantLock.lock()方法后面的任务,这样做保证了同时写实例变量的线程安全性,但 效率是非常低下的。在JDK提供了一种读写锁ReentrantReadWriteLock类,可以在同时进行读操作时不需要同步执行,提升运行速度,加快运行效率。这两个类之间没有继承关系。

        读写锁表示有两个锁,一个是读操作相关的锁,也叫共享锁。另一个是写操作相关的锁,也叫排它锁。读锁之间不互斥,读锁和写锁互斥,写锁之间也互斥,说明只要出现写锁,就会出现互斥同步的效果。读操作是指读取实例变量的值,写操作是指向实例变量写入值。

2 ReentrantLock的缺点

        ReentrantLock类与ReentrantReadWriteLock类相比主要的缺点是使用ReentrantLock对象时,所有的操作都同步,哪怕只对实例变量进行读取操作也会同步处理,这样会耗费大量的时间,降低运行效率。

public class MyService {
    private ReentrantLock lock = new ReentrantLock();
    private String username = "abc";

    public void testMethod1(){
        try {
            lock.lock();
            System.out.println("开始执行 testMethod1 方法的线程名 = " + Thread.currentThread().getName() + ";时间是 = " + Utils.data(System.currentTimeMillis()));
            System.out.println("打印 username = " + username);
            Thread.sleep(4000);
            System.out.println("执行完毕 testMethod1 方法的线程名 = " + Thread.currentThread().getName() + ";时间是 = " + Utils.data(System.currentTimeMillis()));
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}
public class ThreadA extends Thread{
    private MyService service;

    public ThreadA(MyService service) {
        this.service = service;
    }
    @Override
    public void run(){
        service.testMethod1();
    }
}
public class Run1 {
    public static void main(String[] args) {
        MyService service = new MyService();
        ThreadA a = new ThreadA(service);
        a.setName("A");
        a.start();
        ThreadA b = new ThreadA(service);
        b.start();
        b.setName("B");
    }
}

Java多线程技术三:锁的使用——使用ReentrantReadWriteLock类_第1张图片

        从运行时间来看,2个线程读取实例变量共耗时8秒,每个线程占用4秒,非常浪费CPU资源。而读取实例变量的操作可以是同步进行的,也就是读锁之间可以共享。

3 读锁与读锁之间共享

        修改上面MyService.java代码,在运行Run1.java类。

public class MyService {
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private String username = "abc";
    public void testMethod1(){
        try {
            lock.readLock().lock();
            System.out.println("开始执行 testMethod1 方法的线程名 = " + Thread.currentThread().getName() + ";时间是 = " + Utils.data(System.currentTimeMillis()));
            System.out.println("打印 username = " + username);
            Thread.sleep(4000);
            System.out.println("执行完毕 testMethod1 方法的线程名 = " + Thread.currentThread().getName() + ";时间是 = " + Utils.data(System.currentTimeMillis()));
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.readLock().unlock();
        }
    }
}

 Java多线程技术三:锁的使用——使用ReentrantReadWriteLock类_第2张图片

        从控制台打印的时间来看,两个线程机会同时进入lock方法后面的代码,共耗时4秒,说明使用lock.readLock()方法读锁可以提高程序运行效率,允许多个线程同时执行lock方法后面的代码。

        这个实现中如果不使用锁也可以实现异步运行的效果,那么为什么要使用锁呢?这是因为有可能有第三个线程在执行写操作,这时写操作在执行时,这两个读操作就不能与写操作同时运行了,只能在写操作结束后,这两个读操作才能同时运行,避免了出现非线程安全,而且提高了运行效率。

4 写锁与写锁之间互斥

        再次修改上面的MyService.java,然后运行Run1.java。

public class MyService {
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public void testMethod1(){
        try {
            lock.writeLock().lock();
            System.out.println("获得写锁 方法的线程名 = " + Thread.currentThread().getName() + ";时间是 = " + Utils.data(System.currentTimeMillis()));
            Thread.sleep(10000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.writeLock().unlock();
        }
    }
}

 Java多线程技术三:锁的使用——使用ReentrantReadWriteLock类_第3张图片

        使用写锁lock.writeLock()方法的效果就是同一时间只允许一个线程执行lock方法后面的代码。 

5 读写互斥

        

public class MyService {
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public void read(){
        try {
            lock.readLock().lock();
            System.out.println("获得读锁 方法的线程名 = " + Thread.currentThread().getName() + ";时间是 = " + Utils.data(System.currentTimeMillis()));
            Thread.sleep(10000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.readLock().unlock();
        }
    }
    public void write(){
        try {
            lock.writeLock().lock();
            System.out.println("获得写锁 方法的线程名 = " + Thread.currentThread().getName() + ";时间是 = " + Utils.data(System.currentTimeMillis()));
            Thread.sleep(10000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.writeLock().unlock();
        }
    }
}
public class ThreadA extends Thread{
    private MyService service;

    public ThreadA(MyService service) {
        this.service = service;
    }
    @Override
    public void run(){
        service.read();
    }
}
public class ThreadB extends Thread{
    private MyService service;

    public ThreadB(MyService service) {
        this.service = service;
    }
    @Override
    public void run(){
        service.write();
    }
}
public class Run1 {
    public static void main(String[] args) throws InterruptedException {
        MyService service = new MyService();
        ThreadA a = new ThreadA(service);
        a.setName("A");
        a.start();
        Thread.sleep(1000);
        ThreadB b = new ThreadB(service);
        b.start();
        b.setName("B");
    }
}

Java多线程技术三:锁的使用——使用ReentrantReadWriteLock类_第4张图片

        这个实验说明读写操作时互斥的,只要出现写操作的过程,就是互斥的。 

6 写锁与读锁互斥

        修改上面的Run1.java类。

public class Run1 {
    public static void main(String[] args) throws InterruptedException {
        MyService service = new MyService();
        ThreadB b = new ThreadB(service);
        b.setName("B");
        b.start();
        Thread.sleep(1000);
        ThreadA a = new ThreadA(service);
        a.setName("A");
        a.start();
    }
}

 Java多线程技术三:锁的使用——使用ReentrantReadWriteLock类_第5张图片

        从控制台打印的结果看,写锁与读锁也是互斥的。

【总结】读写、写读、写写之间的操作都是互斥的,只有读读时异步非互斥的。 

 

你可能感兴趣的:(java,java,开发语言)