【Java并发】JAVA并发编程实战-读书笔记11

除非你知道线程的中断策略,否则你不应该中断线程。

任务执行线程由标准的Executor实现创建,他实现了一个中断策略使得任务可以通过中断被取消,所以当他们在标准Executor中运行时通过他们的Future来取消任务,可以设置cancel的参数值为true。当尝试取消一个任务的时候,你不应该直接中断线程池,因为你不知道中断请求到达时什么任务正在运行——只能通过任务的Future来做这件事情。

public static void timedRun(Runnable r,long timeout,TimeUnit unit) throws InterruptedException{

  Future<?> task=taskExec.submit(r);

  try{

    task.get(timeout,unit);

  }catch(TimeoutException e){

  }catch(ExecutionException e){

    throw launderThrowable(e.getCause());

  }finally{

    task.cancel(true);

  }

}

上面的例子中为了简化代码,在 finally 中无条件的调用了 Future.cancel,这样做是基于下面的事实:取消一个已完成的任务不会有任何影响。

当Future.get抛出InterruptedExceptionTimeoutException时,如果你知道不在需要结果时,就可以调用Future.cancel来取消任务了。

并不是所有的阻塞方法或阻塞机制都相应中断,如果一个线程是由于进行同步Socket I/O或者等待获得内部锁而阻塞的,那么中断除了能够设置线程的中断状态以外,什么都改变不了。

java.io中的同步Socket I/O在服务器应用程序中,阻塞I/O最常见的形式是读取和写入Socket。但是InputStreamOutputStream中的readwrite方法都不响应中断,但是通过关闭底层的socket可以让readwrite所阻塞的线程抛出一个SocketException

java.nio中的同步I/O,中断一个等待InterruptibleChannel的线程,会导致抛出ClosedByInterruptException,并不关闭链路(也会导致其他线程在这条链路的阻塞)。关闭一个InterruptibleChannel导致多个阻塞在链路操作上的线程抛出AsynchronousCloseException。大多数标准Channels都实现InterruptibleChannel

Selector的异步I/O。如果一个线程阻塞于Selector.select方法,close方法会导致他通过ClosedSelectorException提前返回。

public class ReaderThread extends Thread{

  private final Socket socket;

  private final InputStream in;

  public ReaderThread(Socket socket)throws IOException{

    this.socket=socket;

    this.in=socket.getInputStream();

  }

  public void interrupt(){

    try{

      socket.close();

    }catch(IOException ignored){

    }finally{

      super.interrupt();

    }

  }

  public void run(){

    try{

      byte[] buf=new byte[BUFSZ];

      while(true){

      int count=in.read(buf);

      if(count<0){

        break;

      }else if(count>0){

        processBuffer(buf,count);

      }

    }

  }catch(IOException e){

  }

}

}

上面的例子中,通过重写interrupt来封装非标准取消。我们也可以使用newTaskFor钩子函数来改进用来封装非标准取消的方法,这是Java6中添加到THreadPoolExecutor的新特性。当提交一个CallableExecutorService时,返回一个Future,可以用Future来取消任务。newTaskFor钩子是一个工厂方法,创建Future来代表任务,他返回一个RunnableFuture,这是一个接口,扩展了FutureRunnable

自定义的任务Future允许你重写Future.cancel方法。自定义的取消代码可以实现日志或者收集取消的统计信息,并可以用来取消那些不响应中断的活动。

通过重写interrupt,上面的例子封装了使用Socket的线程的取消行为,同样的事情也可以通过重写任务的Future.cancel方法来实现。

public interface CancellableTask<T> extends Callable<T>{

  void cancel();

  RunnableFuture<T> newTask();

}

public class CancellingExecutor extends ThreadPoolExecutor{

  protected<T> RunnableFuture<T> newTaskFor(Callable<T> callable){

    if(callable instanceof CancellableTask){

      return ((CancellableTask<T>)callable).newTask();

    }else{

      return super.newTaskFor(callable);

    }

  }

}

public abstract class SocketUsingTask<T> implements CancellableTask<T>{

  private Socket socket;

  protected synchronized void setSocket(Socket s){

    socket=s;

  }

  public synchronized void cancel(){

    try{

      if(socket!=null){

        socket.close();

      }

    }catch(IOException ignored){

    }

  }

  public RunnableFuture<T> newTask(){

    return new FutureTask<T>(this){

      public boolean cancel(boolean mayInterruptIfRunning){

        try{

          SocketUsingTask.this.cancel();

        }finally{

          return super.cancel(mayInterruptIfRunning);

        }

      }

    }

  }

}

对于线程持有的服务,只要服务的存在时间大于创建线程的方法存在的时间,那么就应该提供生命周期方法。

PrintWriter这样的字符流类时线程安全的,但是如果在单一日志消息中写入多行则需要在客户端加锁,来避免多线程交替输出。

public class LogWriter{

  private final BlockingQueue<String> queue;

  private final LoggerThread logger;

  public LogWriter(Writer writer){

    this.queue=new LinkedBlockingQueue<String>(CAPACITY);

    this.logger=new LoggerThread(writer);

  }

  public void start(){

    logger.start();

  }

  public void log(String msg)throws InterruptedException{

    queue.put(msg);

  }

  private class LoggerThread extends Thread{

    private final PrintWriter writer;

    public void run(){

      try{

        while(true){

          writer.println(quque.take());

        }

      }catch(InterruptedException ignored){

      }finally{

        writer.close();

      }

    }

  }

}

上面是一个不支持关闭的生产者 - 消费者日志服务
public void log(String msg)throws InterruptException{

  if(!shtudownRequested){

    queue.put(msg);

  }else{

    throw new IllegalStateException(“logger is shut down”);

  }

}

上面是一个检查再运行的例子,并不安全。

因为put是阻塞的,所以我们不可以直接以加锁的方式解决问题。

public class LogService{

  private final BlockingQueue<String> queue;

  private final LoggerThread loggerThread;

  private final PrintWriter writer;

  private boolean isShutdown;

  private int reservations;

  public void start(){

    loggerThread.start();

  }

  public void stop(){

    synchronized(this){

      isShutdown=true;

    }

    loggerThread.interrupt();

  }

  public void log(String msg)throws InterruptedException{

    synchronized(this){

      if(isShutdown){

        throw new IllegalStateException(...);

      }

      ++reservations;

    }

    queue.put(msg);

  }

  private class LoggerThread extends Thread{

    public void run(){

      try{

        while(true){

          try{

            synchronized(LogService.this){

              if(isShutdown&&reservations==0){

                break;

              }

            }

            String msg=queue.take();

            synchronized(LogService.this){

              --reservations;

            }

            writer.println(msg);

          }catch(InterruptedException e){


          }finally{

            writer.close();

          }

        }

      }

    }

  }

}

上面的例子中添加了可靠的取消。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值