- 浏览: 478619 次
- 性别:
- 来自: 大连
文章分类
最新评论
-
龘龘龘:
TrueBrian 写道有个问题,Sample 1中,为了控制 ...
What's New on Java 7 Phaser -
龘龘龘:
楼主总结的不错。
What's New on Java 7 Phaser -
TrueBrian:
有个问题,Sample 1中,为了控制线程的启动时机,博主实际 ...
What's New on Java 7 Phaser -
liguanqun811:
不知道楼主是否对zookeeper实现的分布式锁进行过性能测试 ...
Distributed Lock -
hobitton:
mysql的get lock有版本限制,否则get lock可 ...
Distributed Lock
本文节选自 Effective Java by Joshua Bloch 和 Concurrent Programming in Java by Doug Lea.
1.3 原子数据的同步
java语言保证读或写一个变量是原子(atomic)的,除非这个变量的类型是long或double.换句话说,读入一个非long或double类型的变量,可以保证返回值一定是某个线程保存在该变量中的,即使多个线程在没有同步的时候并发地修改这个变量,也是如此。
虽然原子性保证了一个线程在读写数据的时候,不会看到一个随机的数值,但是它并不保证一个线程写入的值对于另外一个线程是可见的。java的内存模型决定,为了在线程之间可靠地通信,以及为了互斥访问,对原子数据的读写进行同步是需要的。考虑下边的序列号生成程序:
private static int nextSerialNumber = 0; public static int generateSerialNumber() { return nextSerialNumber++; }
这个程序的意图是保证每次调用generateSerialNumber都会返回一个不同的序列号,然而,如果没有同步,这个方法并不能正确的工作。递增操作符(++)既要读nextSerialNumber域,也要写nextSerialNumber域,所以它不是原子的。读和写相互独立的操作。因此,多个并发的线程可能会看到nextSerialNumber有相同的值,因而返回相同的序列号。此外,一个线程重复地调用generateSerialNumber,获得从0到n的一系列序列号之后,另外一个线程调用generateSerialNumber并获得一个序列号是0,这是有可能发生的。如果没有同步机制,第二个线程可能根本看不到第一个线程所作的改变。
1.4 监控机制
正如每个Object都有一个锁, 每个Object也有一个等待集合(wait set),它有wait、notify、notifyAll和Thread.interrupt方法来操作。同时拥有锁和等待集合的实体,通常被成为监视器(monitor)。每个Object的等待集合是由JVM维护的。等待集合一直存放着那些因为调用对象的wait方法而被阻塞的线程。由于等待集合和锁之间的交互机制,只有获得目标对象的同步锁时,才可以调用它的wait、notify和notifyAll方法。这种要求通常无法靠编译来检查,如果条件不能满足,那么在运行的时候调用以上方法就会导致其抛出IllegalMonitorStateException。
wait 方法被调用后,会执行如下操作
- 如果当前线程已经被中断,那么该方法立刻退出,然后抛出一个InterruptedException异常。否则线程会被阻塞。
- JVM把该线程放入目标对象内部且无法访问的等待集合中。
- 目标对象的同步锁被释放,但是这个线程锁拥有的其他锁依然会被这个线程保留着。当线程重新恢复质执行时,它会重新获得目标对象的同步锁
notify方法被调用后,会执行如下操作
- 如果存在的话,JVM会从目标对象内部的等待集合中任意移除一个线程T。如果等待集合中的线程数大于1,那么哪个线程被选中完全是随机的。
- T必须重新获得目标对象的同步锁,这必然导致它将会被阻塞到调用Thead.notify的线程释放该同步锁。如果其他线程在T获得此锁之前就获得它,那么T就要一直被阻塞下去。
- T从执行wait的那点恢复执行。
notifyAll方法被调用后的操作和notify类似,不同的只是等待集合中所有的线程(同时)都要执行那些操作。然而等待集合中的线程必须要在竞争到目标对象的同步锁之后,才能继续执行。
interrupt。如果对一个因为调用了wait方法而被挂起的对象调用Thread.interrupt方法,那么这个方法的执行机制就和notify类似,只是在重新获得对象锁后,该方法就会抛出InterruptedException异常,并且该线程的中断状态被置为false。
对于Object.wait()方法,它一定是在一个同步区域中被调用,而且该同步区域锁住了被调用的对象。下边是使用Object.wait()方法的标准模式:
synchronized(obj) { while( condition checking) { obj.wait(); } …// Other operations }
总是要使用wait循环模式来调用wait方法,永远不要在循环的外边调用wait方法。循环的作用在于在等待的前、后都能测试条件。在等待之前测试条件,如果条件成立的话则跳过等待,这对于确保程序的活性(liveness)是必要的。如果条件已经成立,而且在线程等待之前notify(或者notifyAll)方法已经被调用过,那么无法保证该线程将总会从等待中醒过来。在等待之后测试条件,如果条件不成立的话则继续等待,这对于确保程序的安全性(safety)是必要的。当条件不成立的时候,如果线程继续执行,那么可能破坏被锁保护的约束关系。当条件不成立的时候,有以下一些理由可以使一个线程醒过来:
- 从一个线程调用notify方法的时刻起,到等待线程被唤醒的时刻之间,另一个线程得到了锁,并且改变了被保护的状态。
- 条件没有成立,但是另外一个线程可能意外或者恶意地调用了notify方法。在公有对象上调用wait方法,这其实是将自己暴露在危险的境地中。因为任何持有这个对象引用的线程都可以调用该对象的notify方法。
- 在没有被通知的情况下等待线程也可能被唤醒。这被称为“伪唤醒(spurious wakeup)”。虽然《Java语言规范(The Java Language Specification )》并没有提到这种可能,但是许多JVM实现都使用了具有伪唤醒功能的线程设施,尽管用的很少。
与此相关的一个问题是,为了唤醒正在等待的线程,到底应该使用notify方法还是应该使用notifyAll方法。假设所有的wait调用都是在循环的内部,那么使用notifyAll方法是一个合理而保守的做法。它总会产生正确的结果,它可以保证会唤醒所有需要被唤醒的线程。当然,这样也会唤醒其它一些线程,但是这不会影响程序的正确性。这些线程醒来之后会检查等待条件,发现条件不满足,就会继续等待。使用notifyAll方法的另外一个优点在于可以避免来自不相关线程的意外或者恶意等待。否则的话,这样的等待可能会“吞掉”一个关键的通知,使真正的接收线程无限地等待下去。关于使用notifyAll方法的一个不足在于,虽然使用notifyAll方法不会影响程序的正确性,但是会影响程序的性能。
1.5 死锁
尽管完全同步的原子操作很安全,但是线程可能却因此失去了活性(liveness)。死锁(dead lock)是在两个或多个线程都有权限访问两个或多个对象,并且每个线程都在已经得到一个锁的情况下等待其它线程已经得到的锁。假设线程A持有的对象X的锁,并且正在试图获得对象Y的锁,同时,线程B已经拥有的对象Y的锁,并在试图获得对象X的锁。因此没有哪个线程能够执行进一步的操作,死锁就产生了。例如:
public class Cell { private long value; public Cell(long value) { this.value = value; } public synchronized long getValue() { return value; } public synchronized void setValue(long value) { this.value = value; } public synchronized void swap(Cell other) { long t = getValue(); long v = other.getValue(); setValue(v); other.setValue(t); } public static void main(String args[]) throws Exception { // final Cell c1 = new Cell(100); final Cell c2 = new Cell(200); // Thread t1 = new Thread(new Runnable() { public void run() { long count = 0; try { while(true) { c1.swap(c2); count++; if(count % 100 == 0) { System.out.println("thread1's current progress: " + count); } } } catch (Exception e) { e.printStackTrace(); } } }); t1.setName("thread1"); // Thread t2 = new Thread(new Runnable() { public void run() { long count = 0; try { while(true) { c2.swap(c1); count++; if(count % 100 == 0) { System.out.println("thread2's current progress: " + count); } } } catch (Exception e) { e.printStackTrace(); } } }); t2.setName("thread2"); // t1.start(); t2.start(); t1.join(); t2.join(); } }
如果按照下面的时序执行时序,就会导致死锁:
线程A | 线程B |
进入a.swap(b)时获得a的锁 | |
在执行t = getValue()时,顺利获得a的锁(因为已经持有) | 进入b.swap(a)时获得b的锁 |
执行v = other.getValue()时,由于需要b的锁而处于等待的状态 | 在执行t = getValue()时,顺利获得b的锁 |
执行v = other.getValue()时,由于需要a的锁而处于等待状态 |
以上的代码执行一段时间后可能就会发生死锁。此时可以通过thread dump获得线程的栈跟踪信息。在Unix平台下可以通过向JVM发送SIGQUIT信号(kill -3)获得thread dump,在Windows平台下则通过Ctrl+Break。以上代码在死锁时的thread dump如下:
Found one Java-level deadlock:
=============================
"thread2":
waiting to lock monitor 0x0003e664 (object 0x230c3f40, a Cell),
which is held by "thread1"
"thread1":
waiting to lock monitor 0x0003e6a4 (object 0x230c3f50, a Cell),
which is held by "thread2"
Java stack information for the threads listed above:
===================================================
"thread2":
at Cell.getValue(Cell.java:18)
- waiting to lock <0x230c3f40> (a Cell)
at Cell.swap(Cell.java:27)
- locked <0x230c3f50> (a Cell)
at Cell$2.run(Cell.java:65)
at java.lang.Thread.run(Unknown Source)
"thread1":
at Cell.setValue(Cell.java:22)
- waiting to lock <0x230c3f50> (a Cell)
at Cell.swap(Cell.java:29)
- locked <0x230c3f40> (a Cell)
at Cell$1.run(Cell.java:46)
at java.lang.Thread.run(Unknown Source)
Found 1 deadlock.
为了避免死锁的危险,在一个同步的方法或者代码块中,永远不要放弃对客户的控制。换句话说,在一个被同步的区域内部,不要调用一个可被改写的公有或受保护的方法。从包含该同步区域的类的角度来看,这样的一个方法是一个外来者(alien)。这个类不知道该方法会做什么事情,也控制不了它。假设客户的方法创建另一个线程,再回调到这个类中。然后,新建的线程试图获取原线程所拥有的那把锁,这样就会导致新建的线程被阻塞。如果创建该线程的方法正在等待这个线程完成任务,则会导致死锁。
另外一种比较简单的避免死锁的独占技术是顺序化资源(resource ordering),它的思想就是把一个嵌套的synchronized方法或块中使用的对象和一个数字标签关联起来。如果同步操作是根据对象标签的最小优先(least first)的原则,那么刚才介绍的例子的情况就不会发生。也就是说,如果线程A和线程B都按照相同的顺序获得锁,就可以避免死锁的发生。对于数字标签的选择,可以使用System.identityHashCode的返回值,尽管没有什么机制可以保证identityHashCode的惟一性,但是在实际运行的系统中,这个方法的惟一性在很大程度上得到了保证。swap的一个更好的实现如下:
public void swap(Cell other) { if(this == other) return; // Alias check else if(System.identityHashCode(this) < System.identityHashCode(other)) { this.doSwap(other); } else { other.doSwap(this); } } private synchronized void doSwap(Cell Other) { long t = getValue(); long v = other.getValue(); setValue(v); other.setValue(t); }
评论
System.identityHashCode(this) < System.identityHashCode(other)
为什么this 的hashcode 要小于other 的hashcode 呢?
发表评论
-
Understanding the Hash Array Mapped Trie
2012-03-30 10:36 0mark -
Atomic Bit Operation in Linux Kernel
2012-02-08 00:27 1979Linux Kernel支持atomic bit operat ... -
A Hierarchical CLH Queue Lock
2012-01-14 19:01 2110A Hierarchical CLH Queue Lock ( ... -
Inside AbstractQueuedSynchronizer (4)
2012-01-08 17:06 3464Inside AbstractQueuedSynchroniz ... -
Inside AbstractQueuedSynchronizer (3)
2012-01-07 23:37 4592Inside AbstractQueuedSynchroniz ... -
Inside AbstractQueuedSynchronizer (2)
2012-01-07 17:54 6304Inside AbstractQueuedSynchroniz ... -
Inside AbstractQueuedSynchronizer (1)
2012-01-06 11:04 7888Inside AbstractQueuedSynchroniz ... -
Code Optimization
2011-10-14 00:11 1558当前开发人员在进行编码的时候,可能很少关注纯粹代码级别的优化了 ... -
Distributed Lock
2011-08-02 22:02 91301 Overview 在分布式系统中,通常会 ... -
What's New on Java 7 Phaser
2011-07-29 10:15 81381 Overview Java 7的并 ... -
Sequantial Lock in Java
2011-06-07 17:00 21661 Overview Linux内核中常见的同步机 ... -
Feature or issue?
2011-04-26 22:23 121以下代码中,为何CglibTest.intercept ... -
Bloom Filter
2010-10-19 00:41 50191 Overview Bloom filt ... -
Inside java.lang.Enum
2010-08-04 15:40 64041 Introduction to enum J ... -
Open Addressing
2010-07-07 17:59 33951 Overview Open addressi ... -
JLine
2010-06-17 09:11 10949Overview JLine 是一个用来处理控 ... -
ID Generator
2010-06-14 14:45 1633关于ID Generator,想 ... -
inotify-java
2009-07-22 22:58 82081 Overview 最近公 ... -
Perf4J
2009-06-11 23:13 84301 Overview Perf4j是一个用于计算 ... -
Progress Estimator
2009-02-22 19:37 1485Jakarta Commons Cookbook这本书 ...
相关推荐
java concurrent 阻塞队列 线程 里面有详细的例子,下载后请认真阅读里面的内容,可能有点难以理解,请耐心
Java Concurrent in practice (animated)
java concurrent 包 详细解析
使用java concurrent调用xmlp api生成pdf
java concurrent 多线程 PPT
JAVA的CONCURRENT用法详解.pdf
资深Java专家10年经验总结,全程案例式讲解,首本全面介绍Java多线程编程技术的专著 结合大量实例,全面讲解Java多线程编程中的并发访问、线程间通信、锁等最难突破的核心技术与应用实践 封底 Java多线程无处不在,...
1. java.util.concurrent - Java 并发工具包 2. 阻塞队列 BlockingQueue 3. 数组阻塞队列 ArrayBlockingQueue 4. 延迟队列 DelayQueue 5. 链阻塞队列 LinkedBlockingQueue 6. 具有优先级的阻塞队列 ...
Java Concurrent处理并发需求.txt
java并发工具包 java.util.concurrent中文版pdf
详细的java 多线程相关知识 并附有相关练习题
java concurrent包分类结构图
EBS java concurrent program的实现
java同步大杀器concurrent 包java同步大杀器concurrent 包java同步大杀器concurrent 包java同步大杀器concurrent 包java同步大杀器concurrent 包java同步大杀器concurrent 包java同步大杀器concurrent 包java同步大...
JUC使用指导手册 http://tutorials.jenkov.com/java-util-concurrent/blockingqueue.html 中文译文
如何启动:以win7系统为例,最好jdk8 1.打开cmd,cd到jdk的path,本机是:cd C:\Java\jdk6\bin ...java -cp D:\javaConcurrentAnimated.jar vgrazi.concurrent.samples.launcher.ConcurrentExampleLauncher
Concurrent Programming in Java - Design Principles and Patterns
JAVAConcurrent Programming in Java 对于一些JAVA 理解
资源JavaConcurrent实用知识库分享知识分享
JAVA后台程序以及java.concurrent包的应用