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

发布时间:2023年12月20日

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");
    }
}

944709e435ac4548a629271888451c94.png

? ? ? ? 从运行时间来看,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();
        }
    }
}

?856326abe2064754bb105ba208473f81.png

? ? ? ? 从控制台打印的时间来看,两个线程机会同时进入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();
        }
    }
}

?84866316f73d46b38c70b47549f27dc4.png

? ? ? ? 使用写锁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");
    }
}

195bc3bdf3384af0adbdbabf3de3e45c.png

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

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();
    }
}

?929e7d9ccdf84fcaa18c247ccbba5af1.png

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

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

?

文章来源:https://blog.csdn.net/geminigoth/article/details/135046367
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。