《Java并发编程实战》水平很高,然而并非本好书。组织混乱、长篇大论、难以消化,中文翻译也较死板。这里是一篇批评此书的帖子,非常贴切。俗话说:“看到有这么多人骂你,我就放心了”。html
然而知识老是要学的。这里就总结一下书中及网络上的内容,做为Java并发编程之旅的结束,再也不浪费时间了。java
这本书实际上能够分为两个部分。一是多线程的控制,二是并发同步的管理。把它们揉在一块儿,思路很难清晰。本文就先介绍第一部分,多线程的控制。编程
在Java 5.0以前,多线程编程就是直接操做Thread
。能够从Thread
类派生一个类,或者实现Runnable
接口的run()
方法,而后调用Thread.start()
启动线程。数组
线程的几种状态:网络
Java 5.0增长了java.util.concurrent
包,才有了线程池等强大的工具。多线程
参见Java线程池系列文章。本文略作总结。并发
阻塞队列,顾名思义,它在基本队列的基础上,还有阻塞的功能。即,若是队列已满,则入队操做阻塞等待,直到有空位;若是队列已空,则出队操做阻塞等待,直到队列有元素。相应的方法分别为put()
和take()
。异步
阻塞队列有几种实现:异步编程
就是一个简单的生产者、消费者模型。线程池中的线程是消费者,循环地从阻塞队列中提取任务,执行任务。工具
Java线程池的接口是ExecutorService
,它有几个实现。以ThreadPoolExecutor
为例,它的使用方式是:
BlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(5); ExecutorService threadPoolExecutor = new ThreadPoolExecutor( corePoolSize, //初始线程数 maxPoolSize, //最大线程数 keepAliveTime, //空闲线程最大存活时间 TimeUnit.MILLISECONDS, //时间单位 queue // 任务的阻塞队列 );
要使用这个线程池,可使用它提供的以下方法:
execute(Runnable)
没有返回值 。submit(Runnable)
返回Future
对象,表明未完成的结果(因为Runnable
没有返回值因此内容为空)。submit(Callable)
返回Future
对象,表明未完成的结果。invokeAny(Collection)
执行全部任务,返回第一个完成的结果。invokeAll(Collection)
执行全部任务,返回Future
对象列表。最后,使用shutdown()
和shutdownNow()
来关闭线程池,中止其中的线程。前者采用后文讲到的interrupt方式温和关闭,后者则调用Thread.stop()
强行关闭。
上面的线程池使用起来仍是太具体了,还须要本身建立线程池,还要本身传阻塞队列进去,很差用。因而Java提供了一个帮助类Executors
,很是经常使用。
来看它的经常使用方法:
newFixedThreadPool()
: 建立固定数量的线程池。newCachedThreadPool()
: 建立动态维护线程数的线程池。newSingleThreadExecutor()
: 建立单线程的线程池。Runnable
接口的问题在于没有返回值,过于简单了。所以加入了Callable
接口。相比于Runnable
,一是有返回值,二是能够抛出异常。Future
就是异步编程中对一个尚未完成的任务的抽象,至关于C#中的Task
。一样有cancel()
、isDone()
等方法,调用get()
则阻塞地获取结果。FutureTask
是Future
的一个具体实现类,而且不光实现了Future
,还实现了Runnable
接口,使其用旧方式也可调用。具体可见 Runnable、Callable、Future、FutureTask的区别。
这是一个高级话题。Java建议不要用stop()
粗暴地杀死线程,而是采用interrupt()
这种温和的方式。当线程调用wait()
或sleep()
等阻塞时,对这个线程调用interrupt()
会使线程醒来,并受到InterruptedException
,且线程的中断标记被设置。如何处理这种状况取决于线程本身。具体参见这篇文章。