读写锁ReentrantReadWriteLock概述

大型网站中很重要的一块内容就是数据的读写,ReentrantLock虽然具有完全互斥排他的效果(即同一时间只有一个线程正在执行lock后面的任务),但是效率非常低。所以在JDK中提供了一种读写锁ReentrantReadWriteLock,使用它可以加快运行效率。

读写锁表示两个锁,一个是读操作相关的锁,称为共享锁;另一个是写操作相关的锁,称为排他锁。我把这两个操作理解为三句话:

1、读和读之间不互斥,因为读操作不会有线程安全问题

2、写和写之间互斥,避免一个写操作影响另外一个写操作,引发线程安全问题

3、读和写之间互斥,避免读操作的时候写操作修改了内容,引发线程安全问题

总结起来就是,多个Thread可以同时进行读取操作,但是同一时刻只允许一个Thread进行写入操作

读和读共享

先证明一下第一句话"读和读之间不互斥",举一个简单的例子:

public class ThreadDomain48 extends ReentrantReadWriteLock
{
public void read()
{
try
{
readLock().lock();
System.out.println(Thread.currentThread().getName() + "获得了读锁, 时间为" +
System.currentTimeMillis());
Thread.sleep(10000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
readLock().unlock();
}
}
}
public static void main(String[] args)
{
final ThreadDomain48 td = new ThreadDomain48();
Runnable readRunnable = new Runnable()
{
public void run()
{
td.read();
}
};
Thread t0 = new Thread(readRunnable);
Thread t1 = new Thread(readRunnable);
t0.start();
t1.start();
}

看一下运行结果:

Thread-0获得了读锁, 时间为1444019668424
Thread-1获得了读锁, 时间为1444019668424

尽管方法加了锁,还休眠了10秒,但是两个线程还是几乎同时执行lock()方法后面的代码,看时间就知道了。说明lock.readLock()读锁可以提高程序运行效率,允许多个线程同时执行lock()方法后面的代码

写和写互斥

再证明一下第二句话"写和写之间互斥",类似的证明方法:

public class ThreadDomain48 extends ReentrantReadWriteLock
{
public void write()
{
try
{
writeLock().lock();
System.out.println(Thread.currentThread().getName() + "获得了写锁, 时间为" +
System.currentTimeMillis());
Thread.sleep(10000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
writeLock().unlock();
}
}
}
public static void main(String[] args)
{
final ThreadDomain48 td = new ThreadDomain48();
Runnable readRunnable = new Runnable()
{
public void run()
{
td.write();
}
};
Thread t0 = new Thread(readRunnable);
Thread t1 = new Thread(readRunnable);
t0.start();
t1.start();
}

看一下运行结果:

Thread-0获得了写锁, 时间为1444021393325
Thread-1获得了写锁, 时间为1444021403325

从时间上就可以看出来,10000ms即10s,和代码里一致,证明了读和读之间是互斥的

读和写互斥

最后证明一下第三句话"读和写之间互斥",证明方法无非是把上面二者结合起来而已,看一下:

public class ThreadDomain48 extends ReentrantReadWriteLock
{
public void write()
{
try
{
writeLock().lock();
System.out.println(Thread.currentThread().getName() + "获得了写锁, 时间为" +
System.currentTimeMillis());
Thread.sleep(10000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
writeLock().unlock();
}
} public void read()
{
try
{
readLock().lock();
System.out.println(Thread.currentThread().getName() + "获得了读锁, 时间为" +
System.currentTimeMillis());
Thread.sleep(10000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
readLock().unlock();
}
}
}
public static void main(String[] args)
{
final ThreadDomain48 td = new ThreadDomain48();
Runnable readRunnable = new Runnable()
{
public void run()
{
td.read();
}
};
Runnable writeRunnable = new Runnable()
{
public void run()
{
td.write();
}
};
Thread t0 = new Thread(readRunnable);
Thread t1 = new Thread(writeRunnable);
t0.start();
t1.start();
}

看一下运行结果:

Thread-0获得了读锁, 时间为1444021679203
Thread-1获得了写锁, 时间为1444021689204

从时间上看,也是10000ms即10s,和代码里面是一致的,证明了读和写之间是互斥的。注意一下,"读和写互斥"和"写和读互斥"是两种不同的场景,但是证明方式和结论是一致的,所以就不证明了。

synchronized和ReentrantLock的对比

到现在,看到多线程中,锁定的方式有2种:synchronized和ReentrantLock。两种锁定方式各有优劣,下面简单对比一下:

1、synchronized是关键字,就和if...else...一样,是语法层面的实现,因此synchronized获取锁以及释放锁都是Java虚拟机帮助用户完成的;ReentrantLock是类层面的实现,因此锁的获取以及锁的释放都需要用户自己去操作。特别再次提醒,ReentrantLock在lock()完了,一定要手动unlock()

2、synchronized简单,简单意味着不灵活,而ReentrantLock的锁机制给用户的使用提供了极大的灵活性。这点在Hashtable和ConcurrentHashMap中体现得淋漓尽致。synchronized一锁就锁整个Hash表,而ConcurrentHashMap则利用ReentrantLock实现了锁分离,锁的知识segment而不是整个Hash表

3、synchronized是不公平锁,而ReentrantLock可以指定锁是公平的还是非公平的

4、synchronized实现等待/通知机制通知的线程是随机的,ReentrantLock实现等待/通知机制可以有选择性地通知

5、和synchronized相比,ReentrantLock提供给用户多种方法用于锁信息的获取,比如可以知道lock是否被当前线程获取、lock被同一个线程调用了几次、lock是否被任意线程获取等等

6. ReentrantLock的某些方法可以决定多长时间内尝试获取锁,如果获取不到就抛异常,这样就可以一定程度上减轻死锁的可能性,如果锁被另一个线程占据了,synchronized只会一直等待,很容易错序死锁

7.synchronized的话,锁的范围是整个方法或synchronized块部分;而Lock因为是方法调用,可以跨方法,灵活性更大

总结起来,我认为如果只需要锁定简单的方法、简单的代码块,那么考虑使用synchronized,复杂的多线程处理场景下可以考虑使用ReentrantLock。当然这只是建议性地,还是要具体场景具体分析的。

最后,查看了很多资料,JDK1.5版本只有由于对synchronized做了诸多优化,效率上synchronized和ReentrantLock应该是差不多。


java多线程 21 : ReentrantReadWriteLock ,synchronized和ReentrantLock的对比的更多相关文章

  1. java多线程系列6 synchronized 加强版 ReentrantLock

    ReentrantLock类是可重入.互斥.实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力.ReenreantLock类的常用方法有: Re ...

  2. 【多线程】:Synchronized和ReentrantLock的对比

    相同点: 两者都是可重入锁,同一个线程每进入一次,锁的计数器都自增1,等到锁的计数器下降为0时才能释放锁. 底层实现对比: Synchronized是依赖于JVM实现的,而ReentrantLock是 ...

  3. java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析

    java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java ...

  4. Java多线程(九)之ReentrantLock与Condition

    一.ReentrantLock 类   1.1 什么是reentrantlock   java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 ...

  5. Java多线程6:Synchronized锁代码块(this和任意对象)

    一.Synchronized(this)锁代码块 用关键字synchronized修饰方法在有些情况下是有弊端的,若是执行该方法所需的时间比较长,线程1执行该方法的时候,线程2就必须等待.这种情况下就 ...

  6. Java多线程5:Synchronized锁机制

    一.前言 在多线程中,有时会出现多个线程对同一个对象的变量进行并发访问的情形,如果不做正确的同步处理,那么产生的后果就是“脏读”,也就是获取到的数据其实是被修改过的. 二.引入Synchronized ...

  7. Java多线程简析——Synchronized(同步锁)、Lock以及线程池

    Java多线程 Java中,可运行的程序都是有一个或多个进程组成.进程则是由多个线程组成的.最简单的一个进程,会包括mian线程以及GC线程. 线程的状态 线程状态由以下一张网上图片来说明: 在图中, ...

  8. java多线程——同步块synchronized详解

    Java 同步块(synchronized block)用来标记方法或者代码块是同步的.Java同步块用来避免竞争.本文介绍以下内容: Java同步关键字(synchronzied) 实例方法同步 静 ...

  9. Java多线程之二(Synchronized)

    常用API method 注释 run() run()方法是我们创建线程时必须要实现的方法,但是实际上该方法只是一个普通方法,直接调用并没有开启线程的作用. start() start()方法作用为使 ...

随机推荐

  1. suricate学习笔记1--初步认识(转)

    最近在研究关于dpi网卡采集的代码重组这块,公司一个同事,简单的用CPP讲解了suricata内部的一些处理逻辑,,,其中大部分代码是用C语言写的,对于用C重构代码有很好的借鉴作用,,,如果有相关工作 ...

  2. 【转】fileno函数与ftruncate函数

      fileno函数与ftruncate函数 2011-10-25 10:03:33 分类: LINUX fileno()函数 功    能:把文件流指针转换成文件描述符相关函数:open, fope ...

  3. linux达人养成计划学习笔记(五)—— 关机和重启命令

    一.shutdown 1.格式: shutdown [选项] 时间(now) 选项: -c: 取消前一个关机命令 -h: 关机 -r: 重启 2.程序放入后台执行: shutdown -r 时间 &a ...

  4. python学习笔记——爬虫学习中的重要库urllib

    1 urllib概述 1.1 urllib库中的模块类型 urllib是python内置的http请求库 其提供了如下功能: (1)error 异常处理模块 (2)parse url解析模块 (3)r ...

  5. Canvas文本操作

    Canvas的画图环境提供三个方法如:绘制填充文本:fillText();绘制描边文本:strokeText();绘制文本并返回一个对象:measure();measure()方法返回的对象中包括一个 ...

  6. session的取代者:Json Web Tokens----在客户端存储登陆状态

    https://auth0.com/blog/2014/01/07/angularjs-authentication-with-cookies-vs-token/

  7. IAR仿真时绿色箭头一直指向汇编界面,而C界面没有

    设置如下

  8. 转 MySQL问题排查工具介绍

    原文链接: http://mrchenatu.com/2017/03/24/mysql-tool/ 本总结来自美团内部分享,屏蔽了内部数据与工具 知识准备 索引 索引是存储引擎用于快速找到记录的一种数 ...

  9. IDEA快捷键收集

    生成set 和get方法 .生产重写方法Alt+Insert 查看类的所有方法alt + 7 去掉多余的引用包alt + ctrl + O ctrl + alt + t 生成try 语句 自动导入包 ...

  10. Vue基本概念介绍及vue-cli环境搭建

    1 js中初始化一个Vue对象,传的参数就是对象属性. 挂载点.模板.实例之间的关系. var vm = new Vue({ el:"#app", template:'<di ...