JAVA并发编程:读写锁ReentrantReadWriteLock的基本使用及JDK源码中金典案例分析

1、读写锁简介

  之前提到锁(如 Mutex 和ReentrantLock)基本都是排他锁,这些锁在同一时刻只允许一个线程进行访问,而读写锁在同一时刻可以允许多个读线程访问,但是在写线程访问时,所有的读线程和其他写线程均被阻塞。
  读写锁维护了一对锁,一个读锁和一个写锁,读写锁在 ReentrantLock 上进行了拓展,通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大提升。
  除了保证写操作对读操作的可见性以及并发性的提升之外,读写锁能够简化读写交互场景的编程方式。假设在程序中定义一个共享的用作缓存数据结构,它大部分时间提供读服务(例如查询和搜索),而写操作占有的时间很少,但是写操作完成之后的更新需要对后续的读服务可见。
  一般情况下,读写锁的性能都会比排它锁要好,因为大多数场景读是多于写的。在读多于写的情况下,读写锁能够提供比排它锁更好的并发性和吞吐量。

2、读写锁的基本使用

  读写锁的基本使用,使用也是非常简单,跟 ReentrantLock 差不多,唯一不同的是读锁需要调用 readLock() 方法获取,写锁需要调用 writeLock() 方法获取,一般情况下掌握以下几种即可,读读并发、读写互斥、写写互斥、不支持锁升级、支持锁降级、支持锁重入、读锁不支持条件、写锁支持条件。下面给大家一一来分析以上这几种情况的基本使用,示例代码如下。

  • 读读并发
package com.enjoy.lspj.rwl;

import java.text.SimpleDateFormat;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 读写锁的使用
 * 1、读读并发
 * 2、读写互斥
 * 3、写写互斥
 * 4、支持重入
 * 5、不支持锁升级,只能降级。
 * 6、读写锁,读锁不支持条件
 *  读锁的条件直接调用ReentrantReadWriteLock的 newCondition 会直接exception
 *  public Condition newCondition() {
 *     throw new UnsupportedOperationException();
 *  }
 */
public class ReadWriteLock1 {

    // 定义读写锁
    final static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    final static Lock r = lock.readLock();
    final static Lock w = lock.writeLock();
    final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.sss");

    /**
     * 读读并发
     * 执行结果:可以同时输出线程 t1 和 t2 信息
     */
    public void m1() {
        // 读锁
        new Thread(() -> {
            r.lock();

            System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + " get read lock ...");
            try {
                for (int i = 0; i < 5; i++) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + ",i=" + i);
                }
            } finally {
                r.unlock();
            }
        }, "t1").start();


        // 读锁
        new Thread(() -> {
            r.lock();
            System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + " get read lock ...");
            try {
                for (int i = 0; i < 5; i++) {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + ",i=" + i);
                }
            } finally {
                r.unlock();
            }
        }, "t2").start();
    }

    private String getSystemTime(){
        return sdf.format(System.currentTimeMillis());
    }

    public static void main(String[] args)  {
        ReadWriteLock1 rwl = new ReadWriteLock1();
        //1、读读并发
        rwl.m1();
    }

}

执行结果:
在这里插入图片描述
  从执行结果来看,线程 t1 和 t2 在同一时刻都拿到了读锁,后面再打印相应信息。因此,能证明读锁是可以并发执行的。

  • 读写互斥
package com.enjoy.lspj.rwl;

import java.text.SimpleDateFormat;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 读写锁的使用
 * 1、读读并发
 * 2、读写互斥
 * 3、写写互斥
 * 4、支持重入
 * 5、不支持锁升级,只能降级。
 * 6、读写锁,读锁不支持条件
 *  读锁的条件直接调用ReentrantReadWriteLock的 newCondition 会直接exception
 *  public Condition newCondition() {
 *     throw new UnsupportedOperationException();
 *  }
 */
public class ReadWriteLock2 {

    // 定义读写锁
    final static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    final static Lock r = lock.readLock();
    final static Lock w = lock.writeLock();
    final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.sss");


