Java并发编程-读写锁(ReentrantReadWriteLock)

简介: 章节目录ReentrantReadWriteLock 特性读写锁接口示例读写锁的实现分析读写状态设计写锁的释放与获取读锁的释放与获取锁降级1. ReentrantReadWriteLock 特性1.1 读写锁定义读写锁维护了一对锁,一个读锁,一个写锁,通过分离读锁写锁,使得并发性相比一般的排他锁有了很大提升。

章节目录

  • ReentrantReadWriteLock 特性
  • 读写锁接口示例
  • 读写锁的实现分析
    • 读写状态设计
    • 写锁的释放与获取
    • 读锁的释放与获取
    • 锁降级

1. ReentrantReadWriteLock 特性

1.1 读写锁定义

读写锁维护了一对锁,一个读锁,一个写锁,通过分离读锁写锁,使得并发性相比一般的排他锁有了很大提升。

1.2 读写锁使用场景

1.读写锁比较适用于读多写少的应用场景。
2.读写锁在统一时刻可以允许多个读线程访问,但是在写线程访问时,所有的读线程、其他写线程均被阻塞。

1.3 读写锁的优点

1.保证写操作对读操作的可见性
2.在读多写少的情况下的并发性的提升
3.读写锁简化可读写交互场景的编程方式

2.读写锁接口示例

如下为使用读写锁操作缓存的示例

package org.seckill.lock;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteCache {
    //充当cache
    static Map<String, Object> map = new HashMap<String, Object>();
    //实例化读写锁对象
    static ReentrantReadWriteLock reentrantReadWriteLock =
            new ReentrantReadWriteLock();
    //实例化读锁
    static Lock r = reentrantReadWriteLock.readLock();
    //实例化写锁
    static Lock w = reentrantReadWriteLock.writeLock();

    //获取缓存中值
    public static final Object get(String key) {
        r.lock();
        try {
            return map.get(key);
        } finally {
            r.unlock();
        }
    }

    //写缓存中值,并返回对应value
    public static final Object set(String key, Object obj) {
        w.lock();
        try {
            return map.put(key, obj);
        } finally {
            w.unlock();
        }
    }

    //清空所有内容
    public static final void clear() {
        w.lock();
        try {
            map.clear();
        } finally {
            w.unlock();
        }
    }
}

如上所示:

1.Cache组合一个非线程安全的HashMap做为缓存实现,同时使用读写锁的
读锁和写锁来保证Cache是线程安全的。
2.在读操作get(String key)方法中,需要使用读锁,这使得并发访问该方法时不
会被阻塞。
3.写锁put(String key,Object object)方法和clear()方法,在更新HashMap时必须
提前获取写锁,当获取写锁后,其他线程对于读锁和写锁的获取都被阻塞,只
有写锁释放之后,其他的读写操作才能继续操作,也就是说写锁其实是排他
锁、互斥锁。
4.最终,读锁提升了读操作的并发性,也保证了每次写操作对所有后续读操作
的可见性,同时简化了编程方式,对应1.3
 

3.读写锁的实现分析

3.1 读写状态设计

1.读写锁同样依赖自定义同步器实现同步功能
2.ReentrantLock 中同步状态表示锁被一个线程重复获取的次数。
3.读写锁自定义同步器需要在同步状态上维护多个读线程和一个写线程的状态。
4.读写锁同步器采用在一个4字节的整形变量上使用 按位切割 的方式来维护读
写线程的同步状态。高16位用来表示读,低16位用来表示写。
5.写状态增加1,表示当前线程获取写锁,则 Status = S(当前同步状态)+1,当读
状态加1时,Status = S+(1<<16)

3.2 写锁的获取与释放
如下源码所示:

 protected final boolean tryAcquire(int acquires) {
            /*
             * Walkthrough:
             * 1. If read count nonzero or write count nonzero
             *    and owner is a different thread, fail.
             * 2. If count would saturate, fail. (This can only
             *    happen if count is already nonzero.)
             * 3. Otherwise, this thread is eligible for lock if
             *    it is either a reentrant acquire or
             *    queue policy allows it. If so, update state
             *    and set owner.
             */
            Thread current = Thread.currentThread();
            int c = getState();
            //获取独占锁(写锁)的被获取的数量
            int w = exclusiveCount(c);
            if (c != 0) {
                // (Note: if c != 0 and w == 0 then shared count != 0)
                //1.如果同步状态不为0,且写状态为0,则表示当前同步状态被读锁获取
                //2.或者当前拥有写锁的线程不是当前线程
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // Reentrant acquire
                setState(c + acquires);
                return true;
            }
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }

3.3 读锁的释放与获取

protected final int tryAcquireShared(int unused) {
    for(;;) {
        int c = getState();
        int nextc = c + (1<<16);
        if(nextc < c) {
           throw new Error("Maxumum lock count exceeded");
        }
        if(exclusiveCount(c)!=0 && owner != Thread.currentThread())
           return -1;
        if(compareAndSetState(c,nextc))
           return 1;
    }
}

如果其他线程获取了写锁,则当前线程获取读锁失败,进入等待状态。
如果当前线程获取了写锁或者写锁未被获取,则当前线程安全,依靠CAS保证增加读状态,成功获取锁。

3.4 锁降级

锁降级是指当前把持住写锁,再获取到读锁,随后释放(先前拥有的)写锁的过程。

锁降级过程中的读锁的获取是否有必要,答案是必要的。主要是为了保证数据的可见性,如果当前线程不获取读锁而直接释放写锁,假设此刻另一个线程获取的写锁,并修改了数据,那么当前线程就步伐感知到线程T的数据更新,如果当前线程遵循锁降级的步骤,那么线程T将会被阻塞,直到当前线程使数据并释放读锁之后,线程T才能获取写锁进行数据更新。

