在java1.5之前,java的并发API都是依靠Thread, Runnable, ThreadLocal, ThreadGroup以及Object特有的现成先关方法所构成。此外,还有synchronized, volatile两个关键字对同步和内存一致性的定义。从1.5开始,java的API中多了一个包, java.util.concurrent, 更丰富了并发的API。
本文不是普及知识,所以不对每个API都做详尽的解释,但还是有必要列一张简单的表格。
API | Java版本 | 描述 | 示例 | |
synchronized(obj) { } | 1.4 | 多线程在此代码块必须同步执行。 线程的interrupt()方法对同步锁上的阻塞是无效的,当线程获取到锁而进入同步块后, 可以调用Thread.interrupted()方法来检验本线程是否被interrupted了。 |
synchronized(obj) { if (Thread.interrupted()) { // This thread is interrupted. } } |
|
Lock | since 1.5 | 替换synchronized。 |
Lock l = new ReentrantLock(); l.lock(); try { // access the resource protected by this lock } finally { l.unlock(); } |
|
obj.wait() |
1.4 | 线程阻塞。只能在obj同步块中使用。跳出阻塞的三种方法: 1,其它线程调用obj.notify() 2,其它线程调用obj.notifyAll() 3,其它线程对此线程对象调用了thread.interrupt() 第3中方法会使wait()跑出InterruptedException。 线程执行wait的时候,会释放掉obj上的锁,这使得其它线程可以进入obj的同步块。 |
synchronized(obj) { try { while (!conditionReady) obj.wait(); } catch (InterruptedException e) { // interrupted } } |
|
condition.await() |
since 1.5 | 替换obj.wait()。只能在lock与unLock之间使用。 | Lock l = new ReentrantLock(); Condition c = l.newCondition(); l.lock(); try { while (!conditionReady) c.await(); } catch (InterruptedException e) { // interrupted } finally { l.unlock(); } |
|
obj.notify() |
1.4 | 唤醒阻塞在obj上的一个线程。只能在obj同步块中使用。 | synchronized(obj) { conditionReady = true; obj.notify(); } |
|
condition.signal() |
since 1.5 | 替换obj.nofify()。只能在lock与unLock之间使用。 | Lock l = new ReentrantLock(); Condition c = l.newCondition(); l.lock(); try { c.singal(); } finally { l.unlock(); } |
|
obj.notifyAll() |
1.4 | 唤醒阻塞在obj上的所有线程,所有线程将竞争obj的锁,以进入同步块。只能在obj同步块中使用。 | synchronized(obj) { conditionReady = true; obj.notifyAll(); } |
|
condition.signalAll() |
since 1.5 | 替换obj.nofifyAll()。只能在lock与unLock之间使用。 | Lock l = new ReentrantLock(); Condition c = l.newCondition(); l.lock(); try { c.singalAll(); } finally { l.unlock(); } |
|
Thread.sleep(long millis) |
1.4 | 使当前线程睡眠一段时间。结束睡眠有两种方式: 1,等待睡眠时间结束。 2,在睡眠线程对象上调用interrupt()方法。 非常注意的一件事情,sleep方法并不会释放同步锁。在实例中的第二个sleep方法上, 线程不会释放obj的锁,其它线程无法进入这个同步块。 |
try { Thread.sleep(1000); } catch (InterruptedException e) { // interrupted } synchronized(obj) { try { Thread.sleep(1000); } catch (InterruptedException e) { // interrupted } } |
|
Thread.yield() |
1.4 | 释放cpu,是的cpu重现分配时间。 |
Thread.yield(); |
|
thr.join() |
1.4 | 等待thr线程执行完毕。跳出join的方法有两个: 1,等待thr执行完毕。 2,在等待线程上调用interrupt()方法。 |
try { thr.join(); } catch (InterruptedException e) { // this thread interrupted. } |
|
thr.interrupt() |
1.4 | 1,设置thr的interrupted状态。 2,如果thr线程在sleep, wait, join等所有可以抛出InterruptedException方法上阻塞,则中断此线程。 无法中断在同步锁上阻塞的线程。 |
thr.interrupt(); |
从java1.5开始,java对并发的支持都体现在java.util.concurrent包中。其中的并发集合,大大方便了我们的开发。最常见的则是BlockingQueue. BlockingDeque.
缺点 - 并发集合在单独使用时,是一个非常棒的工具。但当与synchronized和Lock结合使用时,则存在着死锁的可能。为什么呢?请看下面的代码示例:
// Thread 1 synchronized(obj) { blockingQuque.put(e); } // Thread 2 synchronized(obj) { e = blockingQuque.take(); }
上面的代码存在死锁。 同步块的锁与blockingQueue的锁并不是同一个锁,当blockingQueue队列满的时候,put方法会阻塞,阻塞会释放blockingQueue的锁,但并没有释放obj的锁,thread 2无法进入同步块,无法取出blockingQueue中的元素。
Java关键字volatile解决了内存一致问题。voliatile使得寄存器和内存的变量值保持一致。但这并不能保证 i++原子操作。java.util.concurrent.atomic提供了很多原子操作的基本类型。
线程池是java1.5以后带来的新特性。线程的创建非常消耗资源,影响系统的性能。使用线程池,重用线程,可以优化此方面性能。想tomcat的connector, servlet容器,无一不用到线程池。
线程池的实现方式就像是生产者消费者方式。将要执行的Runnable对象放入BlockingQueue中,然后由线程池中的实际线程不停地从queue中取出Runnable的task,并直接执行task的run方法。
线程池两个最常用的interface为:
Executor
ExecutorService extends Executor
ScheduledExecutorService extends ExecutorService
第二个比第一个多出的是schedule功能。常用的方法:
功能 | Name | From | 描述 |
执行线程任务 | void execute(Runnable command) | Executor | 向线程管理者提交一个任务。任务必须是一个继承了Runnable的实例。 |
执行线程任务 |
<T> Future<T> submit(Callable<T> task) <T> Future<T> submit(Runnable task,T result) <T> List<Future<T>>invokeAll(Collection<? extends Callable<T>> tasks) <T> T invokeAny(Collection<? extends Callable<T>> tasks) ... ... |
ExecutorService | 向线程管理者提交有返回值的任务。 |
管理线程 |
void shutdown() |
ExecutorService | Service不再接受submit或execute提交的任务,但之前已经提交到队列成功的任务会执行完毕。 此方法存在缺陷,很容易造成永久等待。因为此方法只会对闲置的线程进行interrupt, 如果线程 正处于执行的wait状态,executor并不认为此线程是闲置的。所以不会对其执行interrupt, 这就 使此线程永远的wait。尝试将后面的例子改成shutdown,你会发现程序永远不会退出。 |
管理线程 |
void shutdownNow() |
ExecutorService | 立刻关闭线程,所有任务都被中断。 |
管理线程 |
boolean awaitTermination(long timeout,TimeUnit unit) |
ExecutorService | 等待线程是否完全停止。 |
执行线程任务 |
ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) |
ScheduledExecutorService | 在未来某时间端,或定期的执行任务。 |
了解上面的接口以及方法非常重要。java.util.concurrent包中的类实现了上面的接口,我们可以用new的方式初始化这些接口的实现类。但,最方便的做法是使用静态工厂模式,创建我们所需要的线程管理器。
java.util.concurrent.Executors中,提供了几个静态方法,可以构造ExecutorService与ScheduledExecutorService的实例。
public static ExecutorService newFixedThreadPool(int nThreads) | 一个固定size的线程池 |
public static ExecutorService newCachedThreadPool() | 一个自适应size的线程池 |
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) | 一个执行schedule的线程池 |
在代码案例中,我会使用线程池,并发集合,原子类型和锁机制。案例中,包括以下几个元素:
生产者线程池。有3个线程。
生产者。生产者将不停的向线程池提交生产任务。
消费者线程池。有2个线程。
消费者。消费者将不停的向线程池提交消费任务。
管理者定时单线程执行者。定时的执行管理者任务。
管理者任务。在到时间后,关闭生产者与消费者线程池。
public class ProducerConsumer { // 初始化生产者线程池 ExecutorService producerPool = Executors.newFixedThreadPool(3); // 初始化消费者线程池 ExecutorService consumerPool = Executors.newFixedThreadPool(2); // 初始化管理者。 ScheduledExecutorService supervisor = Executors.newSingleThreadScheduledExecutor(); // 产品计件器 AtomicInteger counter = new AtomicInteger(0); // 产品存放仓库。容量为5个。 BlockingQueue<Integer> products = new LinkedBlockingQueue<Integer>(5); // 生产出的产品内容。从0递增。 int product = 0; // 对生产过程上锁。 Lock lock = new ReentrantLock(); public void start() { // 提交管理任务,30秒以后执行。 supervisor.schedule(new CloseTask(), 30, TimeUnit.SECONDS); // 开始生产 new Producer().start(); // 开始消费 new Consumer().start(); } // 生产者 private class Producer extends Thread { // 初始化可重复的生产任务。 Runnable task = new Runnable() { public void run() { try { Thread.sleep(1000); int i; try { lock.lock(); // 对生产过程上锁。 i = ++product; // 生产内容 } finally { lock.unlock(); } System.out.println("Producing "+i); products.put(i); // 放入仓库。 counter.getAndIncrement(); // 计件 } catch (InterruptedException e) { e.printStackTrace(); } } }; public void run() { // 每隔300毫秒,向线程池提交一个生产任务。间隔如果太短,会导致队列过大outofmemory. while(!producerPool.isShutdown()) { try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } producerPool.execute(task); } } } // 消费者 private class Consumer extends Thread { // 初始化可重复的消费任务。 Runnable task = new Runnable() { public void run() { try { Thread.sleep(800); int i = products.take(); System.out.println("Consuming "+i); } catch (InterruptedException e) { e.printStackTrace(); } } }; public void run() { // 每隔400毫秒,向线程池提交一个消费任务。间隔如果太短,会导致队列过大outofmemory. while(!consumerPool.isShutdown()) { try { Thread.sleep(400); } catch (InterruptedException e) { e.printStackTrace(); } consumerPool.execute(task); } } } // 管理者任务 private class CloseTask implements Runnable { @Override public void run() { System.out.println("Try to close all tasks."); // 立刻关闭生产线程池。 producerPool.shutdownNow(); try { while (!producerPool.awaitTermination(1000, TimeUnit.MILLISECONDS)); // 循环等待线程池是否关闭 System.out.println("Producer thread pool stops."); } catch (InterruptedException e) { e.printStackTrace(); } // 等待生产仓库被清空。 while (products.size()>0) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } // 立刻关闭生产线程池 consumerPool.shutdownNow(); try { while (!consumerPool.awaitTermination(1000, TimeUnit.MILLISECONDS)); // 循环等待是否关闭完成。 System.out.println("Consumer thread pool stops."); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(counter.get() + " products."); // 关闭管理者 supervisor.shutdownNow(); } } public static void main(String[] args) { new ProducerConsumer().start(); } }
使用jvisualvm查看线程状态,如下图。pool-1对应ProducerPool, pool-2对应ConsumerPool, pool-3对应supervisor.