Java多线程之内置锁与显示锁
Java中具有通过Synchronized实现的内置锁,和ReentrantLock实现的显示锁,这两种锁各有各的好处,算是互有补充,今天就来做一个总结。
Synchronized
内置锁获得锁和释放锁是隐式的,进入synchronized修饰的代码就获得锁,走出相应的代码就释放锁。
synchronized(list){ //获得锁
list.append();
list.count();
}//释放锁
通信
与Synchronized配套使用的通信方法通常有wait(),notify()。
wait()方法会立即释放当前锁,并进入等待状态,等待到相应的notify并重新获得锁过后才能继续执行;notify()不会立刻立刻释放锁,必须要等notify()所在线程执行完synchronized块中的所有代码才会释放。用如下代码来进行验证:
public static void main(String[] args){
List list = new LinkedList();
Thread r = new Thread(new ReadList(list));
Thread w = new Thread(new WriteList(list));
r.start();
w.start();
}
class ReadList implements Runnable{ private List list; public ReadList(List list){ this.list = list; } @Override
public void run(){
System.out.println("ReadList begin at "+System.currentTimeMillis());
synchronized (list){
try {
Thread.sleep();
System.out.println("list.wait() begin at "+System.currentTimeMillis());
list.wait();
System.out.println("list.wait() end at "+System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("ReadList end at "+System.currentTimeMillis()); }
} class WriteList implements Runnable{ private List list; public WriteList(List list){ this.list = list; } @Override
public void run(){
System.out.println("WriteList begin at "+System.currentTimeMillis());
synchronized (list){
System.out.println("get lock at "+System.currentTimeMillis());
list.notify();
System.out.println("list.notify() at "+System.currentTimeMillis());
try {
Thread.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("get out of block at "+System.currentTimeMillis());
}
System.out.println("WriteList end at "+System.currentTimeMillis()); }
}
运行结果
ReadList begin at
WriteList begin at
list.wait() begin at
get lock at
list.notify() at
get out of block at
WriteList end at
list.wait() end at
ReadList end at
可见读线程开始运行,开始wait过后,写线程才获得锁;写线程走出同步块而不是notify过后,读线程才wait结束,亦即获得锁。所以notify不会释放锁,wait会释放锁。值得一提的是,notifyall()会通知等待队列中的所有线程。
编码
编码模式比较简单,单一,不必显示的获得锁,释放锁,能降低因粗心忘记释放锁的错误。使用模式如下:
synchronized(object){ }
灵活性
- 内置锁在进入同步块时,采取的是无限等待的策略,一旦开始等待,就既不能中断也不能取消,容易产生饥饿与死锁的问题
- 在线程调用notify方法时,会随机选择相应对象的等待队列的一个线程将其唤醒,而不是按照FIFO的方式,如果有强烈的公平性要求,比如FIFO就无法满足
性能
Synchronized在JDK1.5及之前性能(主要指吞吐率)比较差,扩展性也不如ReentrantLock。但是JDK1.6以后,修改了管理内置锁的算法,使得Synchronized和标准的ReentrantLock性能差别不大。
ReentrantLock
ReentrantLock是显示锁,需要显示进行 lock 以及 unlock 操作。
通信
与ReentrantLock搭配的通行方式是Condition,如下:
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
condition.await();//this.wait();
condition.signal();//this.notify();
condition.signalAll();//this.notifyAll();
Condition是被绑定到Lock上的,必须使用lock.newCondition()才能创建一个Condition。从上面的代码可以看出,Synchronized能实现的通信方式,Condition都可以实现,功能类似的代码写在同一行中。而Condition的优秀之处在于它可以为多个线程间建立不同的Condition,比如对象的读/写Condition,队列的空/满Condition,在JDK源码中的ArrayBlockingQueue中就使用了这个特性:
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= )
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == )
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = ;
count++;
notEmpty.signal();
}
private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = ;
count--;
if (itrs != null)
itrs.elementDequeued();
notFull.signal();
return x;
}
编码
Lock lock = new ReentrantLock();
lock.lock();
try{ }finally{
lock.unlock();
}
相比于Synchronized要复杂一些,而且一定要记得在finally中释放锁而不是其他地方,这样才能保证即使出了异常也能释放锁。
灵活性
- lock.lockInterruptibly() 可以使得线程在等待锁是支持响应中断;lock.tryLock() 可以使得线程在等待一段时间过后如果还未获得锁就停止等待而非一直等待。有了这两种机制就可以更好的制定获得锁的重试机制,而非盲目一直等待,可以更好的避免饥饿和死锁问题
- ReentrantLock可以成为公平锁(非默认的),所谓公平锁就是锁的等待队列的FIFO,不过公平锁会带来性能消耗,如果不是必须的不建议使用。这和CPU对指令进行重排序的理由是相似的,如果强行的按照代码的书写顺序来执行指令,就会浪费许多时钟周期,达不到最大利用率
性能
虽然Synchronized和标准的ReentrantLock性能差别不大,但是ReentrantLock还提供了一种非互斥的读写锁,
也就是不强制每次最多只有一个线程能持有锁,它会避免“读/写”冲突,“写/写”冲突,但是不会排除“读/读”冲突,
因为“读/读”并不影响数据的完整性,所以可以多个读线程同时持有锁,这样在读写比较高的情况下,性能会有很大的提升。
下面用两种锁分别实现的线程安全的linkedlist:
class RWLockList {//读写锁 private List list;
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock readLock = lock.readLock();
private final Lock writeLock = lock.writeLock(); public RWLockList(List list){this.list = list;} public int get(int k) {
readLock.lock();
try {
return (int)list.get(k);
} finally {
readLock.unlock();
}
} public void put(int value) {
writeLock.lock();
try {
list.add(value);
} finally {
writeLock.unlock();
}
}
} class SyncList { private List list; public SyncList(List list){this.list = list;} public synchronized int get(int k){
return (int)list.get(k);
} public synchronized void put(int value){
list.add(value);
} }
读写锁测试代码:
List list = new LinkedList();
for (int i=;i<;i++){
list.add(i);
}
RWLockList rwLockList = new RWLockList(list);//初始化数据 Thread writer = new Thread(new Runnable() {
@Override public void run() {
for (int i=;i<;i++){
rwLockList.put(i);
}
}
});
Thread reader1 = new Thread(new Runnable() {
@Override public void run() {
for (int i=;i<;i++){
rwLockList.get(i);
}
}
});
Thread reader2 = new Thread(new Runnable() {
@Override public void run() {
for (int i=;i<;i++){
rwLockList.get(i);
}
}
});
long begin = System.currentTimeMillis();
writer.start();reader1.start();reader2.start();
try {
writer.join();
reader1.join();
reader2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("RWLockList take "+(System.currentTimeMillis()-begin) + "ms");
同步锁测试代码:
List list = new LinkedList();
for (int i=;i<;i++){
list.add(i);
}
SyncList syncList = new SyncList(list);//初始化数据
Thread writerS = new Thread(new Runnable() {
@Override public void run() {
for (int i=;i<;i++){
syncList.put(i);
}
}
});
Thread reader1S = new Thread(new Runnable() {
@Override public void run() {
for (int i=;i<;i++){
syncList.get(i);
}
}
});
Thread reader2S = new Thread(new Runnable() {
@Override public void run() {
for (int i=;i<;i++){
syncList.get(i);
}
}
});
long begin1 = System.currentTimeMillis();
writerS.start();reader1S.start();reader2S.start();
try {
writerS.join();
reader1S.join();
reader2S.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("SyncList take "+(System.currentTimeMillis()-begin1) + "ms");
结果:
RWLockList take 248ms
RWLockList take 255ms
RWLockList take 249ms
RWLockList take 224ms
SyncList take 351ms
SyncList take 367ms
SyncList take 315ms
SyncList take 323ms
可见读写锁的确是优于纯碎的互斥锁
总结
内置锁最大优点是简洁易用,显示锁最大优点是功能丰富,所以能用内置锁就用内置锁,在内置锁功能不能满足之时在考虑显示锁。
Java多线程之内置锁与显示锁的更多相关文章
- “全栈2019”Java多线程第二十八章:公平锁与非公平锁详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- “全栈2019”Java多线程第十七章:同步锁详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- java架构之路(多线程)AQS之ReetrantLock显示锁的使用和底层源码解读
说完了我们的synchronized,这次我们来说说我们的显示锁ReetrantLock. 上期回顾: 上次博客我们主要说了锁的分类,synchronized的使用,和synchronized隐式锁的 ...
- Java多线程简析——Synchronized(同步锁)、Lock以及线程池
Java多线程 Java中,可运行的程序都是有一个或多个进程组成.进程则是由多个线程组成的.最简单的一个进程,会包括mian线程以及GC线程. 线程的状态 线程状态由以下一张网上图片来说明: 在图中, ...
- Java多线程学习(六)Lock锁的使用
系列文章传送门: Java多线程学习(二)synchronized关键字(1) Java多线程学习(二)synchronized关键字(2) Java多线程学习(三)volatile关键字 Java多 ...
- Java多线程中的竞争条件、锁以及同步的概念
竞争条件 1.竞争条件: 在java多线程中,当两个或以上的线程对同一个数据进行操作的时候,可能会产生“竞争条件”的现象.这种现象产生的根本原因是因为多个线程在对同一个数据进行操作,此时对该数据的操作 ...
- Java并发(基础知识)——显示锁和同步工具类
显示锁 Lock接口是Java ...
- Java多线程(五) —— 线程并发库之锁机制
参考文献: http://www.blogjava.net/xylz/archive/2010/07/08/325587.html 一.Lock与ReentrantLock 前面的章节主要谈谈原子操作 ...
- Java多线程并发05——那么多的锁你都了解了吗
在多线程或高并发情境中,经常会为了保证数据一致性,而引入锁机制,本文将为各位带来有关锁的基本概念讲解.关注我的公众号「Java面典」了解更多 Java 相关知识点. 根据锁的各种特性,可将锁分为以下几 ...
随机推荐
- 容器set和multiset
一.set和multiset基础 set和multiset会根据特定的排序准则,自动将元素进行排序.不同的是后者允许元素重复而前者不允许. 需要包含头文件: #include <set> ...
- Spark学习视频整合
1.<Scala深入浅出实战经典>http://pan.baidu.com/s/1pJnAUr5 2.<Spark纯实战公益大讲坛>http://pan.baidu.com/s ...
- python 实现简单的感知机
最近在自学机器学习,记录下一些学习记录 如何用python实现一个简单的感知机 需要安装numpy库,即下面用到的np 简单的说就是 通过计算权重向量w和输入向量x的线性组合,判断该线性组合是否大于某 ...
- python matplotlib.pyplot学习记录
matplotlib是python中很强大的绘图工具,在机器学习中经常用到 首先是导入 import matplotlib.pyplot as plt plt中有很多方法,记录下常用的方法 plt.p ...
- Struts2的动态Action实现
源自:Struts2的动态Action实现 在Struts2中动态方法调用有三种方式. 一.指定method属性在struts.xml中指定action的method属性. <package n ...
- Node.js 向一个文件添加内容
最简方案: fs.appendFile('message.txt', 'data to append', function (err) { }); 参考文档: http://www.codeweblo ...
- PHP实现程序单例执行
原创文章,转载请注明出处:http://huyanping.sinaapp.com/?p=222 作者:Jenner 一.场景描写叙述: 近期我们一块业务.须要不断的监听一个文件夹的变化.假设文件夹中 ...
- maven install时报错Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.12.4:test
事故现场: 解决办法: 一是命令行, mvn clean package -Dmaven.test.skip=true 二是写入pom文件, <plugin> <groupId> ...
- Wireshark Lua: 一个从RTP抓包里导出H.264 Payload,变成264裸码流文件(xxx.264)的Wireshark插件
Wireshark Lua: 一个从RTP抓包里导出H.264 Payload,变成264裸码流文件(xxx.264)的Wireshark插件 在win7-64, wireshark Version ...
- 初识 Swift编程语言(中文版)
前言 今天Apple公布了新的编程语言Swift.也提供了一本将近400页的 The Swift Programming Language(Swift编程语言). 尽管我没有开发人员账号.没法实际上机 ...