java 多线程编程 第五章

本文深入探讨Java多线程编程中的高级特性,包括同步容器类的使用、并发容器如ConcurrentHashMap的工作原理、阻塞队列及生产者消费者模式的应用、中断处理策略、同步工具类的功能与使用方法,以及闭锁、信号量、栅栏等高级同步工具的具体实现。

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){}
  }
 } 

 }
 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值