Java多线程发展简史(4)

[size=medium]JDK 6.0

JDK 6.0对锁做了一些优化,比如锁自旋、锁消除、锁合并、轻量级锁、所偏向等。在这里不一一介绍,但是给一个例子以有感性认识:[/size]
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());
}
}

[size=medium]在这个例子中,对vector的加锁完全是没有必要的,这样的锁是可以被优化消除的。

CyclicBarrier是JDK 6.0新增的一个用于流程控制的类,这个类可以保证多个任务在并行执行都完成的情况下,再统一执行下一步操作:[/size]

[img]http://dl.iteye.com/upload/attachment/0074/3165/6973552f-111c-3c94-b74f-4057a9e1a878.png[/img]
[size=medium]上面这个例子就模拟了,两个子任务(分别执行2000毫秒和4000毫秒)完成以后,再执行一个总任务(2000毫秒)并打印完成。

还有一个类似的类是CountDownLatch(使用倒数计数的方式),这样的类出现标志着,JDK对并发的设计已经逐步由微观转向宏观了,开始逐步重视并发程序流程,甚至是框架上的设计,这样的思路我们会在下文的JDK 7.0中继续看到。[/size]
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();
}
}

[size=medium]JDK 7.0

2011年的JDK 7.0进一步完善了并发流程控制的功能,比如fork-join框架:[/size]

[img]http://dl.iteye.com/upload/attachment/0074/3169/2cdbabd8-28fe-3145-b70c-a462085565e5.png[/img]
[size=medium]把任务分解成不同子任务完成;比如Phaser这个类,整合了CyclicBarrier和CountDownLatch两个类的功能,但是提供了动态修改依赖目标的能力;还有NIO2的新开放特性。这里不详细介绍了。

Java的未来

在多线程编程方面,Java的未来会怎样?

JDK 8.0按计划将在2013年夏天发布,Java从动态语言那里学了很多过来,比如闭包等等,在多线程方面会怎样呢?郁于JLS所限,无法有大的突破,还是有另辟蹊径的办法?纵观整个Java发展的历程,都在努力修正多线程模型实现上的种种弊端,尽可能在保留虚拟机优化特性的基础上给使用者屏蔽细节。

在来回想一下Java最基础的线程模型,其他语言是怎样实现的呢?

比如C#,任何类的任何方法,都可以成为线程的执行方法:[/size]
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();
}
}

[size=medium]上面的AnyClass的DoSth方法,就模拟线程执行打印了一句话。

再来看一门小众语言Io,在语法糖的帮助下,实现更加简单:[/size]

thread := Object clone
thread start := method("working" println)
thread @@start

[size=medium]因为Io是基于原型的语言(如果你有兴趣的话,可以在我的blog里找到Io介绍),通过这样的@符号,就实现了新启一个线程运行的功能。

再来看看JDK 5.0的ReentrantLock类,它完全实现了synchronized语义上的全部功能,并且还能具备诸如条件锁、锁超时、公平锁等等更优越的特性(特别值得一提的是tryLock的功能也实现了,就是说可以判定假如这个时间获取锁是否能够成功),甚至在并发量居高不下时,性能还更加优越……我不禁要问,用一个Java实现的锁类去从功能上代替一个已有的同步关键字,这岂不是Java自己在抽自己嘴巴?[/size]
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");
}
}

[size=medium]其实这个问题渊源已久,JLS在最初把Java锁机制(包括synchronized关键字)定得太死,以至于无法在上面做进一步的修正和优化,无奈只好另外重新建一个类来做这些未竟的事情。如果让Jame Gosling重新回到二十多年前,他也许会选择不同的实现。

关于协程(coroutine)。很多语言都内置了对协程的实现(协程的含义请自行查阅维基百科),听起来似乎是一个崭新的名字,但是其实这个概念一点都不新,JavaScript引擎对单个页面的解析就是通过协程的方式在一个线程内完成的。协程的实现困难有两点,一个是异常的处理,一个是出入线程时现场(包括堆栈)的保存和设置。有一些开源库已经有了Java上协程的实现,如果你感兴趣的话,不妨关注Kilim和Coroutine for Java。

最后,让我们来回顾一下Java多线程发展的历史。从Java诞生到如今有二十年了,可未来会怎样,又谁知道呢?[/size]

[img]http://dl.iteye.com/upload/attachment/0074/3171/18b2e279-43b0-3aa0-88d8-b3c17b142ab2.png[/img]
[size=medium]补充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[/size]
ref:http://developer.51cto.com/art/201209/357617_3.htm
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值