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才能获取写锁进行数据更新。

目录
相关文章
|
4天前
|
缓存 Java UED
Java中的多线程编程:从基础到实践
【10月更文挑战第13天】 Java作为一门跨平台的编程语言,其强大的多线程能力一直是其核心优势之一。本文将从最基础的概念讲起,逐步深入探讨Java多线程的实现方式及其应用场景,通过实例讲解帮助读者更好地理解和应用这一技术。
22 3
|
4天前
|
Java 开发者
在Java编程中,正确的命名规范不仅能提升代码的可读性和可维护性,还能有效避免命名冲突。
【10月更文挑战第13天】在Java编程中,正确的命名规范不仅能提升代码的可读性和可维护性,还能有效避免命名冲突。本文将带你深入了解Java命名规则,包括标识符的基本规则、变量和方法的命名方式、常量的命名习惯以及如何避免关键字冲突,通过实例解析,助你写出更规范、优雅的代码。
25 3
|
4天前
|
Java 程序员
在Java编程中,关键字不仅是简单的词汇,更是赋予代码强大功能的“魔法咒语”。
【10月更文挑战第13天】在Java编程中,关键字不仅是简单的词汇,更是赋予代码强大功能的“魔法咒语”。本文介绍了Java关键字的基本概念及其重要性,并通过定义类和对象、控制流程、访问修饰符等示例,展示了关键字的实际应用。掌握这些关键字,是成为优秀Java程序员的基础。
12 3
|
4天前
|
Java 程序员 编译器
在Java编程中,保留字(如class、int、for等)是具有特定语法意义的预定义词汇,被语言本身占用,不能用作变量名、方法名或类名。
在Java编程中,保留字(如class、int、for等)是具有特定语法意义的预定义词汇,被语言本身占用,不能用作变量名、方法名或类名。本文通过示例详细解析了保留字的定义、作用及与自定义标识符的区别,帮助开发者避免因误用保留字而导致的编译错误,确保代码的正确性和可读性。
15 3
|
3天前
|
存储 安全 Java
了解final关键字在Java并发编程领域的作用吗?
在Java并发编程中,`final`关键字不仅用于修饰变量、方法和类,还在多线程环境中确保对象状态的可见性和不变性。本文深入探讨了`final`关键字的作用,特别是其在final域重排序规则中的应用,以及如何防止对象的“部分创建”问题,确保线程安全。通过具体示例,文章详细解析了final域的写入和读取操作的重排序规则,以及这些规则在不同处理器上的实现差异。
了解final关键字在Java并发编程领域的作用吗?
|
4月前
|
Java C++
关于《Java并发编程之线程池十八问》的补充内容
【6月更文挑战第6天】关于《Java并发编程之线程池十八问》的补充内容
48 5
|
1月前
|
缓存 监控 Java
Java中的并发编程:理解并应用线程池
在Java的并发编程中,线程池是提高应用程序性能的关键工具。本文将深入探讨如何有效利用线程池来管理资源、提升效率和简化代码结构。我们将从基础概念出发,逐步介绍线程池的配置、使用场景以及最佳实践,帮助开发者更好地掌握并发编程的核心技巧。
|
3月前
|
安全 Java 开发者
Java中的并发编程:深入理解线程池
在Java的并发编程中,线程池是管理资源和任务执行的核心。本文将揭示线程池的内部机制,探讨如何高效利用这一工具来优化程序的性能与响应速度。通过具体案例分析,我们将学习如何根据不同的应用场景选择合适的线程池类型及其参数配置,以及如何避免常见的并发陷阱。
52 1
|
3月前
|
监控 Java
Java并发编程:深入理解线程池
在Java并发编程领域,线程池是提升应用性能和资源管理效率的关键工具。本文将深入探讨线程池的工作原理、核心参数配置以及使用场景,通过具体案例展示如何有效利用线程池优化多线程应用的性能。
|
2月前
|
Java 数据库
Java中的并发编程:深入理解线程池
在Java的并发编程领域,线程池是提升性能和资源管理的关键工具。本文将通过具体实例和数据,探讨线程池的内部机制、优势以及如何在实际应用中有效利用线程池,同时提出一个开放性问题,引发读者对于未来线程池优化方向的思考。
41 0