API
在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了。 |
|
|
Lock | since 1.5 | 替换synchronized。 |
|
|
obj.wait() |
1.4 | 线程阻塞。只能在obj同步块中使用。跳出阻塞的三种方法: 1,其它线程调用obj.notify() 2,其它线程调用obj.notifyAll() 3,其它线程对此线程对象调用了thread.interrupt() 第3中方法会使wait()跑出InterruptedException。 线程执行wait的时候,会释放掉obj上的锁,这使得其它线程可以进入obj的同步块。 |
|
|
condition.await() |
since 1.5 | 替换obj.wait()。只能在lock与unLock之间使用。 |
|
|
obj.notify() |
1.4 | 唤醒阻塞在obj上的一个线程。只能在obj同步块中使用。 |
|
|
condition.signal() |
since 1.5 | 替换obj.nofify()。只能在lock与unLock之间使用。 |
|
|
obj.notifyAll() |
1.4 | 唤醒阻塞在obj上的所有线程,所有线程将竞争obj的锁,以进入同步块。只能在obj同步块中使用。 |
|
|
condition.signalAll() |
since 1.5 | 替换obj.nofifyAll()。只能在lock与unLock之间使用。 |
|
|
Thread.sleep(long millis) |
1.4 | 使当前线程睡眠一段时间。结束睡眠有两种方式: 1,等待睡眠时间结束。 2,在睡眠线程对象上调用interrupt()方法。 非常注意的一件事情,sleep方法并不会释放同步锁。在实例中的第二个sleep方法上, 线程不会释放obj的锁,其它线程无法进入这个同步块。 |
|
|
Thread.yield() |
1.4 | 释放cpu,是的cpu重现分配时间。 |
|
|
thr.join() |
1.4 | 等待thr线程执行完毕。跳出join的方法有两个: 1,等待thr执行完毕。 2,在等待线程上调用interrupt()方法。 |
|
|
thr.interrupt() |
1.4 | 1,设置thr的interrupted状态。 2,如果thr线程在sleep, wait, join等所有可以抛出InterruptedException方法上阻塞,则中断此线程。 无法中断在同步锁上阻塞的线程。 |
|
并发集合
从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.
来源:oschina
链接:https://my.oschina.net/u/254689/blog/220763