juc并发编程05——读写锁

简介: 可重入锁是一种排他锁,同一时间只允许一个线程操作竞争资源。读写锁是针对读、写场景设计的,允许多个线程同时持有锁。读写锁维护了一个读锁和一个写锁。其机制如下:

可重入锁是一种排他锁,同一时间只允许一个线程操作竞争资源。读写锁是针对读、写场景设计的,允许多个线程同时持有锁。读写锁维护了一个读锁和一个写锁。其机制如下:


没有其它线程占用写锁的情况下,同一时间可以有多个线程加读锁。

没有任意线程占用读锁的情况下, 同一时间只有一个线程可以加写锁。简单总结就是要么读,要么写,允许多个线程同时读,只允许一个线程单独写。看看源码。

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

原来这是一个接口,它本身并不是锁对象,只是维护了一个读锁(readLock),一个写锁(writeLock)。其实现类是ReentrantReadWriteLock.

public class Demo16 {
    public static void main(String[] args) {
        ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        readWriteLock.readLock().lock();
        new Thread(readWriteLock.readLock()::lock).start();
    }
}

上面程序可以正常退出,因为两个线程可以同时获取读锁,并不会hang住。

public class Demo16 {
    public static void main(String[] args) {
        ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        readWriteLock.writeLock().lock();
        new Thread(readWriteLock.readLock()::lock).start();
    }
}

上面代码则不能正常退出,因为主线程使用了写锁,被创建的子线程不允许获取读锁。


ReentrantReadWriteLock不仅拥有读、写锁的功能,还保留了写锁的可重入锁、公平|非公平锁的机制。

public class Demo17 {
    public static void main(String[] args) {
        ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        readWriteLock.writeLock().lock();
        readWriteLock.writeLock().lock();
        new Thread(() -> {
            System.out.println("thread2 try to lock");
            readWriteLock.writeLock().lock();
            System.out.println("thread2 lock successfully");
        }).start();
        readWriteLock.writeLock().unlock();
        System.out.println("thread1 unlock one time");
        readWriteLock.writeLock().unlock();
        System.out.println("thread2 unlock twice");
    }
}

输出为。

thread1 unlock one time
thread2 try to lock
thread2 unlock twice
thread2 lock successfully

注意读锁不具备上述机制,因为它根本不是排他锁。公平锁的简单演示如下。

public class Demo18 {
    public static void main(String[] args) {
        ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
        Runnable action = () -> {
            System.out.println("thread" + Thread.currentThread().getName() + "try to lock");
            lock.writeLock().lock();
            System.out.println("thread" + Thread.currentThread().getName() + " lock successfully");
            lock.writeLock().unlock();
        };
        for (int i = 0; i < 10; i++) {
            new Thread(action).start();
        }
    }
}
输出如下。
threadThread-0try to lock
threadThread-2try to lock
threadThread-1try to lock
threadThread-0 lock successfully
threadThread-2 lock successfully
threadThread-3try to lock
threadThread-1 lock successfully
threadThread-4try to lock
threadThread-3 lock successfully
threadThread-5try to lock
threadThread-6try to lock
threadThread-4 lock successfully
threadThread-5 lock successfully
threadThread-6 lock successfully
threadThread-8try to lock
threadThread-7try to lock
threadThread-8 lock successfully
threadThread-7 lock successfully
threadThread-9try to lock
threadThread-9 lock successfully

真的很简单。下面讲点有意思的:锁降级和锁升级。先回顾下我们对读写锁机制的描述:


没有其它线程占用写锁的情况下,同一时间可以有多个线程加读锁。

发现没有,我们用的是其它,而不是任意。说人话就是,如果线程A持有写锁,其它线程就不允许持有读锁,A线程却可以。

public class Demo19 {
    public static void main(String[] args) {
        ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
        lock.writeLock();
        lock.readLock();
        System.out.println("get read lock...");
    }
}

输出如下。

get read lock...
1

