Callable与Future
Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其它线程执行的任务。不过Callable和Runnable有几点不同:
1、Callable规定的方法是call(),而Runnable规定的方法是run();
2、Callable的任务执行后可返回值,而Runnable的任务是不能返回值的;
3、call()方法可抛出异常,而run()方法是不能抛出异常的。
Callable要采用ExecutorSevice的submit方法提交,而运行Callable任务可拿到一个Future对象,通过Future对象可了解任务执行情况,可取消任务的执行,还可获取任务执行的结果。需要注意的是,Future取得的结果类型和Callable返回的结果类型必须一致,这是通过泛型来实现的;而且在Callable返回结果之前,Future会一直阻塞,直到Callable返回。
CompletionService用于提交一组Callable任务,其take方法则返回已完成的一个Callable任务对应的Future对象。
Lock与Condition
在多线程编程里面一个重要的概念是锁定,如果一个资源是多个线程共享的,为了保证数据的完整性,在进行事务性操作时需要将共享资源锁定,这样可以保证在做事务性操作时只有一个线程能对资源进行操作,从而保证数据的完整性。在jdk5以前,锁定的功能是由synchronized关键字来实现的,但是这样做存在几个问题:
1、每次只能对一个对象进行锁定。若需要锁定多个对象,编程就比较麻烦,一不小心就会出现死锁现象;
2、如果线程因拿不到锁定而进入等待状况,是没有办法将其打断的。
可以这么理解,使用synchronized关键字实现共享资源锁定的功能是以面向过程的方式实现的,无论是synchronized代码块还是synchronized关键字方法,所以导致了上述问题的存在,而jdk5提供了Lock与Condition尝试解决上述问题。而Lock比synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象。两个线程执行的代码片段要实现多个共享资源或资源片段同步互斥的效果,它们必须用同一个Lock对象。
Lock提供了以下一些方法:
1、lock(): 请求锁定,如果锁已被别的线程锁定,调用此方法的线程被阻断进入等待状态;
2、tryLock():如果锁没被别的线程锁定,进入锁定状态,并返回true。若锁已被锁定,返回false,不进入等待状态;此方法还可带时间参数,如果锁在方法执行时已被锁定,线程将继续等待规定的时间,若还不行才返回false;
3、unlock():取消锁定,需要注意的是Lock不会自动取消,编程时必须手动解锁,这个至少在一定程度上解决了上述第二个问题。
Condition,即共享资源,相当于使用synchronized关键字实现并发控制里的监视器对象。一个锁内部可以有多个Condition,即有多路等待和通知,这就解决了上述第一个问题。可以参看jdk1.5提供的Lock与Condition实现的可阻塞队列的应用案例。
在等待 Condition 时,允许发生“虚假唤醒”,这通常作为对基础平台语义的让步。对于大多数应用程序,这带来的实际影响很小,因为 Condition 应该总是在一个循环中被等待,并测试正被等待的状态声明。某个实现可以随意移除可能的虚假唤醒,但建议应用程序程序员总是假定这些虚假唤醒可能发生,因此总是在一个循环中等待。