在分布式开发中,锁是线程控制的重要途径。Java为此也提供了2种锁机制,synchronized和lock。做为Java爱好者,自然少不了对比一下这2种机制,也能从中学到些分布式开发需要注意的地方。
我们先从最简单的入手,逐步分析这2种的区别。
一、synchronized和lock的用法区别
synchronized:在需要同步的对象中加入此控制,synchronized可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。
lock:需要显示指定起始位置和终止位置。一般使用ReentrantLock类做为锁,多个线程中必须要使用一个ReentrantLock类做为对象才能保证锁的生效。且在加锁和解锁处需要通过lock()和unlock()显示指出。所以一般会在finally块中写unlock()以防死锁。
用法区别比较简单,这里不赘述了,如果不懂的可以看看Java基本语法。
二、synchronized和lock性能区别
synchronized是托管给JVM执行的,而lock是java写的控制锁的代码。在Java1.5中,synchronize是性能低效的。因为这是一个重量级操作,需要调用操作接口,导致有可能加锁消耗的系统时间比加锁以外的操作还多。相比之下使用Java提供的Lock对象,性能更高一些。但是到了Java1.6,发生了变化。synchronize在语义上很清晰,可以进行很多优化,有适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在Java1.6上synchronize的性能并不比Lock差。官方也表示,他们也更支持synchronize,在未来的版本中还有优化余地。
说到这里,还是想提一下这2中机制的具体区别。据我所知,synchronized原始采用的是CPU悲观锁机制,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。而在CPU转换线程阻塞时会引起线程上下文切换,当有很多线程竞争锁的时候,会引起CPU频繁的上下文切换导致效率很低。
而Lock用的是乐观锁方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁实现的机制就是CAS操作(Compare and Swap)。我们可以进一步研究ReentrantLock的源代码,会发现其中比较重要的获得锁的一个方法是compareAndSetState。这里其实就是调用的CPU提供的特殊指令。
现代的CPU提供了指令,可以自动更新共享数据,而且能够检测到其他线程的干扰,而 compareAndSet() 就用这些代替了锁定。这个算法称作非阻塞算法,意思是一个线程的失败或者挂起不应该影响其他线程的失败或挂起的算法。
我也只是了解到这一步,具体到CPU的算法如果感兴趣的读者还可以在查阅下,如果有更好的解释也可以给我留言,我也学习下。
三、synchronized和lock用途区别
synchronized原语和ReentrantLock在一般情况下没有什么区别,但是在非常复杂的同步应用中,请考虑使用ReentrantLock,特别是遇到下面2种需求的时候。
1.某个线程在等待一个锁的控制权的这段时间需要中断
2.需要分开处理一些wait-notify,ReentrantLock里面的Condition应用,能够控制notify哪个线程
3.具有公平锁功能,每个到来的线程都将排队等候
下面细细道来……
先说第一种情况,ReentrantLock的lock机制有2种,忽略中断锁和响应中断锁,这给我们带来了很大的灵活性。比如:如果A、B2个线程去竞争锁,A线程得到了锁,B线程等待,但是A线程这个时候实在有太多事情要处理,就是一直不返回,B线程可能就会等不及了,想中断自己,不再等待这个锁了,转而处理其他事情。这个时候ReentrantLock就提供了2种机制,第一,B线程中断自己(或者别的线程中断它),但是ReentrantLock不去响应,继续让B线程等待,你再怎么中断,我全当耳边风(synchronized原语就是如此);第二,B线程中断自己(或者别的线程中断它),ReentrantLock处理了这个中断,并且不再等待这个锁的到来,完全放弃。(如果你没有了解java的中断机制,请参考下相关资料,再回头看这篇文章,80%的人根本没有真正理解什么是java的中断,呵呵)
这里来做个试验,首先搞一个Buffer类,它有读操作和写操作,为了不读到脏数据,写和读都需要加锁,我们先用synchronized原语来加锁,如下:
11 |
long startTime = System.currentTimeMillis(); |
12 |
System.out.println( "开始往这个buff写入数据…" ); |
15 |
if (System.currentTimeMillis() |
16 |
- startTime > Integer.MAX_VALUE) |
19 |
System.out.println( "终于写完了" ); |
25 |
System.out.println( "从这个buff读数据" ); |
接着,我们来定义2个线程,一个线程去写,一个线程去读。
1 |
public class Writer extends Thread { |
5 |
public Writer(Buffer buff) { |
16 |
public class Reader extends Thread { |
20 |
public Reader(Buffer buff) { |
27 |
buff.read(); //这里估计会一直阻塞 |
29 |
System.out.println( "读结束" ); |
好了,写一个Main来试验下,我们有意先去“写”,然后让“读”等待,“写”的时间是无穷的,就看“读”能不能放弃了。
2 |
public static void main(String[] args) { |
3 |
Buffer buff = new Buffer(); |
5 |
final Writer writer = new Writer(buff); |
6 |
final Reader reader = new Reader(buff); |
11 |
new Thread( new Runnable() { |
15 |
long start = System.currentTimeMillis(); |
18 |
if (System.currentTimeMillis() |
20 |
System.out.println( "不等了,尝试中断" ); |
我们期待“读”这个线程能退出等待锁,可是事与愿违,一旦读这个线程发现自己得不到锁,就一直开始等待了,就算它等死,也得不到锁,因为写线程要21亿秒才能完成 T_T ,即使我们中断它,它都不来响应下,看来真的要等死了。这个时候,ReentrantLock给了一种机制让我们来响应中断,让“读”能伸能屈,勇敢放弃对这个锁的等待。我们来改写Buffer这个类,就叫BufferInterruptibly吧,可中断缓存。
1 |
import java.util.concurrent.locks.ReentrantLock; |
3 |
public class BufferInterruptibly { |
5 |
private ReentrantLock lock = new ReentrantLock(); |
10 |
long startTime = System.currentTimeMillis(); |
11 |
System.out.println( "开始往这个buff写入数据…" ); |
14 |
if (System.currentTimeMillis() |
15 |
- startTime > Integer.MAX_VALUE) |
18 |
System.out.println( "终于写完了" ); |
24 |
public void read() throws InterruptedException { |
25 |
lock.lockInterruptibly(); // 注意这里,可以响应中断 |
27 |
System.out.println( "从这个buff读数据" ); |
当然,要对reader和writer做响应的修改
1 |
public class Reader extends Thread { |
3 |
private BufferInterruptibly buff; |
5 |
public Reader(BufferInterruptibly buff) { |
13 |
buff.read(); //可以收到中断的异常,从而有效退出 |
14 |
} catch (InterruptedException e) { |
15 |
System.out.println( "我不读了" ); |
18 |
System.out.println( "读结束" ); |
27 |
public class Writer extends Thread { |
29 |
private BufferInterruptibly buff; |
31 |
public Writer(BufferInterruptibly buff) { |
43 |
public static void main(String[] args) { |
44 |
BufferInterruptibly buff = new BufferInterruptibly(); |
46 |
final Writer writer = new Writer(buff); |
47 |
final Reader reader = new Reader(buff); |
52 |
new Thread( new Runnable() { |
56 |
long start = System.currentTimeMillis(); |
58 |
if (System.currentTimeMillis() |
60 |
System.out.println( "不等了,尝试中断" ); |
这次“读”线程接收到了lock.lockInterruptibly()中断,并且有效处理了这个“异常”。
至于第二种情况,ReentrantLock可以与Condition的配合使用,Condition为ReentrantLock锁的等待和释放提供控制逻辑。
例如,使用ReentrantLock加锁之后,可以通过它自身的Condition.await()方法释放该锁,线程在此等待Condition.signal()方法,然后继续执行下去。await方法需要放在while循环中,因此,在不同线程之间实现并发控制,还需要一个volatile的变量,boolean是原子性的变量。因此,一般的并发控制的操作逻辑如下所示:
1 |
volatile boolean isProcess = false ; |
2 |
ReentrantLock lock = new ReentrantLock(); |
3 |
Condtion processReady = lock.newCondtion(); |
8 |
while (!isProcessReady) { //isProcessReady 是另外一个线程的控制变量 |
9 |
processReady.await(); //释放了lock,在此等待signal |
10 |
} catch (InterruptedException e) { |
11 |
Thread.currentThread().interrupt(); |
这里只是代码使用的一段简化,下面我们看Hadoop的一段摘取的源码:
1 |
private class MapOutputBuffer<K extends Object, V extends Object> |
2 |
implements MapOutputCollector<K, V>, IndexedSortable { |
4 |
boolean spillInProgress; |
5 |
final ReentrantLock spillLock = new ReentrantLock(); |
6 |
final Condition spillDone = spillLock.newCondition(); |
7 |
final Condition spillReady = spillLock.newCondition(); |
8 |
volatile boolean spillThreadRunning = false ; |
9 |
final SpillThread spillThread = new SpillThread(); |
11 |
public MapOutputBuffer(TaskUmbilicalProtocol umbilical, JobConf job, |
13 |
) throws IOException, ClassNotFoundException { |
15 |
spillInProgress = false ; |
16 |
spillThread.setDaemon( true ); |
17 |
spillThread.setName( "SpillThread" ); |
21 |
while (!spillThreadRunning) { |
24 |
} catch (InterruptedException e) { |
25 |
throw new IOException( "Spill thread failed to initialize" , e); |
31 |
protected class SpillThread extends Thread { |
36 |
spillThreadRunning = true ; |
40 |
while (!spillInProgress) { |
46 |
} catch (Throwable t) { |
47 |
sortSpillException = t; |
50 |
if (bufend < bufstart) { |
51 |
bufvoid = kvbuffer.length; |
55 |
spillInProgress = false ; |
58 |
} catch (InterruptedException e) { |
59 |
Thread.currentThread().interrupt(); |
62 |
spillThreadRunning = false ; |
代码中spillDone 就是 spillLock的一个newCondition()。调用spillDone.await()时可以释放spillLock锁,线程进入阻塞状态,而等待其他线程的 spillDone.signal()操作时,就会唤醒线程,重新持有spillLock锁。
这里可以看出,利用lock可以使我们多线程交互变得方便,而使用synchronized则无法做到这点。
最后呢,ReentrantLock这个类还提供了2种竞争锁的机制:公平锁和非公平锁。这2种机制的意思从字面上也能了解个大概:即对于多线程来说,公平锁会依赖线程进来的顺序,后进来的线程后获得锁。而非公平锁的意思就是后进来的锁也可以和前边等待锁的线程同时竞争锁资源。对于效率来讲,当然是非公平锁效率更高,因为公平锁还要判断是不是线程队列的第一个才会让线程获得锁。
- Java synchronized和 Lock 的区别与用法
在分布式开发中,锁是线程控制的重要途径.Java为此也提供了2种锁机制,synchronized和lock.做为Java爱好者,自然少不了对比一下这2种机制,也能从中学到些分布式开发需要注意的地方. ...
- Synchronize 和 Lock 的区别与用法
一.synchronized和lock的用法区别 (1)synchronized(隐式锁):在需要同步的对象中加入此控制,synchronized可以加在方法上,也可以加在特定代码块中,括号中表示需要 ...
- java抽象类与接口的区别及用法
java抽象类与接口的区别及用法 一.抽象类里面的方法可以有实现,但是接口里面的方法确是只能声明. 二.接口是设计的结果 :抽象类是重构的结果 . 三.java不支持多重继承,所以继承抽象类只能继承一 ...
- java - synchronized与lock的区别
synchronized与lock的区别 原始构成 synchronized是关键字属于JVM层面 monitorenter(底层是通过monitor对象来完成,其实wait/notify等对象也依赖 ...
- synchronize和lock的区别 & synchionzie与volatile的区别
synchronized与Lock的区别 https://www.cnblogs.com/iyyy/p/7993788.html Lock和synchronized和volatile的区别和使用 ht ...
- Synchronized和lock的区别和用法
一.synchronized和lock的用法区别 (1)synchronized(隐式锁):在需要同步的对象中加入此控制,synchronized可以加在方法上,也可以加在特定代码块中,括号中表示需要 ...
- 关于synchronize与lock的区别
参考文献:https://www.cnblogs.com/cloudblogs/p/6440160.html 一.synchronize修饰不同代码都是锁住了什么? 大家都知道synchronize可 ...
- 多线程---再次认识volatile,Synchronize,lock
在多线程中我们常用的保证共享变量的方法有很多,现在我们介绍其中的一种,volatile,也是效率最高的一种. 一 .volatile的意义: 为了确保共享变量能被正确和一 ...
- java并发之Lock以及和synchronized区别
从Java5之后,在Java.util.concurrent.locks包下提供了另外一种方式来实现同步访问,那就是Lock. 1.Lock 首先要说明的就是Lock,通过查看Lock的源码可知,Lo ...
随机推荐
- dropwatch 网络协议栈丢包检查利器 与 火丁笔记
http://blog.yufeng.info/archives/2497 源码:http://git.fedorahosted.org/cgit/dropwatch.git http://blog. ...
- ipython结合virtualenv使用
1.virtualenv使python的开发环境相互隔离,隔离环境可以安装自己的依赖包,避免冲突 2.ipython是交互使用python变的便利 3.在virtualenv环境里使用ipython即 ...
- hdu2204Eddy's爱好
大概题意是要你输出1到n中,可以表示成a^b的数,a,b都是大于0的整数的个数, 当中b大于1. 由于1到n中.可以全然开平方的个数就是(n^0.5)的整数部分. 以此类推能够得到,全然开立方.全然开 ...
- 生活娱乐 360安全卫士和QQ大战
360安全卫士指控QQ侵犯用户隐私 [提要]9月26日晚上11点16分,安全软件商360在他们的论坛中发布了最新公告:<360安全卫士发布隐私保护器 专门曝光"窥私"软件&g ...
- leetcode Valid Palindrome C++&python 题解
题目描写叙述 Given a string, determine if it is a palindrome, considering only alphanumeric characters and ...
- Indri和Terrier搜索引擎的使用
介绍 Indri和Terrier都是开源的搜索引擎,当中Indri作为Lemur项目的一个重要部分,具有强大的查询接口,易建索引,可扩展,高效率等长处.能够在SourceForge Lemur Pro ...
- java开始到熟悉66-69
本次内容:DateFormat类 1.DateFormat类 package array; /** * 时间和字符串之间的转化 */ import java.text.DateFormat; impo ...
- nfs 挂载错误
[ 147.080000] svc: failed to register lockdv1 RPC service (errno 146). [ 147.090000] lockd_up: makes ...
- (org.openqa.selenium.WebDriverException: Unable to launch the app: Error: Trying to start logcat capture but it's already started! )错误解决办法
新增: capabilities.setCapability("autoLaunch",false); 将setup中的: driver = new AndroidDriver(n ...
- Gym - 100341C FFT优化DP
题目链接:传送门 题解: 设定dp[i][j]在深度为i下,使用j个节点的方案数 显然的转移方程组就是 dp[h][n] = dp[h-1][i] * dp[h-1][n-i-1] + 2*dp[h- ...