《java并发编程实战》读书笔记——并发应用

1.使用线程池

当应用需要处理多个任务时,例如一个Web服务器处理它接收到的请求,可以使用线程池。
通过重用现有的线程而不是创建新的线程,可以在处理多个请求时分摊在线程创建和销毁过程中产生的巨大开销。
另一个好处是,当一个请求到达时,工作线程通常已经存在,因此不会由于等待创建线程而延时任务的执行,从而提高响应性。
通过适当调整线程池的大小,可以创建足够多的线程以便使处理器保存忙碌状态,同时还可以防止过多的线程相互竞争资源而使应用程序耗尽内存或失败。
更多线程池内容参考《java线程池》

2.任务的取消

Java目前没有提供任何的机制来安全的终止线程(虽然Thread.stop和suspend等方法提供了这样的机制,但由于存在一些严重的缺陷,因此应该避免使用)
但它提供了中断(Interruption),这是一种协作机制,能够使一个线程终止另一个线程的当前工作。
有种方法能做到任务的取消机制:
设置一个volatile对象作为一个任务是否取消的标记。每次任务执行前都先检查一下任务是否取消。
public class Test {
	private volatile boolean iscancle = false;
	private BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<String>(3);
	public void put(){
		while(!iscancle){
			blockingQueue.add("result");
		}
	}
	public void cancle(){
		iscancle = true;
	}
}
但是这样的设计可能会出现严重的问题,在上面的例子中BlockingQueue的深度是3.
如果队列里面已经存放了3个对象。我现在要往里面放第4个对象,线程就会阻塞在add()步奏,等待队列有空位时执行添加操作。如果在这个时候执行cancle,把iscancle
设置为true。但是如果队列一直处于满的状态的话,那么线程将不会再去检查if(!iscancle),那么任务将一直阻塞下去不会退出。

那么正确的做法是使用java线程为我们提供的线程中断机制。
每个线程都有一个boolean类型的中断状态。当线程中断时,这个线程的中断状态将被设置为true。在Thread中提供了中断线程以及查询线程中断状态的方法
interrupt()方法——中断线程
isInterrupted()方法——判断线程是否中断

那么我们平时使用的一些阻塞方法,比如Thread.sleep Object.wait。我们上面例子使用的BlockingQueue也为我们提供了阻塞方法put()
这些阻塞方法都会检查线程何时中断,并且在发现中断时提前返回。
那么这里需要了解的关键就是:
它不会真正立即中断一个正在运行的线程,而只是发出中断请求,把中断状态设置为ture,由运行中的线程在一个合适的时候自己中断自己(在一个合适的时候检查中断状态)

public class Test extends Thread {
	
	private BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<String>(3);
	public void run(){
		while(true){
			try {
				blockingQueue.put("result");
			} catch (InterruptedException e) {
				System.out.println("cancle!");
			}
		}
	}
	public void cancle(){
		interrupt();
	}
	
}

3.处理不可中断的阻塞操作

Java.io包中的同步Socket I/O。在服务器应用程序中,最常见的阻塞IO形式就是对套接字进行读取和写入。虽然InputStream和Outputstream中的read和write等方法都不会响应中断,但通过关闭底层的套接字,可以使得由于执行read和write等方法而被阻塞的线程抛出一个SocketException。
Java.io包中的同步I/O。当中断一个正在InterruptibleChannel上等待的线程时,将抛出ClosedByInterruptException并关闭链路。大多数标准的Channel都实现了InterruptibleChannel。
Selector的异步IO。如果一个线程在调用Selector.select方法时被阻塞了,那么调用close或wakeup方法会使线程抛出ClosedSelectorException并提前返回。
获得某个锁。如果一个线程由于等待某个内置锁而阻塞,那么将无法响应中断,因为线程认为它肯定会获得锁,所以将不会理会中断。但是,Lock类中提供了lockInterruptibly方法,该方法允许在等待一个锁的同时仍然能响应中断。


4.停止基于线程的服务

一种关闭生产者消费者服务的方式就是使用“毒丸”对象。“毒丸”对象指的是在一个队列上放入一个对象,当消费者取得到这个对象的时候,就立即停止。在“先进先出”队列中,“毒丸”对象将确保消费者在关闭之前首先完成队列中的所有工作。
只有在生产者和消费者的数量都已知的情况下可用。
1.需要停止多个生产者的情况:每个生产者都向队列中放入一个“毒丸”,当且仅当消费者在接收到N(生产者数量)个“毒丸”对象以后才停止。
2.需要停止多个消费者的情况:生产者往队列中放入N(消费者数量)个“毒丸”对象。
只有在生产者消费者数量都不是很大的时候, 并且要在无界队列中才能使用(如果有界的话,放入毒丸的操作阻塞,那么服务将一直不会退出)

5.停止服务后保存未执行完的任务。

当通过shutdownNow来强行关闭ExecutorService时,它会尝试取消正在执行的任务,并且返回所有未已经提交但尚未开始的任务。
我们需要做的是:
1.shutdownNow返回的尚未执行的任务保存。
2.保存正在执行但是被取消的任务:
有一个方法是自己定义一个ExecutorService的包装类 A。
里面再包含一个ExecutorService对象 exec
将A的方法 比如executor()都委托给 exec来执行。具体操作如下:
public class TrackingExecutor extends AbstractExecutorService {
	//将TrackingExecutor的方法都委托给exec来执行
	private final ExecutorService exec = Executors.newCachedThreadPool();
	private final Set<Runnable> tasksCancelledAtShutdown = (Set<Runnable>) Collections.synchronizedCollection(new HashSet<Runnable>());
	@Override
	public void execute(final Runnable command) {
		exec.execute(new Runnable(){
			public void run(){
				//关键步奏,若任务被中途中断可以截获
				try{
					command.run();
				} finally{
					if(isShutdown()&&Thread.currentThread().isInterrupted())
						tasksCancelledAtShutdown.add(command);
				}
			}
		});
		
	}
	....
	//将TrackingExecutor的其他方法都委托给exec来执行
	....
}

小结:
在任务、线程、服务以及应用程序等模块中的生命周期结束问题,可能会增加它们在设计和实现时的复杂性。java并没有提供某种抢断式的机制来取消操作或者终止线程。
相反,它提供了一种协作式的中断机制来实现取消操作,但这要依赖于如何构建取消操作协议,以及能否遵循这些协议。通过使用FutureTask和Executor框架,可以帮助我们构建可取消的任务。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值