Concurrent(并发)

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值