结构化并发应用程序
1)任务执行:当围绕“任务执行”来设计应用程序结构时,第一步就是要找出清晰的任务边界。在理想情况下,各个任务之间是相互独立的:任务并不依赖于其他任务的状态、结果或边界效应。【如:大多数服务器应用程序:以独立的客户请求为边界。Web服务器、邮件服务器、文件服务器、EJB服务器以及数据库服务器等,均通过网络接受远程客户的连接请求】
2)任务取消原因:用户请求取消、有时间现在的操作、应用程序事件、错误、关闭。
3)取消方式——协作式机制:①设置某个“已请求取消”表中,任务定期查看该标志;②中断,每个线程都有一个boolean类型的中断状态:public void interrupt(){…};public Boolean isInterrupted(){…}; public static Boolean intertupted(){….};线程中断VS任务中断③Future.cancel()。中断只是个标记,线程不会主动去中断自己,需要自己处理中断。调用阻塞函数期间会抛出InterruptedException。处理InterruptedException:a)传递异常,使得你的方法也成为可中断的阻塞方法。b)恢复中断状态,从而是调用栈中的上层代码能够对其进行处理。
“一段来自http://book.51cto.com/art/200704/44732.htm”代码修改:
class MyThread extends Thread { public void run() { while(!isInterrupted()) // 无限循环,并使线程每隔1秒输出一次字符串 { System.out.println(Thread.currentThread().getName()); System.out.println(getName()+" is running"); try{ sleep(1000);} catch(InterruptedException e) { System.out.println(isInterrupted()); System.out.println(e.getMessage()); interrupt(); System.out.println(isInterrupted()); //break; } } } } public class Cs { public static void main(String[] args) throws InterruptedException { MyThread m=new MyThread(); // 创建线程对象m System.out.println(Thread.currentThread().getName()); System.out.println("Starting thread..."); m.start(); // 启动线程m Thread.sleep(2000); //主线程休眠2秒,使线程m一直得到执行 System.out.println("Interrupt thread..."); System.out.println(m.isInterrupted()); m.interrupt(); System.out.println(m.isInterrupted()); // 调用interrupt()方法中断线程m Thread.sleep(2000); // 主线程休眠2秒,观察中断后的结果 System.out.println("Stopping application..."); // 主线程结束 } }
输出:
main Starting thread... Thread-0 Thread-0 is running Thread-0 Thread-0 is running Interrupt thread... false true false sleep interrupted true Stopping application...
④处理不可中断的阻塞:不是所有课阻塞方法或阻塞机制都能响应中断。对于不可中断操作而被阻塞的线程,可以使用类似于中断的手段来停止这些线程,但要求知道线程阻塞的原因,知道抛出那些异常,处理异常处理中断。【如:socket I/O,同步I/O,Selector异步I/O,获取某个锁】⑤使用newTaskFor封装非标准的取消:newTaskFor是ThreadPoolExecutor的一个用来生成RunnableFuture的工厂方法,RunnableFuture扩展Future和Runnable接口。可以由它改变Future.cancel方法行为。
4)停止基于线程的服务:对于持有线程的服务,只要服务的存在时间大于创建线程的方法的存在时间,那就应该提供生命周期方法关闭自己。
5)处理非正常的线程终止:导致线程提前死亡的最主要原因:RuntimeException。未捕获异常的处理:当一个线程由于未捕获异常而退出是,JVM会把这个事件报告给引用程序提供的UncaughtExceptionHandler异常处理器。
6)递归算法的并行化
7)GUI是单线程的:通过封闭机制来实现线程安全性。所有GUI对象,包括可视化组件和数据模型等,都只能在事件线程中访问。【单线程的GUI框架并不仅限于Java中,在Qt, NexiStep, MaxOS Cocoa, X Windows以及其他环境中的GUI框架都是单线程的。都是因为会发生竞态条件和死锁:表现在事件处理和MVC】
8)串行事件处理:事件是另一种类型的任务。AWT和Swing提供的事件处理机制在结构上也类似于Executor。SwingUtilities.invokeLater和SwingUtilities.invokeAndWait这两个方法的作用酷似Executor。可以将Swing的事件线程视为一个单线程的Executor
9)短时间GUI任务和长时间GUI任务【abstract class SwingWorker<T,V>:execute(), doInBackground, process(), done()】
======================================================================
活跃性、性能与测试:
1)死锁:锁顺序死锁、动态锁顺序死锁、协作对象之间发生的死锁、资源死锁
2)死锁的避免与诊断:①尽量减少潜在的加锁交互数量,将获取锁时需要遵循的协议写入正式文档并始终遵循这些协议;②通过两阶段策略:先找出在什么地方将获取多个锁(使这个集合尽量小),然后对所有这些实例进行全局分析,从而确保它们在整个程序中获取顺序都保持一致(数据库);③支持定时的锁;④通过线程转储信息来分析死锁;JVM通过线程转储的信息在锁的等待关系图中通过搜索循环来找出死锁。
3)其他活跃性危险:①饥饿:线程无法访问它所需要的资源而不能继续执行,【避免使用线程优先级,因为这会增加平台依赖性,并导致活跃性问题,在大多数并发应用程序中。都可以使用默认的线程优先级。JVM需要把Thread API中定义的优先级映射到操作系统的调度优先级,很有可能是不同Java优先级被映射到同一个优先级中】;②糟糕的响应性:长任务和短任务;不良的锁管理,长时间地占用锁;③活锁:该问题尽管不会阻塞线程,但也不能继续执行,因为线程将不断重复执行相同的操作,而且总会失败。【表现】:错误地将不可修复的错误作为可修复的错误;多个相互协作的线程都对彼此进行响应从而修改各种的状态,并使得任何一个线程都无法继续执行,类似让路问题。【解决办法】:在重试机制中引入随机性:等待随机长度的时间和回退(网络协议)。
4)Amdahl定律:在增加计算资源的情况下,程序在理论上能够实现最高加速比,这个值取决于程序中可并行组件与串行组件所占的比重。在所有并发称心中都包含了一些串行部分。
5)线程引入的开销:①上下文切换;②内存同步(可见性);③阻塞:竞争的同步,JVM在实现阻塞行为时,可以采用自旋等待(通过循环不断地尝试获取锁,直到成功)或者通过操作系统挂起被阻塞的线程。
6)减少锁的竞争:①减少锁的持有时间(缩小锁的范围:快进快出);②降低锁的请求频率(减小锁的粒度:锁分解和锁分段,避免热点域);③使用带有协调机制的独占锁(如:并发容器、读-写锁、不可变对象、原子变量),这些机制允许更高的并发性。
7)并发测试:①安全性测试:采用测试不变条件的形式,即判断某个类的行为是否与其他规范保持一致。②活跃性测试;③性能测试:吞吐量(一组并发任务中已完成任务所占的比例)、响应性(请求从发出到完成之间的时间,延迟)、可伸缩性(增加更多资源的情况下,吞吐量的提示情况)。