在分布式开发中,锁是线程控制的重要途径。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原语来加锁,如下:
查看源代码打印帮助
1
public class Buffer {
2
3
private Object lock;
4
5
public Buffer() {
6
lock = this;
7
}
8
9
public void write() {
10
synchronized (lock) {
11
long startTime = System.currentTimeMillis();
12
System.out.println("开始往这个buff写入数据…");
13
for (;;)// 模拟要处理很长时间
14
{
15
if (System.currentTimeMillis()
16
- startTime > Integer.MAX_VALUE)
17
break;
18
}
19
System.out.println("终于写完了");
20
}
21
}
22
23
public void read() {
24
synchronized (lock) {
25
System.out.println("从这个buff读数据");
26
}
27
}
28
}
接着,我们来定义2个线程,一个线程去写,一个线程去读。
1
public class Writer extends Thread {
2
3
private Buffer buff;
4
5
public Writer(Buffer buff) {
6
this.buff = buff;
7
}
8
9
@Override
10
public void run() {
11
buff.write();
12
}
13
14
}
15
16
public class Reader extends Thread {
17
18
private Buffer buff;
19
20
public Reader(Buffer buff) {
21
this.buff = buff;
22
}
23
24
@Override
25
public void run() {
26
27
buff.read();//这里估计会一直阻塞
28
29
System.out.println("读结束");
30
31
}
32
33
}
好了,写一个Main来试验下,我们有意先去“写”,然后让“读”等待,“写”的时间是无穷的,就看“读”能不能放弃了。
1
public class Test {
2
public static void main(String[] args) {
3
Buffer buff = new Buffer();
4
5
final Writer writer = new Writer(buff);
6
final Reader reader = new Reader(buff);
7
8
writer.start();
9
reader.start();
10
11
new Thread(new Runnable() {
12
13
@Override
14
public void run() {
15
long start = System.currentTimeMillis();
16
for (;;) {
17
//等5秒钟去中断读
18
if (System.currentTimeMillis()
19
- start > 5000) {
20
System.out.println("不等了,尝试中断");
21
reader.interrupt();
22
break;
23
}
24
25
}
26
27
}
28
}).start();
29
30
}
31
}
我们期待“读”这个线程能退出等待锁,可是事与愿违,一旦读这个线程发现自己得不到锁,就一直开始等待了,就算它等死,也得不到锁,因为写线程要21亿秒才能完成 T_T ,即使我们中断它,它都不来响应下,看来真的要等死了。这个时候,ReentrantLock给了一种机制让我们来响应中断,让“读”能伸能屈,勇敢放弃对这个锁的等待。我们来改写Buffer这个类,就叫BufferInterruptibly吧,可中断缓存。
查看源代码打印帮助
1
import java.util.concurrent.locks.ReentrantLock;
2
3
public class BufferInterruptibly {
4
5
private ReentrantLock lock = new ReentrantLock();
6
7
public void write() {
8
lock.lock();
9
try {
10
long startTime = System.currentTimeMillis();
11
System.out.println("开始往这个buff写入数据…");
12
for (;;)// 模拟要处理很长时间
13
{
14
if (System.currentTimeMillis()
15
- startTime > Integer.MAX_VALUE)
16
break;
17
}
18
System.out.println("终于写完了");
19
} finally {
20
lock.unlock();
21
}
22
}
23
24
public void read() throws InterruptedException {
25
lock.lockInterruptibly();// 注意这里,可以响应中断
26
try {
27
System.out.println("从这个buff读数据");
28
} finally {
29
lock.unlock();
30
}
31
}
32
33
}
当然,要对reader和writer做响应的修改
查看源代码打印帮助
1
public class Reader extends Thread {
2
3
private BufferInterruptibly buff;
4
5
public Reader(BufferInterruptibly buff) {
6
this.buff = buff;
7
}
8
9
@Override
10
public void run() {
11
12
try {
13
buff.read();//可以收到中断的异常,从而有效退出
14
} catch (InterruptedException e) {
15
System.out.println("我不读了");
16
}
17
18
System.out.println("读结束");
19
20
}
21
22
}
23
24
/**
25
* Writer倒不用怎么改动
26
*/
27
public class Writer extends Thread {
28
29
private BufferInterruptibly buff;
30
31
public Writer(BufferInterruptibly buff) {
32
this.buff = buff;
33
}
34
35
@Override
36
public void run() {
37
buff.write();
38
}
39
40
}
41
42
public class Test {
43
public static void main(String[] args) {
44
BufferInterruptibly buff = new BufferInterruptibly();
45
46
final Writer writer = new Writer(buff);
47
final Reader reader = new Reader(buff);
48
49
writer.start();
50
reader.start();
51
52
new Thread(new Runnable() {
53
54
@Override
55
public void run() {
56
long start = System.currentTimeMillis();
57
for (;;) {
58
if (System.currentTimeMillis()
59
- start > 5000) {
60
System.out.println("不等了,尝试中断");
61
reader.interrupt();
62
break;
63
}
64
65
}
66
67
}
68
}).start();
69
70
}
71
}
这次“读”线程接收到了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();
4
thread: run() {
5
lock.lock();
6
isProcess = true;
7
try {
8
while(!isProcessReady) { //isProcessReady 是另外一个线程的控制变量
9
processReady.await();//释放了lock,在此等待signal
10
}catch (InterruptedException e) {
11
Thread.currentThread().interrupt();
12
} finally {
13
lock.unlock();
14
isProcess = false;
15
}
16
}
17
}
18
}
这里只是代码使用的一段简化,下面我们看Hadoop的一段摘取的源码:
查看源代码打印帮助
1
private class MapOutputBuffer<K extends Object, V extends Object>
2
implements MapOutputCollector<K, V>, IndexedSortable {
3
...
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();
10
...
11
public MapOutputBuffer(TaskUmbilicalProtocol umbilical, JobConf job,
12
TaskReporter reporter
13
) throws IOException, ClassNotFoundException {
14
...
15
spillInProgress = false;
16
spillThread.setDaemon(true);
17
spillThread.setName("SpillThread");
18
spillLock.lock();
19
try {
20
spillThread.start();
21
while (!spillThreadRunning) {
22
spillDone.await();
23
}
24
} catch (InterruptedException e) {
25
throw new IOException("Spill thread failed to initialize", e);
26
} finally {
27
spillLock.unlock();
28
}
29
}
30
31
protected class SpillThread extends Thread {
32
33
@Override
34
public void run() {
35
spillLock.lock();
36
spillThreadRunning = true;
37
try {
38
while (true) {
39
spillDone.signal();
40
while (!spillInProgress) {
41
spillReady.await();
42
}
43
try {
44
spillLock.unlock();
45
sortAndSpill();
46
} catch (Throwable t) {
47
sortSpillException = t;
48
} finally {
49
spillLock.lock();
50
if (bufend < bufstart) {
51
bufvoid = kvbuffer.length;
52
}
53
kvstart = kvend;
54
bufstart = bufend;
55
spillInProgress = false;
56
}
57
}
58
} catch (InterruptedException e) {
59
Thread.currentThread().interrupt();
60
} finally {
61
spillLock.unlock();
62
spillThreadRunning = false;
63
}
64
}
65
}
代码中spillDone 就是 spillLock的一个newCondition()。调用spillDone.await()时可以释放spillLock锁,线程进入阻塞状态,而等待其他线程的 spillDone.signal()操作时,就会唤醒线程,重新持有spillLock锁。
这里可以看出,利用lock可以使我们多线程交互变得方便,而使用synchronized则无法做到这点。
最后呢,ReentrantLock这个类还提供了2种竞争锁的机制:公平锁和非公平锁。这2种机制的意思从字面上也能了解个大概:即对于多线程来说,公平锁会依赖线程进来的顺序,后进来的线程后获得锁。而非公平锁的意思就是后进来的锁也可以和前边等待锁的线程同时竞争锁资源。对于效率来讲,当然是非公平锁效率更高,因为公平锁还要判断是不是线程队列的第一个才会让线程获得锁。
原文地址:http://www.365doit.com/all/news/synchronizeandlock.html/4
- JVM
- 深入理解Java内存模型
- 深入理解Java内存模型(一)——基础
- 深入理解Java内存模型(二)——重排序
- 深入理解Java内存模型(三)——顺序一致性
- 深入理解Java内存模型(四)——volatile
- 深入理解Java内存模型(五)——锁
- 深入理解Java内存模型(六)——final
- 深入理解Java内存模型(七)——总结
- Java内存模型
- Java内存模型2
- 堆内内存还是堆外内存?
- JVM内存配置详解
- Java内存分配全面浅析
- 深入Java核心 Java内存分配原理精讲
- jvm常量池
- JVM调优总结
- JVM调优总结(一)-- 一些概念
- JVM调优总结(二)-一些概念
- VM调优总结(三)-基本垃圾回收算法
- JVM调优总结(四)-垃圾回收面临的问题
- JVM调优总结(五)-分代垃圾回收详述1
- JVM调优总结(六)-分代垃圾回收详述2
- JVM调优总结(七)-典型配置举例1
- JVM调优总结(八)-典型配置举例2
- JVM调优总结(九)-新一代的垃圾回收算法
- JVM调优总结(十)-调优方法
- 基础
- Java 征途:行者的地图
- Java程序员应该知道的10个面向对象理论
- Java泛型总结
- 序列化与反序列化
- 通过反编译深入理解Java String及intern
- android 加固防止反编译-重新打包
- volatile
- 正确使用 Volatile 变量
- 异常
- 深入理解java异常处理机制
- Java异常处理的10个最佳实践
- Java异常处理手册和最佳实践
- Java提高篇——对象克隆(复制)
- Java中如何克隆集合——ArrayList和HashSet深拷贝
- Java中hashCode的作用
- Java提高篇之hashCode
- 常见正则表达式
- 类
- 理解java类加载器以及ClassLoader类
- 深入探讨 Java 类加载器
- 类加载器的工作原理
- java反射
- 集合
- HashMap的工作原理
- ConcurrentHashMap之实现细节
- java.util.concurrent 之ConcurrentHashMap 源码分析
- HashMap的实现原理和底层数据结构
- 线程
- 关于Java并发编程的总结和思考
- 40个Java多线程问题总结
- Java中的多线程你只要看这一篇就够了
- Java多线程干货系列(1):Java多线程基础
- Java非阻塞算法简介
- Java并发的四种风味:Thread、Executor、ForkJoin和Actor
- Java中不同的并发实现的性能比较
- JAVA CAS原理深度分析
- 多个线程之间共享数据的方式
- Java并发编程
- Java并发编程(1):可重入内置锁
- Java并发编程(2):线程中断(含代码)
- Java并发编程(3):线程挂起、恢复与终止的正确方法(含代码)
- Java并发编程(4):守护线程与线程阻塞的四种情况
- Java并发编程(5):volatile变量修饰符—意料之外的问题(含代码)
- Java并发编程(6):Runnable和Thread实现多线程的区别(含代码)
- Java并发编程(7):使用synchronized获取互斥锁的几点说明
- Java并发编程(8):多线程环境中安全使用集合API(含代码)
- Java并发编程(9):死锁(含代码)
- Java并发编程(10):使用wait/notify/notifyAll实现线程间通信的几点重要说明
- java并发编程-II
- Java多线程基础:进程和线程之由来
- Java并发编程:如何创建线程?
- Java并发编程:Thread类的使用
- Java并发编程:synchronized
- Java并发编程:Lock
- Java并发编程:volatile关键字解析
- Java并发编程:深入剖析ThreadLocal
- Java并发编程:CountDownLatch、CyclicBarrier和Semaphore
- Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition
- Synchronized与Lock
- JVM底层又是如何实现synchronized的
- Java synchronized详解
- synchronized 与 Lock 的那点事
- 深入研究 Java Synchronize 和 Lock 的区别与用法
- JAVA编程中的锁机制详解
- Java中的锁
- TreadLocal
- 深入JDK源码之ThreadLocal类
- 聊一聊ThreadLocal
- ThreadLocal
- ThreadLocal的内存泄露
- 多线程设计模式
- Java多线程编程中Future模式的详解
- 原子操作(CAS)
- [译]Java中Wait、Sleep和Yield方法的区别
- 线程池
- 如何合理地估算线程池大小?
- JAVA线程池中队列与池大小的关系
- Java四种线程池的使用
- 深入理解Java之线程池
- java并发编程III
- Java 8并发工具包漫游指南
- 聊聊并发
- 聊聊并发(一)——深入分析Volatile的实现原理
- 聊聊并发(二)——Java SE1.6中的Synchronized
- 文件
- 网络
- index
- 内存文章索引
- 基础文章索引
- 线程文章索引
- 网络文章索引
- IOC
- 设计模式文章索引
- 面试
- Java常量池详解之一道比较蛋疼的面试题
- 近5年133个Java面试问题列表
- Java工程师成神之路
- Java字符串问题Top10
- 设计模式
- Java:单例模式的七种写法
- Java 利用枚举实现单例模式
- 常用jar
- HttpClient和HtmlUnit的比较总结
- IO
- NIO
- NIO入门
- 注解
- Java Annotation认知(包括框架图、详细介绍、示例说明)