    /**
     * 读写互斥
     * 执行结果:线程 t1 和 t2,谁先拿到锁就先执行完释放锁,第二个线程才可以拿到锁再执行
     */
    public void m2() {
        // 读锁
        new Thread(() -> {
            r.lock();
            System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + " get read lock ...");
            try {
                for (int i = 0; i < 5; i++) {
                    System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + ",i=" + i);
                }
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } finally {
                r.unlock();
            }
        }, "t1").start();


        // 写锁
        new Thread(() -> {
            w.lock();
            System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + " get write lock ...");
            try {
                for (int i = 0; i < 5; i++) {
                    System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + ",i=" + i);
                }
            } finally {
                w.unlock();
            }
        }, "t2").start();
    }


    private String getSystemTime(){
        return sdf.format(System.currentTimeMillis());
    }

    public static void main(String[] args)  {
        ReadWriteLock2 rwl = new ReadWriteLock2();
        //2、读写互斥
        rwl.m2();
    }

}

执行结果:
在这里插入图片描述
  从执行结果来看,线程 t1 和 t2,谁先拿到锁就先执行完释放锁,第二个线程才可以拿到锁再执行。因此,能证明读写锁是互斥执行的。

  • 写写互斥
package com.enjoy.lspj.rwl;

import java.text.SimpleDateFormat;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 读写锁的使用
 * 1、读读并发
 * 2、读写互斥
 * 3、写写互斥
 * 4、支持重入
 * 5、不支持锁升级,只能降级。
 * 6、读写锁,读锁不支持条件
 *  读锁的条件直接调用ReentrantReadWriteLock的 newCondition 会直接exception
 *  public Condition newCondition() {
 *     throw new UnsupportedOperationException();
 *  }
 */
public class ReadWriteLock3 {

    // 定义读写锁
    final static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    final static Lock r = lock.readLock();
    final static Lock w = lock.writeLock();
    final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.sss");


    /**
     * 写写互斥
     * 执行结果:线程 t1 和 t2,谁先拿到锁就先执行完释放锁,第二个线程才可以拿到锁再执行
     */
    public void m3() {
        // 写锁
        new Thread(() -> {
            w.lock();
            System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + " get write lock ...");
            try {
                for (int i = 0; i < 5; i++) {
                    System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + ",i=" + i);
                }
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } finally {
                w.unlock();
            }
        }, "t1").start();


        // 写锁
        new Thread(() -> {
            w.lock();
            System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + " get write lock ...");
            try {
                for (int i = 0; i < 5; i++) {
                    System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + ",i=" + i);
                }
            } finally {
                w.unlock();
            }
        }, "t2").start();
    }


    private String getSystemTime(){
        return sdf.format(System.currentTimeMillis());
    }

    public static void main(String[] args)  {
        ReadWriteLock3 rwl = new ReadWriteLock3();
        //3、写写互斥
        rwl.m3();
    }

}

执行结果:
在这里插入图片描述
  从执行结果来看,线程 t1 和 t2,谁先拿到锁就先执行完释放锁,第二个线程才可以拿到锁再执行。因此,能证明写写锁是互斥执行的。

  • 不支持锁升级
package com.enjoy.lspj.rwl;

import java.text.SimpleDateFormat;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 读写锁的使用
 * 1、读读并发
 * 2、读写互斥
 * 3、写写互斥
 * 4、支持重入
 * 5、不支持锁升级,只能降级。
 * 6、读写锁,读锁不支持条件
 *  读锁的条件直接调用ReentrantReadWriteLock的 newCondition 会直接exception
 *  public Condition newCondition() {
 *     throw new UnsupportedOperationException();
 *  }
 */
public class ReadWriteLock4 {

    // 定义读写锁
    final static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    final static Lock r = lock.readLock();
    final static Lock w = lock.writeLock();
    final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.sss");

    /**
     * 先获取读锁,再获取写锁,不支持锁升级
     */
    public void m4() {
        // 读锁
        new Thread(() -> {
            r.lock();
            try {
                System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + " get read lock ...");
                for (int i = 0; i < 5; i++) {
                    System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + ",i=" + i);
                }
                w.lock();
                try {
                    System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + " get write lock ...");
                    for (int i = 0; i < 5; i++) {
                        System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + ",i=" + i);
                    }

                } finally {
                    w.unlock();
                }
            } finally {
                r.unlock();
            }
        }, "t1").start();
    }

    private String getSystemTime(){
        return sdf.format(System.currentTimeMillis());
    }

    public static void main(String[] args)  {
        ReadWriteLock4 rwl = new ReadWriteLock4();
        //4、不支持锁升级
        rwl.m4();
    }
}

