读写锁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是否被任意线程获取等等

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

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

Java多线程13:读写锁和两种同步方式的对比的更多相关文章

  1. java多线程总结:线程的两种创建方式及优劣比较

    1.通过实现Runnable接口线程创建 (1).定义一个类实现Runnable接口,重写接口中的run()方法.在run()方法中加入具体的任务代码或处理逻辑. (2).创建Runnable接口实现 ...

  2. java的两种同步方式, Synchronized与ReentrantLock的区别

    java在编写多线程程序时,为了保证线程安全,需要对数据同步,经常用到两种同步方式就是Synchronized和重入锁ReentrantLock. 相似点: 这两种同步方式有很多相似之处,它们都是加锁 ...

  3. Java中的ReentrantLock和synchronized两种锁定机制的对比

    问题:多个访问线程将需要写入到文件中的数据先保存到一个队列里面,然后由专门的 写出线程负责从队列中取出数据并写入到文件中. http://blog.csdn.net/top_code/article/ ...

  4. Java之线程安全中的三种同步方式

    一个程序在运行起来时,会转换为进程,通常含有多个线程. 通常情况下,一个进程中的比较耗时的操作(如长循环.文件上传下载.网络资源获取等),往往会采用多线程来解决. 比如,现实生活中,银行取钱问题.火车 ...

  5. Java多线程之读写锁机制

    Java多线程中有很多的锁机制,他们都有各自的应用场景,例如今天我说的这种锁机制:读写锁 读写锁,见名知意,主要可以进行两种操作,读和写操作,他们之间结合使用起来又是各不相同的.比如多个线程之间可以同 ...

  6. 1.java多线程_实现线程的两种方式

    1.java多线程基本知识 1.1.进程介绍 不管是我们开发的应用程序,还是我们运行的其他的应用程序,都需要先把程序安装在本地的硬盘上.然后找到这个程序的启动文件, 启动程序的时候,其实是电脑把当前的 ...

  7. java多线程 -- ReadWriteLock 读写锁

    写一条线程,读多条线程能够提升效率. 写写/读写 需要“互斥”;读读 不需要互斥. ReadWriteLock 维护了一对相关的锁,一个用于只读操作,另一个用于写入操作.只要没有 writer,读取锁 ...

  8. java 多线程 day12 读写锁

    import java.util.Random;import java.util.concurrent.locks.ReadWriteLock;import java.util.concurrent. ...

  9. java 序列化 serialVersionUID 的作用 和 两种添加方式

    serialVersionUID适用于Java的序列化机制.简单来说,Java的序列化机制是通过判断类的serialVersionUID来验证版本一致性的.在进行反序列化时,JVM会把传来的字节流中的 ...

随机推荐

  1. BPTT算法推导

    随时间反向传播 (BackPropagation Through Time,BPTT) 符号注解: \(K\):词汇表的大小 \(T\):句子的长度 \(H\):隐藏层单元数 \(E_t\):第t个时 ...

  2. SQL Server日期时间格式转换字符串详解 (详询请加qq:2085920154)

    在SQL Server数据库中,SQL Server日期时间格式转换字符串可以改变SQL Server日期和时间的格式,是每个SQL数据库用户都应该掌握的.本文我们主要就介绍一下SQL Server日 ...

  3. RHEL7学习之NTP配置

    一,安装NTP [root@localhost ~]# yum install ntp Loaded plugins: product-id, subscription-manager This sy ...

  4. vi

    e! 放弃所有修改,从上次保存文件开始再编辑 shift+g 最后一行 gg 第一行 u 恢复上一次操作 如果查找下一个,按"n"即可. set nu 显示行号 编辑模式下111g ...

  5. 给angularJs的service建模

    先回顾一下我们遇到的问题: 通过一个dialogService创建对话框,并将该service的参数数据通过resolve的方式传递给对话框的controller. controller解析数据后放置 ...

  6. SQL 使用 AND 与 OR

    1.介绍:关于AND与OR的使用是比较基础的,但是在项目中如果不注意使用的话会使查出来的数据有错误. 2.主要问题: 在未加任何AND与OR的选择是独立的,与逻辑运算一样.即: SELECT * FR ...

  7. TCP/IP入门(3) --传输层

    原文:http://blog.csdn.net/zjf280441589/article/category/1854365 传输层的主要功能 1)传输层为应用进程之间提供端到端的逻辑通信(网络层是为主 ...

  8. Height Half Values

    public class HeightDemo { /** * 题目:一球从100米高度自由落下,每次落地后反跳回原高度的一半: * 再落下,求它在第10次落地时,共经过多少米?第10次反弹多高? * ...

  9. java加密解压类库

    java压缩类库_支持加密解压.zip public static void unzip(File zipFile, String dest, String passwd) throws ZipExc ...

  10. Swift语法简介(一)

    或许网络上有很多成型的介绍,我只想写下来留给自己.欢迎批评.开撸! 1.第一个程序,Hello,world!古人云,学会了Hello,world!这门语言你就掌握了一半了. print("H ...