目录
相关文章
|
2天前
|
存储 Java
JAVA并发编程AQS原理剖析
很多小朋友面试时候,面试官考察并发编程部分,都会被问:说一下AQS原理。面对并发编程基础和面试经验,专栏采用通俗简洁无废话无八股文方式,已陆续梳理分享了《一文看懂全部锁机制》、《JUC包之CAS原理》、《volatile核心原理》、《synchronized全能王的原理》,希望可以帮到大家巩固相关核心技术原理。今天我们聊聊AQS....
|
2天前
|
Java 程序员 数据库连接
Java编程中的异常处理:从基础到进阶
【9月更文挑战第18天】在Java的世界里,异常处理是每个程序员必须面对的挑战。本文将带你从异常的基本概念出发,通过实际的代码示例,深入探讨如何有效地管理和处理异常。我们将一起学习如何使用try-catch块来捕捉异常,理解finally块的重要性,以及如何自定义异常类来满足特定需求。无论你是初学者还是有经验的开发者,这篇文章都将为你提供新的见解和技巧,让你的Java代码更加健壮和可靠。
|
2天前
|
Java 数据库连接 UED
掌握Java编程中的异常处理
【9月更文挑战第18天】在Java的世界中,异常是那些不请自来的客人,它们可能在任何时候突然造访。本文将带你走进Java的异常处理机制,学习如何优雅地应对这些突如其来的“访客”。从基本的try-catch语句到更复杂的自定义异常,我们将一步步深入,确保你能够在面对异常时,不仅能够从容应对,还能从中学到宝贵的经验。让我们一起探索如何在Java代码中实现健壮的异常处理策略,保证程序的稳定运行。
|
2天前
|
安全 Java 调度
Java 并发编程中的线程安全和性能优化
本文将深入探讨Java并发编程中的关键概念,包括线程安全、同步机制以及性能优化。我们将从基础入手,逐步解析高级技术,并通过实例展示如何在实际开发中应用这些知识。阅读完本文后,读者将对如何在多线程环境中编写高效且安全的Java代码有一个全面的了解。
|
1天前
|
Java
JAVA并发编程ReentrantLock核心原理剖析
本文介绍了Java并发编程中ReentrantLock的重要性和优势,详细解析了其原理及源码实现。ReentrantLock作为一种可重入锁,弥补了synchronized的不足,如支持公平锁与非公平锁、响应中断等。文章通过源码分析,展示了ReentrantLock如何基于AQS实现公平锁和非公平锁,并解释了两者的具体实现过程。
|
3月前
|
安全 Java 程序员
Java并发编程中的锁机制与优化策略
【6月更文挑战第17天】在Java并发编程的世界中,锁是维护数据一致性和线程安全的关键。本文将深入探讨Java中的锁机制,包括内置锁、显式锁以及读写锁的原理和使用场景。我们将通过实际案例分析锁的优化策略,如减少锁粒度、使用并发容器以及避免死锁的技巧,旨在帮助开发者提升多线程程序的性能和可靠性。
|
2月前
|
存储 缓存 Java
Java面试题:解释Java中的内存屏障的作用,解释Java中的线程局部变量(ThreadLocal)的作用和使用场景,解释Java中的锁优化,并讨论乐观锁和悲观锁的区别
Java面试题:解释Java中的内存屏障的作用,解释Java中的线程局部变量(ThreadLocal)的作用和使用场景,解释Java中的锁优化,并讨论乐观锁和悲观锁的区别
33 0
|
4月前
|
安全 Java 编译器
Java并发编程中的锁优化策略
【5月更文挑战第30天】 在多线程环境下,确保数据的一致性和程序的正确性是至关重要的。Java提供了多种锁机制来管理并发,但不当使用可能导致性能瓶颈或死锁。本文将深入探讨Java中锁的优化策略,包括锁粗化、锁消除、锁降级以及读写锁的使用,以提升并发程序的性能和响应能力。通过实例分析,我们将了解如何在不同场景下选择和应用这些策略,从而在保证线程安全的同时,最小化锁带来的开销。
|
4月前
|
安全 Java 开发者
Java并发编程中的锁优化策略
【5月更文挑战第30天】 在Java并发编程领域,锁机制是实现线程同步的关键手段之一。随着JDK版本的发展,Java虚拟机(JVM)为提高性能和降低延迟,引入了多种锁优化技术。本文将深入探讨Java锁的优化策略,包括偏向锁、轻量级锁以及自旋锁等,旨在帮助开发者更好地理解和应用这些高级特性以提升应用程序的性能。
|
4月前
|
安全 Java API
Java 8中的Stream API:简介与实用指南深入理解Java并发编程:线程安全与锁优化
【5月更文挑战第29天】本文旨在介绍Java 8中引入的Stream API,这是一种用于处理集合的新方法。我们将探讨Stream API的基本概念,以及如何使用它来简化集合操作,提高代码的可读性和效率。 【5月更文挑战第29天】 在Java并发编程中,线程安全和性能优化是两个核心议题。本文将深入探讨如何通过不同的锁机制和同步策略来保证多线程环境下的数据一致性,同时避免常见的并发问题如死锁和竞态条件。文章还将介绍现代Java虚拟机(JVM)针对锁的优化技术,包括锁粗化、锁消除以及轻量级锁等概念,并指导开发者如何合理选择和使用这些技术以提升应用的性能。