java多线程编程
第5章
1 同步容器类:
将他们的状态封装,并对公共方法进行同步
1.1 synchronized 表示对对象上锁
1.2带客户端加锁的迭代
synchronized(vector){
... //整个方法期间,vector均会被独享
}
1.3 并发容器
·ConcurrentHashMap 线程安全的map
putIfAbsent 若空则添加
相当于
if(!map.contains(key))return null
else map.get(key)
·CopyOnWriteArrayList 写入时复制,在写入时创建副本,并在完成时替换
1.4 阻塞队列和生产者 消费者模式
·阻塞队列,如果队列已满,则put方法阻塞至有空间可用,如果为空,则take方法则阻塞至有元素可用
BlockingQueue
1.5恢复中断
当代码是Runnable的一部分时,不能够抛出InterruptedException,应当捕获中断,并在当前线程恢复中断状态,以传递至更高层的代码
public class TaskRunnable implements Runnbale{
BlockingQueue<Task> queue;
...
public void run(){
try{
processTask(queue.take());
}catch(InterruptedException e){
Thread.currentThread().interrupt();
}
}
}
1.6 同步工具类:
根据自身状态来协调线程的控制流
包括:
·阻塞队列
·信号量
·栅栏
·闭锁
1.7 闭锁:
闭锁的作用相当于一扇门,在闭锁到达结束状态之前,这扇门一直关闭;当到达结束状态时,允许所有线程通过。(结束状态后,闭锁不会改变状态)
1.8 闭锁的实现:
·CountDownLatch countDown方法递减计数器,表示事情发生
await方法等待计数器归0,或者线程终端,或等待超时
代码:
CountDownLatch startGate = new CountDownLatch(1);
CountDownLatch endGate = new CountDownLatch(num_thread);
for(int i=0;i<num_thread;i++){
Thread t = new Thread(){
try{
startGate.await();
try{
task.run();
}finally{
endGate.countDown(); //完成一个就减一个
}
}catch(InterruptedException ignored){}
};
}
startGate.countDown(); //所有线程启动后一起开始
endGate.await(); //等待所有线程结束
·FutureTask
Runnabl作为基础的任务表示形式,但不能返回值。
但对一些延时操作,如数据库查询或复杂的耗时计算,Callable是更好的抽象。他能返回一个值,或抛出一个异常。
Future表示一个任务的周期,并提供相应的方法判断是否已经完成或取消,以及获取任务的结果和取消任务
interface Callable<V> { V call() throws Exception; }
interface Future<V> {
boolean cancle() //取消
isCancelled()
isDone()
V get() //获取值
}
ExecutorService中的submit方法可以接受一个Runnable或Callable,然后返回一个Future来获取任务的执行结果或取消任务
interface ExecutorService{
Future<T> submit(Callable<T> task);
Future<?> submit(Runnable task);
Future<T> sunmit(Runnable task,T result);
}
FutureTask提供了一个可取消的异步计算,提供了start,cancel操作,可查询计算是否完成:get()方法可以获取计算结果,在获得前阻塞线程
public class FutureTask(V) implements RunnableFuture<V>
interface RunnableFuture<V> extends Runnable,Future<V>{ void run();}
可以看出RunnableFuture继承了Runnable接口和Future接口,而FutureTask实现了RunnableFuture接口。所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
代码:
Future<List> future = getDataFromRemoteByFuture();
//do something
List data = future.get(); //获取耗时计算的返回值
private Future<List> getDataFromRemoteByFuture(){
return threadPool.submit(new Callable<List>(){
public List call() throws Exception(){return list}; //耗时计算
}
);
}
1.9 信号量
计数信号量可以控制同时访问特定资源的操作数量。
Semaphore管理一组虚拟许可,在执行操作室可以获取许可,并在使用后释放许可。如果没有许可,acquire将阻塞至有许可。
代码:
public class BoundedHashSet<T> { //实现一个有界的阻塞容器
private final Set<T> set;
private final Semaphore sem;
public BoundedHashSet(int bound){ //设置容器的边界
this.set = Collections.synchronizedSet(new HashSet<T>());
sem = new Semaphore(bound);
}
public boolean add(T o) throws InterruptedException {
sem.acquire(); //获取许可,许可数-1,保证set有界,为bound
boolean wasAdded = false;
try{
wasAdded = set.add(o); //容器中添加对象
return wasAdded();
}
finally{
if(!wasAdded) sem.release(); //如果没有添加元素,则立即释放许可
}
}
public boolean remove(Object o){
boolean waRemoved = set.remove(o);
if(wasRemoved)
sem.release(); //执行完成后,删除容器中的值,并释放许可
return wasRemoved;
}
}
1.10 栅栏
栅栏类似于闭锁
区别:
·栅栏用于等待其他线程到达栅栏位置,再一起执行
·闭锁用于等待事件完成。其实,但从这里看,还是可以相互替换的
·最大的区别,闭锁一旦打开,只能维持打开转改,是一次性的。而栅栏在下次仍可以使用
非常好的例子: http://blog.youkuaiyun.com/yujin753/article/details/46125283
栅栏部分代码:
private CyclicBarrier cyclicBarrier = new CyclicBarrier(num); //设置等待的数量
public void run(){
try{
cyclicBarrier.await(); //等待所有线程运行到该位置,再一起执行
}catch(Exception e){
}
}
1.11 构建高效的结果缓存 书中的例子
需求:构建一个缓存,将之前的计算结果存在里面。要求满足多线程,同时避免重复运算
public interface Computable<A,V>(){ //A为参数,V为返回结果
V compute(A arg) throws InterruptedException;
}
public class Memoizer<A,V> implements Computable<A,V>{
//ConcurrentMap 是线程安全的map,存放<参数,future>
//实现思路:如果该参数的计算不在map中,启动异步任务,将futureTask先存入map
//等计算好了,可以通过get()方法获取
//同时使用cache.putIfAbsent方法,即不在则插入。避免产生相同的异步任务
private final ConcurrentMap<A,Future<V>> cache = new ConcurrentHashMap<A,Future<V>>();
public V compute(final A arg) throws InterruptedException {
while(True){
Future<V> f = cache.get(arg);
if(f == null){
Callable<V> eval = new Callable<V>() {
public V call() throws InterruptedException{
return c.compute(arg); //复杂耗时计算
}
};
FutureTask<V> ft = new FutureTask<V>(eval); //创建异步任务
f = cache.putIfAbsent(arg,ft); //存入缓存,线程安全
if(f == null) {
f=ft;ft.run() //putIfAbsent在插入成功时,会返回null,在插入的键在原表中已存在,返回键值
} //这样避免重复创建任务
}
//返回处理的结果
try{
return f.get();
}catch(Exception e){}
}
}
}