Java多线程发展简史(七)

2014-11-24 09:39:52 · 作者: · 浏览: 5
所以一般它的性能要比使用synchronized高。
Lock-free算法就是基于CAS来实现原子化“set”的方式,通常有这样两种形式:

import java.util.concurrent.atomic.AtomicInteger;

public class LockFree {
private AtomicInteger max = new AtomicInteger();

// type A
public void setA(int value) {
while (true) { // 1.circulation
int currentValue = max.get();
if (value > currentValue) {
if (max.compareAndSet(currentValue, value)) // 2.CAS
break; // 3.exit
} else
break;
}
}

// type B
public void setB(int value) {
int currentValue;
do { // 1.circulation
currentValue = max.get();
if (value <= currentValue)
break; // 3.exit
} while (!max.compareAndSet(currentValue, value)); // 2.CAS
}
}
不过,对CAS的使用并不总是正确的,比如ABA问题。我用下面这样一个栈的例子来说明:
1.线程t1先查看了一下栈的情况,发现栈里面有A、B两个元素,栈顶是A,这是它所期望的,它现在很想用CAS的方法把A pop出去。
2.这时候线程t2来了,它pop出A、B,又push一个C进去,再把A push回去,这时候栈里面存放了A、C两个元素,栈顶还是A。
3.t1开始使用CAS:head.compareAndSet(A,B),把A pop出去了,栈里就剩下B了,可是这时候其实已经发生了错误,因为C丢失了。
为什么会发生这样的错误?因为对t1来说,它两次都查看到栈顶的A,以为期间没有发生变化,而实际上呢?实际上已经发生了变化,C进来、B出去了,但是t1它只看栈顶是A,它并不知道曾经发生了什么。
那么,有什么办法可以解决这个问题呢?
最常见的办法是使用一个计数器,对这个栈只要有任何的变化,就触发计数器+1,t1在要查看A的状态,不如看一下计数器的情况,如果计数器没有变化,说明期间没有别人动过这个栈。JDK 5.0里面提供的AtomicStampedReference就是起这个用的。
使用immutable对象的拷贝(比如CopyOnWrite)也可以实现无锁状态下的并发访问。举一个简单的例子,比如有这样一个链表,每一个节点包含两个值,现在我要把中间一个节点(2,3)替换成(4,5),不使用同步的话,我可以这样实现:

构建一个新的节点连到节点(4,6)上,再将原有(1,1)到(2,3)的指针指向替换成(1,1)到(4,5)的指向。
除了这两者,还有很多不用同步来实现原子操作的方法,比如我曾经介绍过的Peterson算法。
以下这个表格显示了JDK 5.0涉及到的常用容器:

其中:
● unsafe这一列的容器都是JDK之前版本有的,且非线程安全的;
● synchronized这一列的容器都是JDK之前版本有的,且通过synchronized的关键字同步方式来保证线程安全的;

● concurrent pkg一列的容器都是并发包新加入的容器,都是线程安全,但是都没有使用同步来实现线程安全。
再说一下对于线程池的支持。在说线程池之前,得明确一下Future的概念。Future也是JDK 5.0新增的类,是一个用来整合同步和异步的结果对象。一个异步任务的执行通过Future对象立即返回,如果你期望以同步方式获取结果,只需要调用它的get方法,直到结果取得才会返回给你,否则线程会一直hang在那里。Future可以看做是JDK为了它的线程模型做的一个部分修复,因为程序员以往在考虑多线程的时候,并不能够以面向对象的思路去完成它,而不得不考虑很多面向线程的行为,但是Future和后面要讲到的Barrier等类,可以让这些特定情况下,程序员可以从繁重的线程思维中解脱出来。把线程控制的部分和业务逻辑的部分解耦开。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class FutureUsage {

public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();

Callable task = new Callable() {
public Object call() throws Exception {

Thread.sleep(4000);

Object result = "finished";
return result;
}
};

Future future = executor.submit(task);
System.out.println("task submitted");

try {
System.out.println(future.get());
} catch (InterruptedException e) {
} catch (ExecutionException e) {
}

// Thread won't be destroyed.
}
}
上面的代码是一个最简单的线程池使用的例子,线程池接受提交上来的任务,分配给池中的线程去执行。对于任务压力的情况,JDK中一个功能完备的线程池具备这样的优先级处理策略:
1. 请求到来首先交给coreSize内的常驻线程执行
2.如果coreSize的线程全忙,任务被放到队列里面
3.如果队列放满了,会新增线程,直到达到maxSize
4.如果还是处理不过来,会把一个异常扔到RejectedExecutionHandler中去,用户可以自己设定这种情况下的最终处理策略
对于大于coreSize而小于maxSize的那些线程,空闲了keepAliveTime后,会被销毁。观察上面说的优先级顺序可以看到,假如说给Executo