前面我们讲解了Lock的使用,下面我们来讲解一下ReadWriteLock锁的使用,顾明思义,读写锁在读的时候,上读锁,在写的时候,上写锁,这样就很巧妙的解决synchronized的一个性能问题:读与读之间互斥。

ReadWriteLock也是一个接口,原型如下:

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

该接口只有两个方法,读锁和写锁。也就是说,我们在写文件的时候,可以将读和写分开,分成2个锁来分配给线程,从而可以做到读和读互不影响,读和写互斥,写和写互斥,提高读写文件的效率。该接口也有一个实现类ReentrantReadWriteLock,下面我们就来学习下这个类。

我们先看一下,多线程同时读取文件时,用synchronized实现的效果,代码如下:

public class ReadAndWriteLock {
public synchronized void get(Thread thread) {
System.out.println("start time:"+System.currentTimeMillis());
for(int i=0; i<5; i++){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(thread.getName() + ":正在进行读操作……");
}
System.out.println(thread.getName() + ":读操作完毕!");
System.out.println("end time:"+System.currentTimeMillis());
} public static void main(String[] args) {
final ReadAndWriteLock lock = new ReadAndWriteLock();
new Thread(new Runnable() {
@Override
public void run() {
lock.get(Thread.currentThread());
}
}).start(); new Thread(new Runnable() {
@Override
public void run() {
lock.get(Thread.currentThread());
}
}).start();
}
}

测试结果如下:

start time:1442459467623
Thread-0:正在进行读操作……
Thread-0:正在进行读操作……
Thread-0:正在进行读操作……
Thread-0:正在进行读操作……
Thread-0:正在进行读操作……
Thread-0:读操作完毕!
end time:1442459467723
start time:1442459467723
Thread-1:正在进行读操作……
Thread-1:正在进行读操作……
Thread-1:正在进行读操作……
Thread-1:正在进行读操作……
Thread-1:正在进行读操作……
Thread-1:读操作完毕!
end time:1442459467823

整个过程耗时200ms

我们可以看到,及时是在读取文件,在加了synchronized关键字之后,读与读之间,也是互斥的,也就是说,必须等待Thread-0读完之后,才会轮到Thread-1线程读,而无法做到同时读文件,这种情况在大量线程同时都需要读文件的时候,读写锁的效率,明显要高于synchronized关键字的实现。下面我们来测试一下,代码如下:

public class ReadAndWriteLock {
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void get(Thread thread) {
lock.readLock().lock();
try{
System.out.println("start time:"+System.currentTimeMillis());
for(int i=0; i<5; i++){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(thread.getName() + ":正在进行读操作……");
}
System.out.println(thread.getName() + ":读操作完毕!");
System.out.println("end time:"+System.currentTimeMillis());
}finally{
lock.readLock().unlock();
}
} public static void main(String[] args) {
final ReadAndWriteLock lock = new ReadAndWriteLock();
new Thread(new Runnable() {
@Override
public void run() {
lock.get(Thread.currentThread());
}
}).start(); new Thread(new Runnable() {
@Override
public void run() {
lock.get(Thread.currentThread());
}
}).start();
}
}

测试结果如下:

start time:1442460030593
start time:1442460030593
Thread-1:正在进行读操作……
Thread-0:正在进行读操作……
Thread-1:正在进行读操作……
Thread-0:正在进行读操作……
Thread-1:正在进行读操作……
Thread-0:正在进行读操作……
Thread-1:正在进行读操作……
Thread-0:正在进行读操作……
Thread-1:正在进行读操作……
Thread-0:正在进行读操作……
Thread-0:读操作完毕!
Thread-1:读操作完毕!
end time:1442460030693
end time:1442460030693

整个过程耗时:100ms

从测试结果来看,Thread-0和Thread-1是在同时读取文件。

通过两次实验的对比,我们可以看出来,读写锁的效率明显高于synchronized关键字

不过要注意的是,如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁。如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁。读锁和写锁是互斥的。

下面我们来验证下读写锁的互斥关系,代码如下:

public class ReadAndWriteLock {
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public static void main(String[] args) {
final ReadAndWriteLock lock = new ReadAndWriteLock();
// 建N个线程,同时读
ExecutorService service = Executors.newCachedThreadPool();
service.execute(new Runnable() {
@Override
public void run() {
lock.readFile(Thread.currentThread());
}
});
// 建N个线程,同时写
ExecutorService service1 = Executors.newCachedThreadPool();
service1.execute(new Runnable() {
@Override
public void run() {
lock.writeFile(Thread.currentThread());
}
});
}
// 读操作
public void readFile(Thread thread){
lock.readLock().lock();
boolean readLock = lock.isWriteLocked();
if(!readLock){
System.out.println("当前为读锁!");
}
try{
for(int i=0; i<5; i++){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(thread.getName() + ":正在进行读操作……");
}
System.out.println(thread.getName() + ":读操作完毕!");
}finally{
System.out.println("释放读锁!");
lock.readLock().unlock();
}
}
// 写操作
public void writeFile(Thread thread){
lock.writeLock().lock();
boolean writeLock = lock.isWriteLocked();
if(writeLock){
System.out.println("当前为写锁!");
}
try{
for(int i=0; i<5; i++){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(thread.getName() + ":正在进行写操作……");
}
System.out.println(thread.getName() + ":写操作完毕!");
}finally{
System.out.println("释放写锁!");
lock.writeLock().unlock();
}
}
}