执行结果:
在这里插入图片描述
  从执行结果来看,线程 t1 先获取读锁,再想去获取写锁,一直拿不到,处于阻塞状态。因此,能证明读写锁不支持锁升级。

  • 支持锁降级
package com.enjoy.lspj.rwl;

import java.text.SimpleDateFormat;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 读写锁的使用
 * 1、读读并发
 * 2、读写互斥
 * 3、写写互斥
 * 4、支持重入
 * 5、不支持锁升级,只能降级。
 * 6、读写锁,读锁不支持条件
 *  读锁的条件直接调用ReentrantReadWriteLock的 newCondition 会直接exception
 *  public Condition newCondition() {
 *     throw new UnsupportedOperationException();
 *  }
 */
public class ReadWriteLock5 {

    // 定义读写锁
    final static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    final static Lock r = lock.readLock();
    final static Lock w = lock.writeLock();
    final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.sss");

    /**
     * 支持重入,支持锁降级
     */
    public void m5() {
        // 读锁
        new Thread(() -> {
            w.lock();
            try {
                System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + " get write lock ...");
                for (int i = 0; i < 5; i++) {
                    System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + ",i=" + i);
                }
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                r.lock();
                try {
                    System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + " get read lock ...");
                    for (int i = 0; i < 5; i++) {
                        System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + ",i=" + i);
                    }

                } finally {
                    r.unlock();
                    System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + " release read lock ...");
                }
            } finally {
                w.unlock();
                System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + " release write lock ...");
            }
        }, "t1").start();
    }

    private String getSystemTime(){
        return sdf.format(System.currentTimeMillis());
    }

    public static void main(String[] args) {
        ReadWriteLock5 rwl = new ReadWriteLock5();
        //5、支持锁降级
        rwl.m5();
    }
}

执行结果:
在这里插入图片描述
  从执行结果来看,线程 t1 获取写锁后执行完相关业务逻辑,未释放写锁,可以直接获取读锁执行相关业务。因此,能证明读写锁支持锁降级。

  • 读写锁,支持锁重入
package com.enjoy.lspj.rwl;

import java.text.SimpleDateFormat;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 读写锁的使用
 * 1、读读并发
 * 2、读写互斥
 * 3、写写互斥
 * 4、支持重入
 * 5、不支持锁升级,只能降级。
 * 6、读写锁,读锁不支持条件
 *  读锁的条件直接调用ReentrantReadWriteLock的 newCondition 会直接exception
 *  public Condition newCondition() {
 *     throw new UnsupportedOperationException();
 *  }
 */
public class ReadWriteLock6 {

    // 定义读写锁
    final static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    final static Lock r = lock.readLock();
    final static Lock w = lock.writeLock();
    final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.sss");

    /**
     * 读写锁,支持重入
     */
    public void m6() {
        // 读锁重入
        new Thread(() -> {
            r.lock();
            try {
                System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + " get read lock ...");
                r.lock();
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + " once again get read lock ...");
                } finally {
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    r.unlock();
                    System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + " release read lock ...");
                }
            } finally {
                r.unlock();
                System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + "  release read lock ...");
            }
        }, "t1").start();

        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 写锁重入
        new Thread(() -> {
            w.lock();
            try {
                System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + " get write lock ...");
                w.lock();
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + " once again get write lock ...");
                } finally {
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    w.unlock();
                    System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + " release write lock ...");
                }
            } finally {
                w.unlock();
                System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + "  release write lock ...");
            }
        }, "t2").start();
    }



    private String getSystemTime(){
        return sdf.format(System.currentTimeMillis());
    }

    public static void main(String[] args) throws InterruptedException {
        ReadWriteLock6 rwl = new ReadWriteLock6();
        //6、读写锁,支持重入
        rwl.m6();
    }

}

执行结果:
在这里插入图片描述
  从执行结果来看,线程 t1 先获取一把读锁,执行完相关业务后,在未释放锁的情况下,可以直接再次获取到同一把读锁,线程 t2 也是如此。因此,能证明读写锁是支持重入的。

  • 读写锁,读锁不支持条件
package com.enjoy.lspj.rwl;