不过上面的例子中写锁与读锁的顺序不能反。再看之前我的概念也是这么写的。


没有任意线程占用读锁的情况下, 同一时间只有一个线程可以加写锁。

为什么呢?其实是因为写锁可以降级为读锁。换句话说,我本来是写锁,看见是自己人来了(同一个线程想获取读锁),我把自己降级读锁,允许你进来和我玩。但是如果是读锁,即使我看见自己人来了,我也没有办法升级成为写锁阿。毕竟降级简单升级难。


看下列代码。

public class Demo20 {
    public static void main(String[] args) throws InterruptedException {
        ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        readWriteLock.writeLock().lock();
        readWriteLock.readLock().lock();
        new Thread(() -> {
            System.out.println("thrad2 to get read lock");
            readWriteLock.readLock().lock();
            System.out.println("thrad2 get read lock succussfully");
        }).start();
        TimeUnit.SECONDS.sleep(1);
        System.out.println("thread1 unlock write lock");
        readWriteLock.writeLock().unlock();
    }
}

结果是。


thrad2 to get read lock
thread1 unlock write lock
thrad2 get read lock succussfully

原来写锁的降级只是一瞬间,就是在持有写锁的情况下同一线程想获取读锁的那一瞬间。


相关文章
|
8月前
|
安全 Java 编译器
高并发编程之什么是 JUC
高并发编程之什么是 JUC
68 1
|
8月前
|
Java
JUC并发编程之等待唤醒机制
在JUC(Java Util Concurrent)并发编程中,线程等待唤醒机制是实现线程之间协作和同步的重要手段。这种机制允许一个线程挂起等待某个条件满足后被唤醒,以及另一个线程在满足某个条件后唤醒等待的线程。在Java中,有多种实现线程等待唤醒机制的方式,包括使用Object的wait()和notify()方法、Condition接口以及LockSupport类。
|
8月前
|
缓存 Java 编译器
JUC 并发编程之JMM
Java内存模型是Java虚拟机(JVM)规范中定义的一组规则,用于屏蔽各种硬件和操作系统的内存访问差异,保证多线程情况下程序的正确执行。Java内存模型规定了线程之间如何交互以及线程和内存之间的关系。它主要解决的问题是可见性、原子性和有序性。
|
存储 安全
并发编程——ReentrantReadWriteLock
因为ReentrantLock是互斥锁,如果有一个操作是读多写少,同时还需要保证线程安全,那么使用ReentrantLock会导致效率比较低。因为多个线程在对同一个数据进行读操作时,也不会造成线程安全问题。
43 0
|
8月前
|
Java 程序员 开发者
深入理解Java并发编程:线程同步与锁机制
【4月更文挑战第30天】 在多线程的世界中,确保数据的一致性和线程间的有效通信是至关重要的。本文将深入探讨Java并发编程中的核心概念——线程同步与锁机制。我们将从基本的synchronized关键字开始,逐步过渡到更复杂的ReentrantLock类,并探讨它们如何帮助我们在多线程环境中保持数据完整性和避免常见的并发问题。文章还将通过示例代码,展示这些同步工具在实际开发中的应用,帮助读者构建对Java并发编程深层次的理解。
|
7月前
|
安全 算法 Java
|
并行计算 Java 应用服务中间件
JUC并发编程超详细详解篇(一)
JUC并发编程超详细详解篇
1693 1
JUC并发编程超详细详解篇(一)
|
安全 Java 调度
JUC并发编程(上)
JUC并发编程(上)
83 0
|
存储 缓存 监控
JUC并发编程(下)
JUC并发编程(下)
48 0
|
缓存 算法 安全
06.一文看懂并发编程中的锁
大家好,我是王有志。相信你经常会听到读锁/写锁,公平锁/非公平锁,乐观锁/悲观锁等五花八门的锁,那么每种锁有什么用呢?它们又有什么区别呢?今天我们就一起聊聊并发编程中的各种锁。
225 1
06.一文看懂并发编程中的锁