并发编程常见方法(二)

很多时候,基础并不代表好用。Java的多线程编程也是一样。灵活使用基本的Thread确实可以解决大部分问题,但同时会牺牲一些东西,包括性能和可维护性。

接下来是一些实际开发非常常见的功能。

ExecutorService

这是一个线程池,可以管理很多线程。它有一些不同的声明方式,用途各不相同。

创建一个线程池,固定5个线程:

ExecutorService pool = Executors.newFixedThreadPool(5);
// newFixedThreadPool使用的阻塞队列没有上限,容易导致OOM

或者:

ExecutorService pool = new ThreadPoolExecutor(
	5, // 核心线程数,闲置会一直保持这个数量
	10, //最大线程数
	60, // 存活时间,适用于核心线程以外多出的线程
	TimeUnit.SECONDS, // 时间单位
	new LinkedBlockingQueue<>(100) // 这个阻塞队列会用来存放任务
);

只有等待队列已满时,才会尝试开启核心线程以外的线程。

核心方法

submit()接受一个Runnable或Callable对象,返回一个Future。Future类会立即返回,但任务此时并没有完成。Future和这个任务有对应关系。

execute()接受一个Runnable对象,它没有返回值。仅仅是执行了。

shutdown()用于关闭线程池。此后线程池将不会接受新的任务,但会等待已有任务执行完毕。

shutdownNow()更加暴力,会立即关闭线程池并强行中断所有任务。它可能不会成功。

awaitTermination()在线程池被关闭后才会返回,对于shutdown的情况,它会等待任务执行完毕。

创建方式

Executors工具类提供了一些预设的ExecutorService

newFixedThreadPool(5) — 固定大小的线程池

newSingleThreadExecutor() — 单一线程的Executor

newCachedThreadPool() — 缓存线程池,它的核心线程数为0

newScheduledThreadPool(2) — 定时线程池,有一些有用的定时特性

Future

Future和一个任务有对应关系。它会被ExecutorService立即返回,可以通过它获取任务的执行结果。不过,当然需要任务真正执行完毕才能看到。

Future是有泛型的,因为支持的返回类型可以有很多。

Future.get()返回任务的返回值。如果任务还没有完成,它会阻塞当前线程。

它还提供了isDone(), cancel()isCancelled()方法。

cancel(true)会中断已经在运行的任务;cancel(false)只会取消还没开始排队或在队列中等待的任务,如果已经在运行,它会继续运行。它返回一个布尔值,代表是否成功取消了任务。

Lock

这是一类用于管理资源的锁对象。ReentrantLock是最常见的Lock,之所以叫做“Reentrant”,是因为它具有可重入性(同一线程可以反复获得同一把锁)。

使用锁最核心的原则是,要保证锁在操作完成后一定被释放,无论发生了什么。这常常需要try搭配finally代码块实现。

lock()方法用于加锁,如果已经处于锁定状态,它会等待直至解锁,然后再锁定它并返回。

unlock()方法可以解锁。在线程完成自己的业务操作后,必须调用它。只有锁由当前线程持有时才可以调用。

tryLock(1, TimeUnit.SECONDS)用于尝试加锁,在指定时间内,如果没有成功获取锁,它会返回false。


评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注