import java.text.SimpleDateFormat;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 读写锁的使用
 * 1、读读并发
 * 2、读写互斥
 * 3、写写互斥
 * 4、支持重入
 * 5、不支持锁升级,只能降级。
 * 6、读写锁,读锁不支持条件
 *  读锁的条件直接调用ReentrantReadWriteLock的 newCondition 会直接exception
 *  public Condition newCondition() {
 *     throw new UnsupportedOperationException();
 *  }
 */
public class ReadWriteLock7 {

    // 定义读写锁
    final static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    final static Lock r = lock.readLock();
    final static Lock w = lock.writeLock();
    final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.sss");

    /**
     * 读写锁,读锁不支持条件
     */
    public void m7() {
        // 读锁的条件直接调用ReentrantReadWriteLock的 newCondition 会直接exception
        Condition condition = r.newCondition();
        // 读锁,上面调用就抛异常了,都不会执行到这
        new Thread(() -> {
            r.lock();
            try {
                System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + " get read lock ...");
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } finally {
                r.unlock();
            }
        }, "t1").start();
    }


    private String getSystemTime(){
        return sdf.format(System.currentTimeMillis());
    }

    public static void main(String[] args) throws InterruptedException {
        ReadWriteLock7 rwl = new ReadWriteLock7();
        //7、读写锁,读锁不支持条件
        rwl.m7();
    }

}

执行结果:
在这里插入图片描述
  从执行结果来看,代码执行到 Condition condition = r.newCondition(); 这一行直接抛异常,读锁的条件直接调用 ReentrantReadWriteLock 的 newCondition 会直接exception。因此,能证明读锁不支持条件。

  • 读写锁,写锁支持条件
package com.enjoy.lspj.rwl;

import java.text.SimpleDateFormat;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 读写锁的使用
 * 1、读读并发
 * 2、读写互斥
 * 3、写写互斥
 * 4、支持重入
 * 5、不支持锁升级,只能降级。
 * 6、读写锁,读锁不支持条件
 *  读锁的条件直接调用ReentrantReadWriteLock的 newCondition 会直接exception
 *  public Condition newCondition() {
 *     throw new UnsupportedOperationException();
 *  }
 */
public class ReadWriteLock8 {

    // 定义读写锁
    final static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    final static Lock r = lock.readLock();
    final static Lock w = lock.writeLock();
    final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.sss");

    /**
     * 读写锁,写锁支持条件
     */
    public void m8() {
        Condition condition = w.newCondition();
        // 写锁
        new Thread(() -> {
            w.lock();
            try {
                System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + " get wirte lock ...");
                try {
                	System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + " 即将进入阻塞状态 ...");
                    condition.await();
                    System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + " 被唤醒,执行结束 ...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } finally {
                w.unlock();
            }
        }, "t1").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            w.lock();
            try {
                System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + " get write lock ...");
                condition.signal();
                System.out.println(getSystemTime() + " Thread " + Thread.currentThread().getName() + " 唤醒了 Thread t1 ...");
            } finally {
                w.unlock();
            }
        },"t2").start();
    }

    private String getSystemTime(){
        return sdf.format(System.currentTimeMillis());
    }

    public static void main(String[] args) throws InterruptedException {
        ReadWriteLock8 rwl = new ReadWriteLock8();
        //8、读写锁,写锁支持条件
        rwl.m8();

    }

}

执行结果:
在这里插入图片描述
  从执行结果来看,线程 t1 获取到写锁,执行相关业务逻辑,调用 Condition 的 await() 方法线程进入阻塞状态,t1 释放写锁,t2 获取写锁,调用 Condition 的 signal() 方法唤醒了阻塞队列中的线程 t1 ,t1 拿到写锁继续执行相关业务逻辑。因此,能证明写锁支持条件。

3、JDK源码中金典案例分析

  以下是JDK源码中的一个金典示例,从示例中可以看出 ReentrantReadWriteLock 的使用场景,可以用作缓存,还从示例中可以分析出读写锁的很多特点,比如,读写互斥、不支持锁升级、支持锁降级等特点。接下来,我们来仔细分析一下其实现。

/**
 * JDK 源码中一个金典例子,缓存数据
 */
class CachedData {
    Object data;
    // 缓存是否过期
    volatile boolean cacheValid;
    // 定义一把读写锁
    final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