测试结果如下:

// 读锁和读锁测试结果:
当前为读锁!
当前为读锁!
pool-2-thread-1:正在进行读操作……
pool-1-thread-1:正在进行读操作……
pool-2-thread-1:正在进行读操作……
pool-1-thread-1:正在进行读操作……
pool-2-thread-1:正在进行读操作……
pool-1-thread-1:正在进行读操作……
pool-2-thread-1:正在进行读操作……
pool-1-thread-1:正在进行读操作……
pool-1-thread-1:正在进行读操作……
pool-2-thread-1:正在进行读操作……
pool-1-thread-1:读操作完毕!
pool-2-thread-1:读操作完毕!
释放读锁!
释放读锁!
// 测试结果不互斥 // 读锁和写锁,测试结果如下:
当前为读锁!
pool-1-thread-1:正在进行读操作……
pool-1-thread-1:正在进行读操作……
pool-1-thread-1:正在进行读操作……
pool-1-thread-1:正在进行读操作……
pool-1-thread-1:正在进行读操作……
pool-1-thread-1:读操作完毕!
释放读锁!
当前为写锁!
pool-2-thread-1:正在进行写操作……
pool-2-thread-1:正在进行写操作……
pool-2-thread-1:正在进行写操作……
pool-2-thread-1:正在进行写操作……
pool-2-thread-1:正在进行写操作……
pool-2-thread-1:写操作完毕!
释放写锁!
// 测试结果互斥 // 写锁和写锁,测试结果如下:
当前为写锁!
pool-1-thread-1:正在进行写操作……
pool-1-thread-1:正在进行写操作……
pool-1-thread-1:正在进行写操作……
pool-1-thread-1:正在进行写操作……
pool-1-thread-1:正在进行写操作……
pool-1-thread-1:写操作完毕!
释放写锁!
当前为写锁!
pool-2-thread-1:正在进行写操作……
pool-2-thread-1:正在进行写操作……
pool-2-thread-1:正在进行写操作……
pool-2-thread-1:正在进行写操作……
pool-2-thread-1:正在进行写操作……
pool-2-thread-1:写操作完毕!
释放写锁!
// 测试结果互斥

关于锁的内容我们就讲到这里,下面对锁的相关概念做一个介绍:

1.可重入(Reentrant)锁

如果锁具备可重入性,则称作为可重入锁。像synchronized和 ReentrantLock都是可重入锁,可重入性在我看来实际上表明了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。举个简单的例子,当一 个线程执行到某个synchronized方法时,比如说method1,而在method1中会调用另外一个synchronized方法 method2,此时线程不必重新去申请锁,而是可以直接执行方法method2。

看下面这段代码就明白了:

classMyClass {
public synchronized void method1() {
method2();
}
public synchronized void method2() {
}
}

上述代码中的两个方法method1和method2都用synchronized修饰了,假如某一时刻,线程A执行到了method1,此时线程 A获取了这个对象的锁,而由于method2也是synchronized方法,假如synchronized不具备可重入性,此时线程A需要重新申请 锁。但是这就会造成一个问题,因为线程A已经持有了该对象的锁,而又在申请获取该对象的锁,这样就会线程A一直等待永远不会获取到的锁。  而由于synchronized和Lock都具备可重入性,所以不会发生上述现象。

2.可中断锁

  可中断锁:顾名思义,就是可以相应中断的锁。

  在Java中,synchronized就不是可中断锁,而Lock是可中断锁。

  如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。

  3.公平锁

  公平锁即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。

  非公平锁即无法保证锁的获取是按照请求锁的顺序进行的。这样就可能导致某个或者一些线程永远获取不到锁。

  在Java中,synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。

而对于ReentrantLock和ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。设置方法如下:ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);

4、读写锁

前面已经介绍,这里不做赘述

--------------------- 本文来自 牛奋lch 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/liuchuanhong1/article/details/53539341?utm_source=copy

