Java多线程发展简史(4)
JDK 6.0
JDK 6.0对锁做了一些优化,比如锁自旋、锁消除、锁合并、轻量级锁、所偏向等。在这里不一一介绍,但是给一个例子以有感性认识:
import java.util.Vector; public class LockElimination { public String getStr() { Vector v = new Vector(); v.add(3); v.add(4); return v.toString(); } public static void main(String[] args) { System.out.println(new LockElimination().getStr()); } } 在这个例子中,对vector的加锁完全是没有必要的,这样的锁是可以被优化消除的。
CyclicBarrier是JDK 6.0新增的一个用于流程控制的类,这个类可以保证多个任务在并行执行都完成的情况下,再统一执行下一步操作:

上面这个例子就模拟了,两个子任务(分别执行2000毫秒和4000毫秒)完成以后,再执行一个总任务(2000毫秒)并打印完成。
还有一个类似的类是CountDownLatch(使用倒数计数的方式),这样的类出现标志着,JDK对并发的设计已经逐步由微观转向宏观了,开始逐步重视并发程序流程,甚至是框架上的设计,这样的思路我们会在下文的JDK 7.0中继续看到。
import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class BarrierUsage extends Thread { private static CyclicBarrier barrier = new CyclicBarrier(2, new Thread() { public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { } System.out.println("finish"); }; }); private final int sleepMilSecs; public BarrierUsage(int sleepMilSecs) { this.sleepMilSecs = sleepMilSecs; } @Override public void run() { try { Thread.sleep(sleepMilSecs); System.out.println(sleepMilSecs + " secs slept"); barrier.await(); } catch (InterruptedException e) { } catch (BrokenBarrierException e) { } } public static void main(String[] args) { new BarrierUsage(2000).start(); new BarrierUsage(4000).start(); } } JDK 7.0
2011年的JDK 7.0进一步完善了并发流程控制的功能,比如fork-join框架:

