一、读写锁简介 

现实中有这样一种场景:对共享资源有读和写的操作,且写操作没有读操作那么频繁。在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源;但是如果一个线程想去写这些共享资源,就不应该允许其他线程对该资源进行读和写的操作了。

  针对这种场景,JAVA的并发包提供了读写锁ReentrantReadWriteLock,它表示两个锁,一个是读操作相关的锁,称为共享锁;一个是写相关的锁,称为排他锁,描述如下:

线程进入读锁的前提条件:
    没有其他线程的写锁,
    没有写请求或者有写请求,但调用线程和持有锁的线程是同一个

线程进入写锁的前提条件:
    没有其他线程的读锁
    没有其他线程的写锁

而读写锁有以下三个重要的特性:

aaarticlea/png;base64,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" alt="" />

二、源码解读

1、内部类

读写锁实现类中有许多内部类,我们先来看下这些类的定义:

public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable

读写锁并没有实现Lock接口,而是实现了ReadWriteLock。并发系列中真正实现Lock接口的并不多,除了前面提到过的重入锁(ReentrantLock),另外就是读写锁中为了实现读锁和写锁的两个内部类:

public static class ReadLock implements Lock, java.io.Serializable
public static class WriteLock implements Lock, java.io.Serializable

另外读写锁也设计成模板方法模式,通过继承队列同步器,提供了公平与非公平锁的特性:

static abstract class Sync extends AbstractQueuedSynchronizer
final static class NonfairSync extends Sync
final static class FairSync extends Sync

2、读写状态的设计

同步状态在前面重入锁的实现中是表示被同一个线程重复获取的次数,即一个整形变量来维护,但是之前的那个表示仅仅表示是否锁定,而不用区分是读锁还是写锁。而读写锁需要在同步状态(一个整形变量)上维护多个读线程和一个写线程的状态。

读写锁对于同步状态的实现是在一个整形变量上通过“按位切割使用”:将变量切割成两部分,高16位表示读,低16位表示写。

假设当前同步状态值为S,get和set的操作如下:

1、获取写状态:

S&0x0000FFFF:将高16位全部抹去

2、获取读状态:

S>>>16:无符号补0,右移16位

3、写状态加1:

S+1

4、读状态加1:

  S+(1<<16)即S + 0x00010000

在代码层吗的判断中,如果S不等于0,当写状态(S&0x0000FFFF),而读状态(S>>>16)大于0,则表示该读写锁的读锁已被获取。

3、写锁的获取与释放

protected final boolean tryAcquire(int acquires) {
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)
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
}
if ((w == 0 && writerShouldBlock(current)) ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}

1、c是获取当前锁状态;w是获取写锁的状态。

2、如果锁状态不为零,而写锁的状态为0,则表示读锁状态不为0,所以当前线程不能获取写锁。或者锁状态不为零,而写锁的状态也不为0,但是获取写锁的线程不是当前线程,则当前线程不能获取写锁。

写锁是一个可重入的排它锁,在获取同步状态时,增加了一个读锁是否存在的判断。

写锁的释放与ReentrantLock的释放过程类似,每次释放将写状态减1,直到写状态为0时,才表示该写锁被释放了。

4、读锁的获取与释放

 protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
if (!readerShouldBlock(current) &&
compareAndSetState(c, c + SHARED_UNIT)) {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != current.getId())
cachedHoldCounter = rh = readHolds.get();
rh.count++;
return 1;
}
return fullTryAcquireShared(current);
}

1、读锁是一个支持重进入的共享锁,可以被多个线程同时获取。

2、在没有写状态为0时,读锁总会被成功获取,而所做的也只是增加读状态(线程安全)

3、读状态是所有线程获取读锁次数的总和,而每个线程各自获取读锁的次数只能选择保存在ThreadLocal中,由线程自身维护。

读锁的每次释放均减小状态(线程安全的,可能有多个读线程同时释放锁),减小的值是1<<16。

5、锁降级

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

而锁升级是将读锁变成写锁,但是ReentrantReadWriteLock不支持这种方式。

我们先来看锁升级的程序:

  ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
rwl.readLock().lock();
System.out.println("get readLock");
rwl.writeLock().lock();
System.out.println("get writeLock");

这种线获取读锁,不释放紧接着获取写锁,会导致死锁!!!

 ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
rwl.writeLock().lock();
System.out.println("get writeLock");
rwl.readLock().lock();
System.out.println("get readLock");

这个过程跟上面的刚好相反,程序可以正常运行不会出现死锁。但是锁降级并不会自动释放写锁。仍然需要显示的释放。

由于读写锁用于读多写少的场景,天然的使用于实现缓存,下面看一个简易的实现缓存的DEMO:

 import java.util.HashMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock; public class CachedTest
{
volatile HashMap<String,String> cacheMap = new HashMap<String,String>(); ReadWriteLock rwLock = new ReentrantReadWriteLock(); public String getS(String key)
{
rwLock.readLock().lock();
String value = null;
try
{
if(cacheMap.get(key) == null)
{
rwLock.readLock().unlock();
rwLock.writeLock().lock();
try
{
//这里需要再次判断,防止后面阻塞的线程再次放入数据
if(cacheMap.get(key) == null)
{
value = "" + Thread.currentThread().getName();
cacheMap.put(key, value);
System.out.println(Thread.currentThread().getName() + "put the value" + value);
}
}
finally
{
//这里是锁降级,读锁的获取与写锁的释放顺序不能颠倒
rwLock.readLock().lock();
rwLock.writeLock().unlock();
}
}
}
finally
{
rwLock.readLock().unlock();
}
return cacheMap.get(key);
}
}