【转】java并发编程系列之ReadWriteLock读写锁的使用的更多相关文章

  1. Java并发编程系列-(4) 显式锁与AQS

    4 显示锁和AQS 4.1 Lock接口 核心方法 Java在java.util.concurrent.locks包中提供了一系列的显示锁类,其中最基础的就是Lock接口,该接口提供了几个常见的锁相关 ...

  2. 干货:Java并发编程系列之volatile(二)

    接上一篇<Java并发编程系列之synchronized(一)>,这是第二篇,说的是关于并发编程的volatile元素. Java语言规范第三版中对volatile的定义如下:Java编程 ...

  3. Java并发编程系列-(5) Java并发容器

    5 并发容器 5.1 Hashtable.HashMap.TreeMap.HashSet.LinkedHashMap 在介绍并发容器之前,先分析下普通的容器,以及相应的实现,方便后续的对比. Hash ...

  4. Java并发编程系列-(7) Java线程安全

    7. 线程安全 7.1 线程安全的定义 如果多线程下使用这个类,不过多线程如何使用和调度这个类,这个类总是表示出正确的行为,这个类就是线程安全的. 类的线程安全表现为: 操作的原子性 内存的可见性 不 ...

  5. Java并发编程系列-(8) JMM和底层实现原理

    8. JMM和底层实现原理 8.1 线程间的通信与同步 线程之间的通信 线程的通信是指线程之间以何种机制来交换信息.在编程中,线程之间的通信机制有两种,共享内存和消息传递. 在共享内存的并发模型里,线 ...

  6. Java并发编程系列-(9) JDK 8/9/10中的并发

    9.1 CompletableFuture CompletableFuture是JDK 8中引入的工具类,实现了Future接口,对以往的FutureTask的功能进行了增强. 手动设置完成状态 Co ...

  7. 【原创】Java并发编程系列1:大纲

    [原创]Java并发编程系列1:大纲 一个人能力当中所蕴藏的潜能,远超过自己想象以外. 为什么要学习并发编程 随着现今互联网行业的迅猛发展,其业务复杂度.并发量也在不断增加,对程序的要求变得越来越高, ...

  8. Java并发编程系列-(3) 原子操作与CAS

    3. 原子操作与CAS 3.1 原子操作 所谓原子操作是指不会被线程调度机制打断的操作:这种操作一旦开始,就一直运行到结束,中间不会有任何context switch,也就是切换到另一个线程. 为了实 ...

  9. Java并发编程系列-(2) 线程的并发工具类

    2.线程的并发工具类 2.1 Fork-Join JDK 7中引入了fork-join框架,专门来解决计算密集型的任务.可以将一个大任务,拆分成若干个小任务,如下图所示: Fork-Join框架利用了 ...

随机推荐

  1. PeekMessage和GetMessage函数的主要区别

    PeekMessage和GetMessage函数的主要区别 PeekMessage和GetMessage函数的主要区别有:1. GetMessage的主要功能是从消息队列中“取出”消息,消息被取出以后 ...

  2. 查看内核页表kernel_page_tables (aarch32)

    作者 彭东林 pengdonglin137@163.com   平台 Linux-4.10.17 Qemu + vexpress-ca9     概述 通过配置内核,会在/sys/kernel/deb ...

  3. WordPress主题开发:WP_Query基本用法

    为什么要学WP_Query? wordpress默认会根据网址调用数据,不能满足我们所有建站要求,而WP_Query可以用于查询任何你想要的内容,相当于自定义数据调用. 便于记忆,在讲用法之前我们回顾 ...

  4. 分析iOS Crash文件,使用命令符号化iOS Crash文件

    TBMainClient.ipa改名为TBMainClient.zip并解压得到TBMainClient.app 然后将TBMainClient.app      TBMainClient.app.d ...

  5. 在linux下创建自定义service服务

    三个部分 这个脚本分为3个部分:[Unit] [Service] [Install]. Unit Unit表明该服务的描述,类型描述.我们称之为一个单元.比较典型的情况是单元A要求在单元B启动之后再启 ...

  6. win7下设置环境变量

    手工当然可以进行环境变量的设置,但是如果一个小组有需要设置固定环境变量的操作,这可能就会有点麻烦了,xp下设置环境变量比较简单,直接用set,win7下需要使用setx SETX XX_HOME &q ...

  7. 开源项目PullToRefresh详解(二)——PullToRefreshGridView

    这里介绍的是PullToRefreshGridView的使用方法,和之前的PullToRefreshListView方法如出一辙,因为这个开源项目模块化很棒,所以很容易实现.等于说我们可以按照之前使用 ...

  8. 从客户端(ctl00$ContentPlaceHolder1$result="<?xml version="1.0" ...")中检测到有潜在危险的 Request.Form 值。

    ylbtech-Error-WebForm:从客户端(ctl00$ContentPlaceHolder1$result="<?xml version="1.0" . ...

  9. 奇怪吸引子---Bouali

    奇怪吸引子是混沌学的重要组成理论,用于演化过程的终极状态,具有如下特征:终极性.稳定性.吸引性.吸引子是一个数学概念,描写运动的收敛类型.它是指这样的一个集合,当时间趋于无穷大时,在任何一个有界集上出 ...

  10. HDU—— 5159 Building Blocks

    Problem Description After enjoying the movie,LeLe went home alone. LeLe decided to build blocks. LeL ...