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

目录
相关文章
|
1天前
|
存储 安全 Java
Java并发编程中的高效数据结构:ConcurrentHashMap解析
【4月更文挑战第25天】在多线程环境下,高效的数据访问和管理是至关重要的。Java提供了多种并发集合来处理这种情境,其中ConcurrentHashMap是最广泛使用的一个。本文将深入分析ConcurrentHashMap的内部工作原理、性能特点以及它如何在保证线程安全的同时提供高并发性,最后将展示其在实际开发中的应用示例。
|
2天前
|
Java API 调度
[Java并发基础]多进程编程
[Java并发基础]多进程编程
|
2天前
|
Java API 调度
[AIGC] 深入理解Java并发编程:从入门到进阶
[AIGC] 深入理解Java并发编程:从入门到进阶
|
2天前
|
前端开发 Java 测试技术
Java从入门到精通:4.1.1参与实际项目,锻炼编程与问题解决能力
Java从入门到精通:4.1.1参与实际项目,锻炼编程与问题解决能力
|
2天前
|
Dubbo Java 应用服务中间件
Java从入门到精通:3.2.2分布式与并发编程——了解分布式系统的基本概念,学习使用Dubbo、Spring Cloud等分布式框架
Java从入门到精通:3.2.2分布式与并发编程——了解分布式系统的基本概念,学习使用Dubbo、Spring Cloud等分布式框架
|
2天前
|
SQL Java 数据库连接
Java从入门到精通:2.3.2数据库编程——了解SQL语言,编写基本查询语句
Java从入门到精通:2.3.2数据库编程——了解SQL语言,编写基本查询语句
|
2天前
|
SQL Java 数据库连接
Java从入门到精通:2.3.1数据库编程——学习JDBC技术,掌握Java与数据库的交互
ava从入门到精通:2.3.1数据库编程——学习JDBC技术,掌握Java与数据库的交互
|
4月前
|
存储 监控 安全
Java虚拟机的锁优化策略
Java虚拟机的锁优化策略
28 0
|
7月前
|
Java 编译器
解密Java多线程中的锁机制:CAS与Synchronized的工作原理及优化策略
解密Java多线程中的锁机制:CAS与Synchronized的工作原理及优化策略
|
12天前
|
Java 程序员 编译器
Java中的线程同步与锁优化策略
【4月更文挑战第14天】在多线程编程中,线程同步是确保数据一致性和程序正确性的关键。Java提供了多种机制来实现线程同步,其中最常用的是synchronized关键字和Lock接口。本文将深入探讨Java中的线程同步问题,并分析如何通过锁优化策略提高程序性能。我们将首先介绍线程同步的基本概念,然后详细讨论synchronized和Lock的使用及优缺点,最后探讨一些锁优化技巧,如锁粗化、锁消除和读写锁等。