1、业务逻辑很好理解,一个线程进来先获取读锁,如果map里面没有值,则释放读锁,获取写锁,将该线程的value放入map中。

2、这里有两次value为空的判断,第一次判断很好理解,第二次判断是防止当前线程在获取写锁的时候,其他的线程阻塞在获取写锁的地方。当当前线程将vaule放入map之后,释放写锁。如果这个位置没有value的判断,后续获得写锁的线程以为map仍然为空,会再一次将value值放入map中,覆盖前面的value值,显然这不是我们愿意看见的。

3、在第35行的位置,这里处理的锁降级的逻辑。按照我们正常的逻辑思维,因为是先释放写锁,再获取读锁。那么锁降级为什么要这么处理呢?答案是为了保证数据的可见性,因为如果当前线程不获取读锁而是直接释放写锁,如果该线程在释放写锁与获取读锁这个时间段内,有另外一个线程获取的写锁并修改了数据,那么当前线程无法感知数据的变更。如果按照锁降级的原则来处理,那么当前线程获取到读锁之后,会阻塞其他线程获取写锁,那么数据就不会被其他线程所改动,这样就保证了数据的一致性。

ReentrantReadWriteLock读写锁详解的更多相关文章

  1. java 读写锁详解

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt124 在java多线程中,为了提高效率有些共享资源允许同时进行多个读的操作, ...

  2. Java并发包源码学习系列:ReentrantReadWriteLock读写锁解析

    目录 ReadWriteLock读写锁概述 读写锁案例 ReentrantReadWriteLock架构总览 Sync重要字段及内部类表示 写锁的获取 void lock() boolean writ ...

  3. ReentrantReadWriteLock读写锁的使用2

    本文可作为传智播客<张孝祥-Java多线程与并发库高级应用>的学习笔记. 这一节我们做一个缓存系统. 在读本节前 请先阅读 ReentrantReadWriteLock读写锁的使用1 第一 ...

  4. ReentrantReadWriteLock读写锁的使用

    Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象.两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象. 读写锁:分为读 ...

  5. 锁对象-Lock: 同步问题更完美的处理方式 (ReentrantReadWriteLock读写锁的使用/源码分析)

    Lock是java.util.concurrent.locks包下的接口,Lock 实现提供了比使用synchronized 方法和语句可获得的更广泛的锁定操作,它能以更优雅的方式处理线程同步问题,我 ...

  6. ReentrantReadWriteLock读写锁简单原理案例证明

    ReentrantReadWriteLock存在原因? 我们知道List的实现类ArrayList,LinkedList都是非线程安全的,Vector类通过用synchronized修饰方法保证了Li ...

  7. java并发锁ReentrantReadWriteLock读写锁源码分析

    1.ReentrantReadWriterLock 基础 所谓读写锁,是对访问资源共享锁和排斥锁,一般的重入性语义为如果对资源加了写锁,其他线程无法再获得写锁与读锁,但是持有写锁的线程,可以对资源加读 ...

  8. java多线程:并发包中ReentrantReadWriteLock读写锁的锁降级模板

    写锁降级为读锁,但读锁不可升级或降级为写锁. 锁降级是为了让当前线程感知到数据的变化. //读写锁 private ReentrantReadWriteLock lock=new ReentrantR ...

  9. java中ReentrantReadWriteLock读写锁的使用

    Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象.两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象. 读写锁:分为读 ...

随机推荐

  1. Java 仿迅雷多线程下载

    package net.webjoy.jackluo.android_json; /** * 1.http Range "bytes="+ start+end * 2.Random ...

  2. Express 路由

    路由 路由是指如何定义应用的端点(URIs)以及如何响应客户端的请求. 路由是由一个 URI.HTTP 请求(GET.POST等)和若干个句柄组成,它的结构如下: app.METHOD(path, [ ...

  3. 使用Privoxy做智能代理切换

    使用Privoxy做智能代理切换 You take the blue pill, the story ends, you wake up in your bed, and believe whatev ...

  4. [CareerCup] 18.12 Largest Sum Submatrix 和最大的子矩阵

    18.12 Given an NxN matrix of positive and negative integers, write code to find the submatrix with t ...

  5. [CareerCup] 16.3 Dining Philosophers 哲学家聚餐问题

    16.3 In the famous dining philosophers problem, a bunch of philosophers are sitting around a circula ...

  6. Java_Eclipse_Maven插件部署

    方法一:在线; Eclipse-help-Install New Software 1.下载地址: m2e - http://download.eclipse.org/technology/m2e/r ...

  7. MRP运算生成采购单时间的逻辑

    由MRP运算产生的采购单日期,由生产单指定的安排计划日期.公司设置里的采购提前期和隐藏的供应商供货提前期三个字段共同决定. 可以很容易的在系统中找到,供应商供货提前期,需要在产品视图中将字段selle ...

  8. css限制div字符超出部分,简单有方便

    text-overflow: -o-ellipsis-lastline;overflow: hidden;text-overflow: ellipsis;display: -webkit-box;-w ...

  9. .net 大文件上传注意,修改 IIS 配置

    原因 Web 服务器上的请求筛选被配置为拒绝该请求,因为内容长度超过配置的值. 可尝试的操作:确认 applicationhost.config 或 web.config 文件中的 configura ...

  10. svn学习笔记(3)设置

    1.图标集