3

《Java并发编程实战》水平很高,然而并不是本好书。组织混乱、长篇大论、难以消化,中文翻译也较死板。这里是一篇批评此书的帖子,很是贴切。俗话说:“看到有这么多人骂你,我就放心了”。

然而知识总是要学的。这里就总结一下书中及网络上的内容,作为Java并发编程之旅的结束,不再浪费时间了。

两个部分

这本书实际上可以分为两个部分。一是多线程的控制,二是并发同步的管理。把它们揉在一起,思路很难清晰。本文就先介绍第一部分,多线程的控制。

Thread和Runnable

在Java 5.0之前,多线程编程就是直接操作Thread。可以从Thread类派生一个类,或者实现Runnable接口的run()方法,然后调用Thread.start()启动线程。

线程的几种状态:

clipboard.png

Java 5.0增加了java.util.concurrent包,才有了线程池等强大的工具。

Java线程池

参见Java线程池系列文章。本文略做总结。

阻塞队列 BlockingQueue

阻塞队列,顾名思义,它在基本队列的基础上,还有阻塞的功能。即,如果队列已满,则入队操作阻塞等待,直到有空位;如果队列已空,则出队操作阻塞等待,直到队列有元素。相应的方法分别为put()take()

阻塞队列有几种实现:

  1. ArrayBlockingQueue:基于数组结构的有界阻塞队列,按 FIFO(先进先出)原则对元素进行排序。
  2. LinkedBlockingQueue:基于链表结构的阻塞队列,按FIFO排序元素,吞吐量通常要高于ArrayBlockingQueue。
  3. SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue。
  4. PriorityBlockingQueue:一个具有优先级的无限阻塞队列。

自己实现线程池

就是一个简单的生产者、消费者模型。线程池中的线程是消费者,循环地从阻塞队列中提取任务,执行任务。

Java线程池

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()强行关闭。

Executors类

上面的线程池使用起来还是太具体了,还需要自己创建线程池,还要自己传阻塞队列进去,不好用。于是Java提供了一个帮助类Executors,非常常用。

来看它的常用方法:

  • newFixedThreadPool(): 创建固定数量的线程池。
  • newCachedThreadPool(): 创建动态维护线程数的线程池。
  • newSingleThreadExecutor(): 创建单线程的线程池。

Callable接口和Future接口

Runnable接口的问题在于没有返回值,过于简单了。因此加入了Callable接口。相比于Runnable,一是有返回值,二是可以抛出异常。
Future就是异步编程中对一个还没有完成的任务的抽象,相当于C#中的Task。同样有cancel()isDone()等方法,调用get()则阻塞地获取结果。
FutureTaskFuture的一个具体实现类,并且不光实现了Future,还实现了Runnable接口,使其用旧方式也可调用。具体可见 Runnable、Callable、Future、FutureTask的区别

任务的取消

这是一个高级话题。Java建议不要用stop()粗暴地杀死线程,而是采用interrupt()这种温和的方式。当线程调用wait()sleep()等阻塞时,对这个线程调用interrupt()会使线程醒来,并受到InterruptedException,且线程的中断标记被设置。如何处理这种情况取决于线程自己。具体参见这篇文章


Toconscience
153 声望39 粉丝