前面我们讲解了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. Android:Attribute is missing the Android namespace prefix

    今天编写XML文件时,出现了Attribute is missing the Android namespace prefix的错误,开始一直找没找出原因,后来仔细一看原来只是一个很简单的单词书写错误 ...

  2. 【推荐】腾讯android镜像(做Android开发的得好好利用下这个网站,国内的大公司还是可以滴……)

    原文地址:http://android-mirror.bugly.qq.com:8080/include/usage.html ☀ Windows I. Open Android SDK Manage ...

  3. 在xcode5下设置两个viewController跳转——关键是禁用arc

    1.禁用arc 2.然后使用如下代码: - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( ...

  4. iOS中block简介-作用域

    转:http://www.2cto.com/kf/201401/269467.html 用block可以定义任意的代码片段,将其像对象一样传入另一个方法:它是c级别的语法,和C语言中的函数指针非常相似 ...

  5. JQuery日期插件

    JQuery是一款非常优秀的脚本框架,其丰富的控件使用起来也非常简单,配置非常灵活.下面做一个使用日期插件datapicker的例子. 1.下载jQuery核心文件就不用说了吧,datepicker是 ...

  6. nginx网站攻击防护

    1.上上个月架构全部迁移上云以后,总的来说比较稳定,业务量也上来,可爱的坏人也来了,7X24小时不停恶意攻击我的网站,第一次收到报警是网站流入流量1分钟以内连续3次超过1000000bps,换算下1M ...

  7. 关于面试总结3-SQL查询

    前言 select top n 形式的语句可以获取查询的前几个记录,但是 mysql没有此语法,mysql用limit来实现相关功能. LIMIT子句可以被用于强制 SELECT 语句返回指定的记录数 ...

  8. java List转换为字符串并加入分隔符的一些方法总结

    方法一: public String listToString(List list, char separator) { StringBuilder sb = new StringBuilder(); ...

  9. 为python安装matplotlib模块

    matplotlib是python中强大的画图模块. 首先确保已经安装python,然后用pip来安装matplotlib模块. 进入到cmd窗口下,执行python -m pip install - ...

  10. 神探夏洛克第一季/全集Sherlock1迅雷下载

    第一季 Sherlock Season 1 (2010)看点:夏洛克·福尔摩斯(Sherlock Holmes)是一个虚构的侦探人物,是由19世纪末的英国侦探小说家阿瑟·柯南·道尔所塑造的一个才华横溢 ...