java.util.concurrent简介
java.util.concurrent 包含许多线程安全、测试良好、高性能的并发构建块。不客气地说,创建 java.util.concurrent 的目的就是要实现 Collection 框架对数据结构所执行的并发操作。通过提供一组可靠的、高性能并发构建块,开发人员可以提高并发类的线程安全、可伸缩性、性能、可读性和可靠性。在JDK1.5之前,如果要进行业务并发时,通常需要有程序员独立完成代码实现,当然也有一些开源的框架提供了这些功能,但是这些依然没有JDK自带的功能使用起来方便。而当针对高质量Java多线程并发程序设计时,为防止死蹦等现象的出现,比如使用java之前的wait()、notify()和synchronized等,每每需要考虑性能、死锁、公平性、资源管理以及如何避免线程安全性方面带来的危害等诸多因素,往往会采用一些较为复杂的安全策略,加重了程序员的开发负担。在JDK1.5出现之后,Doug Lea 终于为我们这些可怜的小程序员推出了java.util.concurrent工具包以简化并发完成。开发者们借助于此,将有效的减少竞争条件(race conditions)和死锁线程。concurrent包很好的解决了这些问题,为我们提供了更实用的并发程序模型。其中大量使用工厂模式更是使得代码变得健壮,大量的接口封装让调用变得更加容易。
在这里附上一张关系图:
在学习concurrent之前,你应该对线程以及线程池的概念相对熟悉。关于更多concurrent特性与线程进阶知识回顾请参阅(http://www.cnblogs.com/sarafill/archive/2011/05/18/2049461.html),在此表示感谢。
由于concurrent知识量巨大,本文讲解未列出全部API,注意查阅帮助文档。
在concurrent中会有以下常见的类出现以及使用:
Executor | 具体Runnable任务的执行者。 |
ExecutorService | 一个线程池管理者,其实现类有多种,我会介绍一部分。我们能把Runnable,Callable提交到池中让其调度。 |
Semaphore | 一个计数信号量。 |
ReentrantLock | 一个可重入的互斥锁定 Lock,功能类似synchronized,但要强大的多。 |
BlockingQueue | 阻塞队列。 |
CompletionService | ExecutorService的扩展,可以获得线程执行结果。 |
CountDownLatch | 一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。 |
CyclicBarrier | 一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点。 |
Future | 表示异步计算的结果。 |
ScheduledExecutorService | 一个 ExecutorService,可安排在给定的延迟后运行或定期执行的命令。 |
接下来逐一介绍。
Executor
Executor主要是用于创建线程池,常见方法如下:
newFixedThreadPool(固定大小线程池)
创建一个可重用固定线程集合的线程池,以共享的无界队列方式来运行这些线程(只有要请求的过来,就会在一个队列里等待执行)。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)(也就是说,当前一条线程终止,接下来排队的线程将会获得执行机会)。
newCachedThreadPool(无界线程池,可以进行自动线程回收)
创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们(也就是说,当某条线程死亡后所遗留的位置可被新线程使用)。对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能。调用 execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。注意,可以使用 ThreadPoolExecutor 构造方法创建具有类似属性但细节不同(例如超时参数)的线程池。
newSingleThreadExecutor(单个线程)
创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。(注意,如果因为在关闭前的执行期间出现失败而终止了此单个线程,那么如果需要,一个新线程将代替它执行后续的任务)。可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。与其他等效的 newFixedThreadPool(1) 不同,可保证无需重新配置此方法所返回的执行程序即可使用其他的线程。
newScheduledThreadPool(延迟连接池)
可设置线程在提交后,至少要延迟多长时间才能执行。
前三者返回的是ExecutorService对象,第四个返回的是ScheduledExecutorService对象。这些对象可以理解为就是一个线程池。
newFixedThreadPool(固定大小线程池)
老规矩,先看代码:
public class Test {
public static void main(String[] args) {
// 创建一个可重用固定线程数的线程池,最大线程数为5
ExecutorService pool = Executors.newFixedThreadPool(5);
// 创建线程
Thread t1 = new MyThread();
Thread t2 = new MyThread();
Thread t3 = new MyThread();
Thread t4 = new MyThread();
Thread t6 = new MyThread();
Thread t7 = new MyThread();
Thread t8 = new MyThread();
Thread t9 = new MyThread();
Thread t10 = new MyThread();
Thread t11 = new MyThread();
Thread t12 = new MyThread();
// 将线程放入池中进行执行
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
pool.execute(t4);
pool.execute(t6);
pool.execute(t7);
pool.execute(t8);
pool.execute(t9);
pool.execute(t10);
pool.execute(t11);
pool.execute(t12);
// 关闭线程池
pool.shutdown();
//shutdown后,不再接受新的线程请求,但会先执行完已经提交的线程,再结束线程池
//如果改为shutdownNow,则尝试强制终止任务,并返回等待中的Runnable列表
}
}
class MyThread extends Thread {
@Override
public synchronized void run() {
System.out.println(Thread.currentThread().getName() + "正在执行。。。");
try {
wait(2000);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + "执行完毕。。。。。。");
}
}
控制台输出:
pool-1-thread-3正在执行。。。
pool-1-thread-4正在执行。。。
pool-1-thread-1正在执行。。。
pool-1-thread-2正在执行。。。
pool-1-thread-5正在执行。。。
pool-1-thread-1执行完毕。。。。。。
pool-1-thread-2执行完毕。。。。。。
pool-1-thread-5执行完毕。。。。。。
pool-1-thread-4执行完毕。。。。。。
pool-1-thread-2正在执行。。。
pool-1-thread-3执行完毕。。。。。。
pool-1-thread-3正在执行。。。
pool-1-thread-4正在执行。。。
pool-1-thread-5正在执行。。。
pool-1-thread-1正在执行。。。
pool-1-thread-2执行完毕。。。。。。
pool-1-thread-2正在执行。。。
pool-1-thread-3执行完毕。。。。。。
pool-1-thread-1执行完毕。。。。。。
pool-1-thread-4执行完毕。。。。。。
pool-1-thread-5执行完毕。。。。。。
pool-1-thread-2执行完毕。。。。。。
可以看到,最多只能同时运行5条线程。就像开通了5条车道一样,同一时间只能通过五辆车。超过五辆车的,则进入等待,直到有车子走完了,再挤进去。
注意,执行是无序的。
newCachedThreadPool(无界线程池)
与上面的类似,只是改动下pool的创建方式:ExecutorService pool = Executors.newCachedThreadPool();
输出:
pool-1-thread-3正在执行。。。
pool-1-thread-4正在执行。。。
pool-1-thread-1正在执行。。。
pool-1-thread-2正在执行。。。
pool-1-thread-5正在执行。。。
pool-1-thread-6正在执行。。。
pool-1-thread-7正在执行。。。
pool-1-thread-8正在执行。。。
pool-1-thread-9正在执行。。。
pool-1-thread-10正在执行。。。
pool-1-thread-11正在执行。。。
pool-1-thread-7执行完毕。。。。。。
pool-1-thread-8执行完毕。。。。。。
pool-1-thread-4执行完毕。。。。。。
pool-1-thread-5执行完毕。。。。。。
pool-1-thread-6执行完毕。。。。。。
pool-1-thread-2执行完毕。。。。。。
pool-1-thread-1执行完毕。。。。。。
pool-1-thread-3执行完毕。。。。。。
pool-1-thread-11执行完毕。。。。。。
pool-1-thread-10执行完毕。。。。。。
pool-1-thread-9执行完毕。。。。。。
newCachedThreadPool()所产生的线程池会尽可能的容纳更多的线程,这种方式的特点是:可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。
请注意,线程的执行仍然是无序的。
newSingleThreadExecutor(单个线程)
同样的,只需改动一下pool的创建方式:改为 ExecutorService pool = Executors.newSingleThreadExecutor();
输出:
pool-1-thread-1正在执行。。。
pool-1-thread-1执行完毕。。。。。。
pool-1-thread-1正在执行。。。
pool-1-thread-1执行完毕。。。。。。
pool-1-thread-1正在执行。。。
pool-1-thread-1执行完毕。。。。。。
pool-1-thread-1正在执行。。。
pool-1-thread-1执行完毕。。。。。。
pool-1-thread-1正在执行。。。
pool-1-thread-1执行完毕。。。。。。
pool-1-thread-1正在执行。。。
pool-1-thread-1执行完毕。。。。。。
pool-1-thread-1正在执行。。。
pool-1-thread-1执行完毕。。。。。。
pool-1-thread-1正在执行。。。
pool-1-thread-1执行完毕。。。。。。
pool-1-thread-1正在执行。。。
pool-1-thread-1执行完毕。。。。。。
pool-1-thread-1正在执行。。。
pool-1-thread-1执行完毕。。。。。。
pool-1-thread-1正在执行。。。
pool-1-thread-1执行完毕。。。。。。
显然,同一时间只能执行一条线程。
注意了,该线程池的执行是有序的。
newScheduledThreadPool(延迟连接池)
public class Test {
public static void main(String[] args) {
// 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
// 创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口
Thread t1 = new MyThread();
Thread t2 = new MyThread();
Thread t3 = new MyThread();
// 将线程放入池中进行执行
pool.execute(t1);
// 使用延迟执行
pool.schedule(t2, 1000, TimeUnit.MILLISECONDS);
pool.schedule(t3, 1000, TimeUnit.MILLISECONDS);
// 关闭线程池
pool.shutdown();
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "正在执行。。。");
}
}
显然,如果线程位置短缺,执行所要等待的时间会比预定的长。注意,该执行是无序的
Semaphore
Semaphore在多线程环境下被扩放使用,操作系统的信号量是个很重要的概念,在进程控制方面都有应用。Java 并发库 的Semaphore 可以很轻松完成信号量控制,Semaphore可以控制某个资源可被同时访问的个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。比如在Windows下可以设置共享文件的最大客户端访问个数。简单地说, acquire() 用于发放通行证, release() 用于取回许可证,许可证的总数是有限的,如果发放完了仍然有线程请求,那么只能等待。
Semaphore实现的功能就类似厕所有5个坑,假如有10个人要上厕所,那么同时只能有多少个人去上厕所呢?同时只能有5个人能够占用,当5个人中的任何一个人让开后,其中等待的另外5个人中又有一个人可以占用了。另外等待的5个人中可以是随机获得优先机会,也可以是按照先来后到的顺序获得机会,这取决于构造Semaphore对象时传入的参数选项。单个信号量的Semaphore对象可以实现互斥锁的功能,并且可以是由一个线程获得了“锁”,再由另一个线程释放“锁”,这可应用于死锁恢复的一些场合。
Semaphore维护了当前访问的个数,提供同步机制,控制同时访问的个数。在数据结构中链表可以保存“无限”的节点,用Semaphore可以实现有限大小的链表。另外重入锁 ReentrantLock 也可以实现该功能,但实现上要复杂些。
下面的Demo中申明了一个只有2个许可的Semaphore,而有10个线程要获得许可,通过acquire()和release()获取和释放访问许可。
public class Test {
public static void main(String[] args) {
// 创建一个线程池
ExecutorService exe = Executors.newCachedThreadPool();
/* 创建一个一个计数信号量,每次最多发放2个许可
* 如果构造方法为Semaphore(2);
* 那么线程的执行可能不会依照申请的顺序发放
* new Semaphore(2, false) 同等于 new Semaphore(2)
*/
Semaphore sem = new Semaphore(2, true);
for(int i=0; i<10; i++) {
new MyThread(i, sem).start();
}
}
}
class MyThread extends Thread {
int num;
Semaphore sem;
public MyThread(int num, Semaphore sem) {
this.num = num;
this.sem = sem;
}
@Override
public synchronized void run() {
try {
sem.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第" + num + "个线程已经获得一个许可");
try {
wait(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第" + num + "个线程执行完毕,即将释放一个许可");
sem.release();
}
}
输出结果:
第0个线程已经获得一个许可
第1个线程已经获得一个许可
第0个线程执行完毕,即将释放一个许可
第1个线程执行完毕,即将释放一个许可
第2个线程已经获得一个许可
第5个线程已经获得一个许可
第2个线程执行完毕,即将释放一个许可
第5个线程执行完毕,即将释放一个许可
第3个线程已经获得一个许可
第9个线程已经获得一个许可
第3个线程执行完毕,即将释放一个许可
第7个线程已经获得一个许可
第9个线程执行完毕,即将释放一个许可
第4个线程已经获得一个许可
第7个线程执行完毕,即将释放一个许可
第4个线程执行完毕,即将释放一个许可
第6个线程已经获得一个许可
第8个线程已经获得一个许可
第8个线程执行完毕,即将释放一个许可
第6个线程执行完毕,即将释放一个许可
奇怪了,为什么不是按照顺序的呢?上面明明使用的是new Semaphore(2, true)!
注意,别忘了你用的是多线程。因为线程执行的顺序可能是不一样的,因此会导致acquire()申请顺序不一样。实际上,如果你使用了 new Semaphore(2, true) ,那么Semaphore是严格按照 acquire() 的申请顺序来发放许可的。
本文仍在后续整理中,敬请关注。
以上部分内容转载或参考来源如下:
http://www.cnblogs.com/sarafill/archive/2011/05/18/2049461.html
http://heimaxiebo.iteye.com/blog/1848156
http://www.open-open.com/bbs/view/1320131360999
在此表示感谢。
转载请注明来源,版权归原作者所有,未经同意严禁用于任何商业用途。
微博:http://weibo.com/theworldsong
邮箱:theworldsong@foxmail.com