    // 处理缓存数据的方法
    void processCachedData() {
        // 先获取读锁
        rwl.readLock().lock();
        // 判断缓存是否过期,如果缓存没有过期则调用 use(data);
        if (!cacheValid) {
            // 如果缓存过期了,要去加载真实数据,set 给缓存

            // 由于锁不能升级,在获取写锁之前必须释放读锁
            rwl.readLock().unlock();
            rwl.writeLock().lock();
            try {
                // 重新检查状态,因为其他线程可能已经获得写锁并在我们之前更改了状态。
                if (!cacheValid) {
                    // 实际业务操作,加载真实数据
                    // TODO data = ...
                    cacheValid = true;
                }
                // 在释放写锁之前获取读锁来降级,也就是更新缓存之后接着读取 所以先加锁
                rwl.readLock().lock();
            } finally {
                // 释放写锁,保持读
                rwl.writeLock().unlock(); // Unlock write, still hold read
            }
        }

        try {
            //不管上面的if进不进都会执行这里
            //缓存可用
            // TODO use(data);
        } finally {
            rwl.readLock().unlock();
        }
    }
}

4、总结

  通过上面几个示例对读写锁 ReentrantReadWriteLock 的了解,我们知道读写锁的特点大致可以分为以下几种情况, 读读并发、读写互斥、写写互斥、支持锁重入、支持写锁条件、不支持读锁条件、支持锁降级、不支持锁升级。

  思考一下,读写锁为什么不支持升级?支持降级呢?

  通用对读写锁几个重要特点的了解,如果所有的线程都去申请获取读锁的话,是可以多个线程同时申请读锁成功;如果是申请写锁,那么只能一个线程申请成功,且不可能同时持有读锁和写锁;由于读写锁是互斥锁,线程在申请写锁的时候,需要先等到读锁都释放,才能申请写锁成功。
  假设有 t1,t2 和 t2 三个线程,它们都已持有读锁。假设线程 t1 尝试从读锁升级到写锁。那么它必须等待 t2 和 t3 释放掉已经获取到的读锁。如果随着时间推移,t2 和 t3 由于某种原因逐渐释放了它们的读锁,此时线程 t1 确实是可以成功升级并获取写锁。但是我们考虑一种特殊情况。假设线程 t1 和 t2 都想升级到写锁,那么对于线程 t1 而言,它需要等待其他所有线程,包括线程 t2 在内释放读锁。而线程 t2 也需要等待所有的线程,包括线程 t1 释放读锁。这就是一种非常典型的死锁的情况。谁都愿不愿意率先释放掉自己持有的锁。但是读写锁的升级并不是不可能的,也有可以实现的方案,如果我们保证每次只有一个线程可以升级,那么就可以保证线程安全。只不过最常见的 ReentrantReadWriteLock 对此并不支持。但是锁降级是可以的,因为写锁只有一个线程占有,所以这个时候利用锁的降级是很好的办法,可以提高系统的整体性能。

// 定义读写锁
final static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.sss");

/**
 * 读写锁为什么不能升级?接下来分析以下代码逻辑
 * 结论:会产生死锁
 */
private void test(){
    // 读锁
    lock.readLock().lock(); // 假设有 t1,t2 和 t2 三个线程,它们都已持有了读锁。
    // TODO ; // 比如线程 t1 在执行相关业务逻辑的过程中,线程 t2 t3 来了,获取了读锁,此时三个线程都获取了读锁
    /**
     * 1、假设线程 t1 尝试从读锁升级到写锁。那么它必须等待 t2 和 t3 释放掉已经获取到的读锁。
     * 如果随着时间推移,t2 t3 由于某种原因逐渐释放了它们的读锁,此时线程 t1 确实是可以成功升级并获取写锁。
     *
     * 2、假设线程 t1 和 t2 都想升级到写锁,那么对于线程 t1 而言,它需要等待其他所有线程,包括线程 t2 在内释放读锁。
     * 而线程 t2 也需要等待所有的线程,包括线程 t1 在内释放读锁。谁都不愿意释放掉自己持有的锁,这就是一种非常典型的死锁的情况。
     *
     */
    lock.writeLock().lock(); // t1 等待 t2 和 t3 释放读锁,t2 等待 t1 和 t3 释放读锁
    // TODO ;
    lock.writeLock().unlock();
    lock.readLock().unlock();
}

备注:博主微信公众号,不定期更新文章,欢迎扫码关注。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值