把任务分解成不同子任务完成;比如Phaser这个类,整合了CyclicBarrier和CountDownLatch两个类的功能,但是提供了动态修改依赖目标的能力;还有NIO2的新开放特性。这里不详细介绍了。
Java的未来
在多线程编程方面,Java的未来会怎样?
JDK 8.0按计划将在2013年夏天发布,Java从动态语言那里学了很多过来,比如闭包等等,在多线程方面会怎样呢?郁于JLS所限,无法有大的突破,还是有另辟蹊径的办法?纵观整个Java发展的历程,都在努力修正多线程模型实现上的种种弊端,尽可能在保留虚拟机优化特性的基础上给使用者屏蔽细节。
在来回想一下Java最基础的线程模型,其他语言是怎样实现的呢?
比如C#,任何类的任何方法,都可以成为线程的执行方法:
using System; using System.Threading; public class AnyClass { public void DoSth() { Console.WriteLine("working"); } } class ThreadTest{ public static void Main() { AnyClass anyClass = new AnyClass(); ThreadStart threadDelegate = new ThreadStart(anyClass.DoSth); Thread myThread = new Thread(threadDelegate); myThread.Start(); } } 上面的AnyClass的DoSth方法,就模拟线程执行打印了一句话。
再来看一门小众语言Io,在语法糖的帮助下,实现更加简单:
thread := Object clone thread start := method("working" println) thread @@start 因为Io是基于原型的语言(如果你有兴趣的话,可以在我的blog里找到Io介绍),通过这样的@符号,就实现了新启一个线程运行的功能。
再来看看JDK 5.0的ReentrantLock类,它完全实现了synchronized语义上的全部功能,并且还能具备诸如条件锁、锁超时、公平锁等等更优越的特性(特别值得一提的是tryLock的功能也实现了,就是说可以判定假如这个时间获取锁是否能够成功),甚至在并发量居高不下时,性能还更加优越……我不禁要问,用一个Java实现的锁类去从功能上代替一个已有的同步关键字,这岂不是Java自己在抽自己嘴巴?
import java.util.concurrent.locks.ReentrantLock; public class ReentrantLockUsage implements Runnable { private static ReentrantLock lock = new ReentrantLock(); @Override public void run() { lock.lock(); try { System.out.println("do something 1"); Thread.sleep(2000); } catch (InterruptedException e) { } finally { lock.unlock(); // Why put it in finally block? } System.out.println("finish 1"); } public static void main(String[] args) { new Thread(new ReentrantLockUsage()).start(); lock.lock(); try { System.out.println("do something 2"); Thread.sleep(2000); } catch (InterruptedException e) { } finally { lock.unlock(); } System.out.println("finish 2"); } } 其实这个问题渊源已久,JLS在最初把Java锁机制(包括synchronized关键字)定得太死,以至于无法在上面做进一步的修正和优化,无奈只好另外重新建一个类来做这些未竟的事情。如果让Jame Gosling重新回到二十多年前,他也许会选择不同的实现。
关于协程(coroutine)。很多语言都内置了对协程的实现(协程的含义请自行查阅维基百科),听起来似乎是一个崭新的名字,但是其实这个概念一点都不新,JavaScript引擎对单个页面的解析就是通过协程的方式在一个线程内完成的。协程的实现困难有两点,一个是异常的处理,一个是出入线程时现场(包括堆栈)的保存和设置。有一些开源库已经有了Java上协程的实现,如果你感兴趣的话,不妨关注Kilim和Coroutine for Java。
最后,让我们来回顾一下Java多线程发展的历史。从Java诞生到如今有二十年了,可未来会怎样,又谁知道呢?

补充2012-09-18:
jinnianshilongnian回复:
一、我觉得非原子性的++操作这句话有点模糊,如下所示:
1、nonAtomicCounter++; 不是原子的原因是因为它是静态/实例变量,需要 读/操作/写对象成员变量。可以加把锁保证读/操作/写原子性
synchronized(atomicCounter) {
nonAtomicCounter++;
}
2、如果nonAtomicCounter++; 是局部变量 仅有一条指令 iinc i,1;但局部变量又不会线程不安全;
3、nonAtomicCounter如果是long(64位)的在32位机器即使是局部变量也是线程不安全的(四火补充:在64位机器上也不是线程安全的);
4、Atomic×××等类通过Unsafe的compareAndSwap××× 即CAS完成的。
应该是使用成员变量的++时。
二、对于文中这段话:
但是,上面的情况是对boolValue使用volatile修饰保证其可见性的情况下出现的,如果不对boolValue使用 volatile修饰,运行时就一次不会出现(起码在我的电脑上)打印“WTF!”的情形,换句话说,这反而是不太正常的,我无法猜测JVM做了什么操作,基本上唯一可以确定的是,没有用volatile修饰的时候,boolValue在获取的时候,并不能总取到最真实的值。
这个应该是工作内存 和 主内存 同步的问题。 用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值。
但[boolValue == !boolValue] 和 check/swap 操作并不是原子操作。
也可以通过 在check/swap的两个boolValue加锁来保证同步
synchronized(this) {
boolValue = !boolValue;
}
三、对于DCL问题那段代码,网上也有文章说即使使用volatile也不能保证DCL的安全性:
http://www.ibm.com/developerworks/java/library/j-dcl/index.html
四、说明:
你给的ibm那个文章链接也说到,“The memory model allows what is known as "out-of-order writes" and is a prime reason why this idiom fails.” 所以根因在于out of order writes,引起的问题是“partially initialized”,但是文章里面提到使用volatile不能解决问题的原因在于一些JVM的实现并不能保证顺序一致性,换句话说,对于 happens-before守则并没有严格遵守,且不说他的说法是否有根据,我谈论这个问题的时候一定是在JLS明确规定以下进行的。至于虚拟机实现上的问题,我不得而知。
FYI:
http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#dcl
http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
另外对于element前面如果不加volatile/final的话,也不能保证解决DCL问题,这里四火做一个说明:
在于instance的可见性由volatile保证了,可是element的name并没有任何语义上的保证,这里可以使用 volatile,但是对于不可变对象其实也可以使用在这里语义弱一些的final,也是可以解决问题的,JSR133对这两个关键字的语义做了强化,我上面给的链接里面也提到了,“the values assigned to the final fields in the constructor will be visible to all other threads without synchronization”。
感谢讨论。
原文链接:http://www.raychase.net/?p=698
ref:http://developer.51cto.com/art/201209/357617_3.htm