黑马程序员---线程并发库

软件包 java.util.concurrent

在并发编程中很常用的实用工具类。

请参见: 
          描述

接口摘要
BlockingDeque 支持两个附加操作的 Queue,这两个操作是:获取元素时等待双端队列变为非空;存储元素时等待双端队列中的空间变得可用。
BlockingQueue 支持两个附加操作的 Queue,这两个操作是:获取元素时等待队列变为非空,以及存储元素时等待空间变得可用。
Callable 返回结果并且可能抛出异常的任务。
CompletionService 将生产新的异步任务与使用已完成任务的结果分离开来的服务。
ConcurrentMap 提供其他原子 putIfAbsentremovereplace 方法的 Map
ConcurrentNavigableMap 支持 NavigableMap 操作,且以递归方式支持其可导航子映射的 ConcurrentMap
Delayed 一种混合风格的接口,用来标记那些应该在给定延迟时间之后执行的对象。
Executor 执行已提交的 Runnable 任务的对象。
ExecutorService Executor 提供了管理终止的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future 的方法。
Future Future 表示异步计算的结果。
RejectedExecutionHandler 无法由 ThreadPoolExecutor 执行的任务的处理程序。
RunnableFuture 作为 Runnable 的 Future
RunnableScheduledFuture 作为 Runnable 的 ScheduledFuture
ScheduledExecutorService 一个 ExecutorService,可安排在给定的延迟后运行或定期执行的命令。
ScheduledFuture 一个延迟的、结果可接受的操作,可将其取消。
ThreadFactory 根据需要创建新线程的对象。
 

类摘要
AbstractExecutorService 提供 ExecutorService 执行方法的默认实现。
ArrayBlockingQueue 一个由数组支持的有界阻塞队列
ConcurrentHashMap 支持获取的完全并发和更新的所期望可调整并发的哈希表。
ConcurrentLinkedQueue 一个基于链接节点的无界线程安全队列
ConcurrentSkipListMap 可缩放的并发 ConcurrentNavigableMap 实现。
ConcurrentSkipListSet 一个基于 ConcurrentSkipListMap 的可缩放并发 NavigableSet 实现。
CopyOnWriteArrayList ArrayList 的一个线程安全的变体,其中所有可变操作(addset 等等)都是通过对底层数组进行一次新的复制来实现的。
CopyOnWriteArraySet 对其所有操作使用内部 CopyOnWriteArrayList 的 Set
CountDownLatch 一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
CyclicBarrier 一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。
DelayQueue Delayed 元素的一个无界阻塞队列,只有在延迟期满时才能从中提取元素。
Exchanger 可以在对中对元素进行配对和交换的线程的同步点。
ExecutorCompletionService 使用提供的 Executor 来执行任务的 CompletionService
Executors 此包中所定义的 ExecutorExecutorServiceScheduledExecutorServiceThreadFactory和 Callable 类的工厂和实用方法。
FutureTask 可取消的异步计算。
LinkedBlockingDeque 一个基于已链接节点的、任选范围的阻塞双端队列
LinkedBlockingQueue 一个基于已链接节点的、范围任意的 blocking queue
PriorityBlockingQueue 一个无界阻塞队列,它使用与类 PriorityQueue 相同的顺序规则,并且提供了阻塞获取操作。
ScheduledThreadPoolExecutor ThreadPoolExecutor,它可另行安排在给定的延迟后运行命令,或者定期执行命令。
Semaphore 一个计数信号量。
SynchronousQueue 一种阻塞队列,其中每个插入操作必须等待另一个线程的对应移除操作 ,反之亦然。
ThreadPoolExecutor 一个 ExecutorService,它使用可能的几个池线程之一执行每个提交的任务,通常使用Executors 工厂方法配置。
ThreadPoolExecutor.AbortPolicy 用于被拒绝任务的处理程序,它将抛出 RejectedExecutionException.
ThreadPoolExecutor.CallerRunsPolicy 用于被拒绝任务的处理程序,它直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务。
ThreadPoolExecutor.DiscardOldestPolicy 用于被拒绝任务的处理程序,它放弃最旧的未处理请求,然后重试 execute;如果执行程序已关闭,则会丢弃该任务。
ThreadPoolExecutor.DiscardPolicy 用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务。
 

枚举摘要
TimeUnit TimeUnit 表示给定单元粒度的时间段,它提供在这些单元中进行跨单元转换和执行计时及延迟操作的实用工具方法。
 

异常摘要
BrokenBarrierException 当某个线程试图等待处于断开状态的 barrier 时,或者 barrier 进入断开状态而线程处于等待状态时,抛出该异常。
CancellationException 如果某项值生成任务(如 FutureTask)的结果因为任务被取消而无法获取到,则抛出该异常。
ExecutionException 当试图获取已通过抛出异常而中止的任务的结果时,抛出此异常。
RejectedExecutionException 当无法执行某个任务时,由 Executor 抛出的异常。
TimeoutException 阻塞操作超时时,抛出该异常。

软件包 java.util.concurrent 的描述

在并发编程中很常用的实用工具类。此包包括了几个小的、已标准化的可扩展框架,以及一些提供有用功能的类,没有这些类,这些功能会很难实现或实现起来冗长乏味。下面简要描述主要的组件。另请参阅 locks 和 atomic 包。

线程池

接口。 Executor  是一个简单的标准化接口,用于定义类似于线程的自定义子系统,包括线程池、异步 IO 和轻量级任务框架。根据所使用的具体 Executor 类的不同,可能在新创建的线程中,现有的任务执行线程中,或者调用  execute()  的线程中执行任务,并且可能顺序或并发执行。 ExecutorService  提供了多个完整的异步任务执行框架。ExecutorService 管理任务的排队和安排,并允许受控制的关闭。 ScheduledExecutorService  子接口及相关的接口添加了对延迟的和定期任务执行的支持。ExecutorService 提供了安排异步执行的方法,可执行由  Callable  表示的任何函数,结果类似于  Runnable Future  返回函数的结果,允许确定执行是否完成,并提供取消执行的方法。 RunnableFuture  是拥有  run  方法的 Future, run  方法执行时将设置其结果。

实现。类 ThreadPoolExecutor 和 ScheduledThreadPoolExecutor 提供可调的、灵活的线程池。Executors 类提供大多数 Executor 的常见类型和配置的工厂方法,以及使用它们的几种实用工具方法。其他基于 Executor 的实用工具包括具体类 FutureTask,它提供 Future 的常见可扩展实现,以及 ExecutorCompletionService,它有助于协调对异步任务组的处理。

队列

java.util.concurrent ConcurrentLinkedQueue 类提供了高效的、可伸缩的、线程安全的非阻塞 FIFO 队列。java.util.concurrent 中的五个实现都支持扩展的 BlockingQueue 接口,该接口定义了 put 和 take 的阻塞版本:LinkedBlockingQueueArrayBlockingQueueSynchronousQueuePriorityBlockingQueue 和 DelayQueue。这些不同的类覆盖了生产者-使用者、消息传递、并行任务执行和相关并发设计的大多数常见使用的上下文。BlockingDeque 接口扩展 BlockingQueue,以支持 FIFO 和 LIFO(基于堆栈)操作。LinkedBlockingDeque 类提供一个实现。

BlockingQueue 接口:

阻塞队列与Semaphore有些相似,但也不同,阻塞队列是一方存放数据,另一方释放数据,Semaphore通常则是由同一方设置和释放信号量。

支持两个附加操作的 Queue,这两个操作是:获取元素时等待队列变为非空,以及存储元素时等待空间变得可用。

BlockingQueue 下表中总结了这些方法:

 功能 \ 结果   Throws Exception     Special value   Blocks Times out
Insert 插入 add(e) offer(e) put(e) offer(e, time, unit)
Remove 移除 remove() poll() take() poll(time, unit)
Examine 检查 element() peek() not applicable  not applicable

只有 put 方法和 take 方法才具有阻塞功能。

BlockingQueue 不接受 null 元素。试图 addput 或 offer 一个 null 元素时,某些实现会抛出 NullPointerExceptionnull 被用作指示 poll 操作失败的警戒值。

LinkedBlockingQueue 是不固定大小的。ArrayBlockingQueue 是固定大小的。

常识:数组就是连续的一片内存;链表就是不连续的一片内存。

ArrayBlockingQueue  数组阻塞队列,演示:

队列是先进先出。

*用3个空间的队列来演示阻塞队列的功能和效果。

package cn.itcast.heima;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockingQueueTest {
	
	public static void main(String[] args) {
		
		//定义一个有3个空间的数组阻塞队列。
		final BlockingQueue queue = new ArrayBlockingQueue(3);
		
		//启动两个线程往队列里放数据
		for(int i=0;i<2;i++){
			new Thread(){
				@Override
				public void run() {
					while(true) {
						try {
							//小睡一会儿准备放数据。
							Thread.sleep((long) (Math.random() * 1000));

							System.out.println("线程"
									+ Thread.currentThread().getName()
									+ "准备往队列queue放数据。");

							//往队列里添加一个整型数据
							queue.put(3);

							System.out.println("线程"
									+ Thread.currentThread().getName()
									+ "已放入一个数据,现在队列里有" + queue.size() + "个数据。");

						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
				}
			}.start();
		}
		
		//启动一个线程进行取走数据。
		new Thread(){
			@Override
			public void run() {
				while(true) {
					try {
						//小睡一会儿准备取数据。将等待时间改成100和1000,看现象。
						Thread.sleep(1000);

						System.out.println("线程"
								+ Thread.currentThread().getName() + "准备取数据。");

						//从队列头部取走一个数据,并且记住该位置。
						queue.take();

						System.out.println("线程"
								+ Thread.currentThread().getName()
								+ "已取走一个数据,现在队列里还剩" + queue.size() + "个数据。");

					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}.start();
	}
}
运行结果:

线程Thread-0准备往队列queue放数据。

线程Thread-0已放入一个数据,现在队列里有1个数据。

线程Thread-1准备往队列queue放数据。

线程Thread-1已放入一个数据,现在队列里有2个数据。

线程Thread-2准备取数据。

线程Thread-2已取走一个数据,现在队列里还剩1个数据。

线程Thread-1准备往队列queue放数据。

线程Thread-1已放入一个数据,现在队列里有2个数据。

线程Thread-0准备往队列queue放数据。

线程Thread-0已放入一个数据,现在队列里有3个数据。

线程Thread-1准备往队列queue放数据。

线程Thread-0准备往队列queue放数据。

线程Thread-2准备取数据。

线程Thread-2已取走一个数据,现在队列里还剩2个数据。

线程Thread-1已放入一个数据,现在队列里有3个数据。

线程Thread-1准备往队列queue放数据。

线程Thread-2准备取数据。

线程Thread-2已取走一个数据,现在队列里还剩2个数据。

线程Thread-0已放入一个数据,现在队列里有3个数据。

线程Thread-0准备往队列queue放数据。

线程Thread-2准备取数据。

线程Thread-2已取走一个数据,现在队列里还剩2个数据。

线程Thread-1已放入一个数据,现在队列里有3个数据。

线程Thread-1准备往队列queue放数据。


*用两个具有1个空间的队列来实现同步通知的功能。

package cn.itcast.heima;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

/*子线程循环10次,主线程循环10次,子线程再循环10次,主线程再循环10次,如此循环50次。*/

public class BlockingQueueCommunication {
	
	public static void main(String[] args) {
		
		final Business business = new Business();
		
		new Thread(
				new Runnable() {
					@Override
					public void run() {
						for(int j=1; j <= 50; j++) {
							business.sub(j);
						}
					}
				}).start();
		
		for(int j=1; j <= 50; j++) {
			business.main(j);
		}
	}
	
	static class Business {
		BlockingQueue queue1 = new ArrayBlockingQueue(1);
		BlockingQueue queue2 = new ArrayBlockingQueue(1);
		
		//初始化时就塞满queue2
		Business(){
			try {
				queue2.put(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		public void sub(int j) {
			try {
				queue1.put(1);
				
				for (int i = 1; i <= 10; i++) {
					System.out.println("sub thread sequence of"
							+ i + ", loop of" + j);
				}
				queue2.take();
				
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		public void main(int j) {
			try {
				queue2.put(1);
				
				for (int i = 1; i <= 10; i++) {
					System.out.println("main thread sequence of" + i
							+ ", loop of" + j);
				}
				queue1.take();
				
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
运行结果:

sub thread sequence of1, loop of1

sub thread sequence of2, loop of1

sub thread sequence of3, loop of1

sub thread sequence of4, loop of1

sub thread sequence of5, loop of1

sub thread sequence of6, loop of1

sub thread sequence of7, loop of1

sub thread sequence of8, loop of1

sub thread sequence of9, loop of1

sub thread sequence of10, loop of1

main thread sequence of1, loop of1

main thread sequence of2, loop of1

main thread sequence of3, loop of1

main thread sequence of4, loop of1

main thread sequence of5, loop of1

main thread sequence of6, loop of1

main thread sequence of7, loop of1

main thread sequence of8, loop of1

main thread sequence of9, loop of1

main thread sequence of10, loop of1

sub thread sequence of1, loop of2

sub thread sequence of2, loop of2

sub thread sequence of3, loop of2

sub thread sequence of4, loop of2

sub thread sequence of5, loop of2

sub thread sequence of6, loop of2

sub thread sequence of7, loop of2

sub thread sequence of8, loop of2

sub thread sequence of9, loop of2

sub thread sequence of10, loop of2

main thread sequence of1, loop of2

main thread sequence of2, loop of2

main thread sequence of3, loop of2

main thread sequence of4, loop of2

main thread sequence of5, loop of2

main thread sequence of6, loop of2

main thread sequence of7, loop of2

main thread sequence of8, loop of2

main thread sequence of9, loop of2

main thread sequence of10, loop of2

sub thread sequence of1, loop of3

sub thread sequence of2, loop of3

sub thread sequence of3, loop of3

sub thread sequence of4, loop of3

sub thread sequence of5, loop of3

sub thread sequence of6, loop of3

sub thread sequence of7, loop of3

sub thread sequence of8, loop of3

sub thread sequence of9, loop of3

sub thread sequence of10, loop of3

main thread sequence of1, loop of3

main thread sequence of2, loop of3

main thread sequence of3, loop of3

main thread sequence of4, loop of3

main thread sequence of5, loop of3

main thread sequence of6, loop of3

main thread sequence of7, loop of3

main thread sequence of8, loop of3

main thread sequence of9, loop of3

main thread sequence of10, loop of3

sub thread sequence of1, loop of4

sub thread sequence of2, loop of4

sub thread sequence of3, loop of4

sub thread sequence of4, loop of4

sub thread sequence of5, loop of4

sub thread sequence of6, loop of4

sub thread sequence of7, loop of4

sub thread sequence of8, loop of4

sub thread sequence of9, loop of4

sub thread sequence of10, loop of4

main thread sequence of1, loop of4

main thread sequence of2, loop of4

main thread sequence of3, loop of4

main thread sequence of4, loop of4

main thread sequence of5, loop of4

main thread sequence of6, loop of4

main thread sequence of7, loop of4

main thread sequence of8, loop of4

main thread sequence of9, loop of4

main thread sequence of10, loop of4

sub thread sequence of1, loop of5

sub thread sequence of2, loop of5

sub thread sequence of3, loop of5

sub thread sequence of4, loop of5

sub thread sequence of5, loop of5

sub thread sequence of6, loop of5

sub thread sequence of7, loop of5

sub thread sequence of8, loop of5

sub thread sequence of9, loop of5

sub thread sequence of10, loop of5

main thread sequence of1, loop of5

main thread sequence of2, loop of5

main thread sequence of3, loop of5

main thread sequence of4, loop of5

main thread sequence of5, loop of5

main thread sequence of6, loop of5

main thread sequence of7, loop of5

main thread sequence of8, loop of5

main thread sequence of9, loop of5

main thread sequence of10, loop of5

......

           

计时

TimeUnit 类为指定和控制基于超时的操作提供了多重粒度(包括纳秒级)。该包中的大多数类除了包含不确定的等待之外,还包含基于超时的操作。在使用超时的所有情况中,超时指定了在表明已超时前该方法应该等待的最少时间。在超时发生后,实现会“尽力”检测超时。但是,在检测超时与超时之后再次实际执行线程之间可能要经过不确定的时间。接受超时期参数的所有方法将小于等于 0 的值视为根本不会等待。要“永远”等待,可以使用 Long.MAX_VALUE 值。

TimeUnit 是一个时间单位的枚举。它里面包含很多的时间单位。


同步器

四个类可协助实现常见的专用同步语句。Semaphore 是一个经典的并发工具。CountDownLatch 是一个极其简单但又极其常用的实用工具,用于在保持给定数目的信号、事件或条件前阻塞执行。CyclicBarrier 是一个可重置的多路同步点,在某些并行编程风格中很有用。Exchanger 允许两个线程在 collection 点交换对象,它在多流水线设计中是有用的。

Semaphore 计数信号灯,演示:

使用Semaphore可以控制同时访问资源的线程个数,例如,实现一个文件允许的并发访问数。

        Semaphore实现的功能就类似厕所有5个坑,假如有十个人要上厕所,那么同时能有多少个人去上厕所呢?同时只能有5个人能够占用,当5个人中的任何一个人让开后,其中在等待的另外5个人中又有一个可以占用了。

        另外等待的5个人中可以是随机获得优先机会,也可以是按照先来后到的顺序获得机会,这取决于构造Semaphore对象时传入的参数选项。

   Semaphore(int permits, boolean fair) 创建具有给定的许可数和给定的公平设置的 Semaphore

package cn.itcast.heima;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

public class SemaphoreTest {
	
	public static void main(String[] args) {
		//new一个线程池
		ExecutorService executor = Executors.newCachedThreadPool();
		
		//new一个含有3个信号灯的同步器。
		final Semaphore semaphore = new Semaphore(3);
		
		//开启10个线程
		for(int i=0;i<10;i++){
			Runnable runnable = new Runnable(){
				@Override
				public void run() {
					
					try {
						//从此信号量获取一个许可,在获取一个许可前一直将线程阻塞。
						semaphore.acquire();
						
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					
					System.out.println("线程"+Thread.currentThread().getName()+"已进入,当前有"+(3-semaphore.availablePermits())+"个线程并发。");
					
					try {
						//等待数秒钟之后
						Thread.sleep((long)(Math.random()*10000));
						
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					
					System.out.println("线程"+Thread.currentThread().getName()+"准备离开");
					
					//释放许可
					semaphore.release();
					
					System.out.println("线程"+Thread.currentThread().getName()+"已离开,当前有"+(3-semaphore.availablePermits())+"个线程并发。");
				}
			};
			executor.execute(runnable);
		}
	}
}
运行结果:

线程pool-1-thread-2已进入,当前有3个线程并发。

线程pool-1-thread-1已进入,当前有3个线程并发。

线程pool-1-thread-3已进入,当前有3个线程并发。

线程pool-1-thread-2准备离开

线程pool-1-thread-2已离开,当前有2个线程并发。

线程pool-1-thread-4已进入,当前有3个线程并发。

线程pool-1-thread-3准备离开

线程pool-1-thread-3已离开,当前有2个线程并发。

线程pool-1-thread-5已进入,当前有3个线程并发。

线程pool-1-thread-1准备离开

线程pool-1-thread-1已离开,当前有2个线程并发。

线程pool-1-thread-6已进入,当前有3个线程并发。

线程pool-1-thread-4准备离开

线程pool-1-thread-4已离开,当前有2个线程并发。

线程pool-1-thread-7已进入,当前有3个线程并发。

线程pool-1-thread-6准备离开

线程pool-1-thread-6已离开,当前有2个线程并发。

线程pool-1-thread-8已进入,当前有3个线程并发。

线程pool-1-thread-8准备离开

线程pool-1-thread-8已离开,当前有2个线程并发。

线程pool-1-thread-9已进入,当前有3个线程并发。

线程pool-1-thread-7准备离开

线程pool-1-thread-7已离开,当前有2个线程并发。

线程pool-1-thread-10已进入,当前有3个线程并发。

线程pool-1-thread-5准备离开

线程pool-1-thread-5已离开,当前有2个线程并发。

线程pool-1-thread-10准备离开

线程pool-1-thread-10已离开,当前有1个线程并发。

线程pool-1-thread-9准备离开

线程pool-1-thread-9已离开,当前有0个线程并发。


CyclicBarrier 循环障碍物,演示:
表示大家彼此等待,大家集合好后才开始出发,分散活动后又在指定地点集合碰面,这就好比整个公司的人员利用周末时间集体郊游一样,先各自从家出发到公司集合后,再同时出发到公园游玩,在指定地点集合后再同时开始就餐,
package cn.itcast.heima;

import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CyclicBarrierTest {

	public static void main(String[] args) {
		//new一个线程池
		ExecutorService executor = Executors.newCachedThreadPool();
		
		//new一个三个线程一起走的循环障碍物
		final CyclicBarrier cb = new CyclicBarrier(3);
		
		//启动三个线程
		for(int i=0;i<3;i++){
			Runnable runnable = new Runnable(){
				@Override
				public void run() {
					try {
						//三个线程从家来学校集合,远近不一样
						Thread.sleep((long)(Math.random()*10000));
						
						System.out.println("线程"+Thread.currentThread().getName()+"到了,现在有"+(cb.getNumberWaiting()+1)+"个线程到了,"+(cb.getNumberWaiting() == 2 ? "到齐了,开始去往下一个站点!" : "继续等..."));
						
						cb.await();
						
						//去往公园的路上
						Thread.sleep((long)(Math.random()*10000));
						
						System.out.println("线程"+Thread.currentThread().getName()+"到了,现在有"+(cb.getNumberWaiting()+1)+"个线程到了,"+(cb.getNumberWaiting() == 2 ? "到齐了,开始去往下一个站点!" : "继续等..."));
						
						cb.await();
						
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			};
			executor.execute(runnable);
		}
		executor.shutdown();
	}
}
运行结果:

线程pool-1-thread-3到了,现在有1个线程到了,继续等...

线程pool-1-thread-2到了,现在有2个线程到了,继续等...

线程pool-1-thread-1到了,现在有3个线程到了,到齐了,开始去往下一个站点!

线程pool-1-thread-3到了,现在有1个线程到了,继续等...

线程pool-1-thread-1到了,现在有2个线程到了,继续等...

线程pool-1-thread-2到了,现在有3个线程到了,到齐了,开始去往下一个站点!


CountDownLatch 倒计时的门闩,演示:

犹如倒计时计数器,调用CountDownLatch对象的countDown方法就将计数器减1,当计数到达0时,则所有等待者或单个等待者开始执行。

给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。

CountDownLatch 是一个通用同步工具,它有很多用途。将计数 1 初始化的 CountDownLatch 用作一个简单的开/关锁存器,或入口:在通过调用countDown() 的线程打开入口前,所有调用 await 的线程都一直在入口处等待。用 N 初始化的 CountDownLatch 可以使一个线程在 N 个线程完成某项操作之前一直等待,或者使其在某项操作完成 N 次之前一直等待。

示例用法: 下面给出了两个类,其中一组 worker 线程使用了两个倒计数锁存器:

  • 第一个类是一个启动信号,在 driver 为继续执行 worker 做好准备之前,它会阻止所有的 worker 继续执行。
  • 第二个类是一个完成信号,它允许 driver 在完成所有 worker 之前一直等待。
package cn.itcast.heima;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CountDownLatchTest {

	public static void main(String[] args) {
		//new一个线程池
		ExecutorService executor = Executors.newCachedThreadPool();
		
		//new两个倒计时器门闩
		final CountDownLatch startSignal = new CountDownLatch(1);
		final CountDownLatch doneSignal = new CountDownLatch(3);
		
		//开启三个worker线程
		for(int i=0;i<3;i++){
			Runnable runnable = new Runnable(){
				@Override
				public void run() {
					try {
						System.out.println("工人线程"+Thread.currentThread().getName()+"就位,等待主线程发号施令");
						
						startSignal.await();
						
						//正在完任务中
						Thread.sleep((long)(Math.random()*10000));
						
						doneSignal.countDown();
						
						System.out.println("工人线程"+Thread.currentThread().getName()+"已完成任务,等待主线程验收。");
						
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			};
			executor.execute(runnable);
		}
		
		//主线程是施令者
		try {
			//主线程等待工人就绪中...
			Thread.sleep((long)(Math.random()*1000));
			
			System.out.println("主线程准备发布命令");
			
			startSignal.countDown();
			
			System.out.println("命令已发布,等待工人线程完成工作...");
			
			doneSignal.await();
			
			System.out.println("主线程已验收全部作业️,下班!");
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		//关闭线程池
		executor.shutdown();
	}
}
运行结果:

工人线程pool-1-thread-2就位,等待主线程发号施令

工人线程pool-1-thread-1就位,等待主线程发号施令

工人线程pool-1-thread-3就位,等待主线程发号施令

主线程准备发布命令

命令已发布,等待工人线程完成工作...

工人线程pool-1-thread-2已完成任务,等待主线程验收。

工人线程pool-1-thread-1已完成任务,等待主线程验收。

工人线程pool-1-thread-3已完成任务,等待主线程验收。

主线程已验收全部作业,下班!


Exchanger同步交换器,演示:

可以在对中对元素进行配对和交换的线程的同步点。每个线程将条目上的某个方法呈现给 exchange 方法,与伙伴线程进行匹配,并且在返回时接收其伙伴的对象。Exchanger 可能被视为 SynchronousQueue 的双向形式。Exchanger 可能在应用程序(比如遗传算法和管道设计)中很有用。

讲解Exchanger的比喻:好比两个毒贩要进行交易,一手交钱、一手交货,不管谁先来到接头地点后,就处于等待状态了,当另外一方也到达了接头地点(所谓到达接头地点,也就是到到达了准备接头的状态)时,两者的数据就立即交换了,然后就又可以各忙各的了。

exchange方法就相当于两手高高举着待交换物,等待人家前来交换,一旦人家到来(即人家也执行到exchange方法),则两者立马完成数据的交换。

package cn.itcast.heima;
import java.util.concurrent.Exchanger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExchangerTest {

	public static void main(String[] args) {
		//new一个线程池
		ExecutorService executor = Executors.newCachedThreadPool();
		
		//new一个Exchanger同步交换器
		final Exchanger exchanger = new Exchanger();
		
		//用此线程池分别开启两个不同的线程,
		executor.execute(new Runnable(){
			@Override
			public void run() {
				try {
					//定义一个要交换出去的数据
					String give = "toy";
					
					//去交换地点的路上
					Thread.sleep((long)(Math.random()*10000));
					
					System.out.println("卖家线程"+Thread.currentThread()+"到达目的地,准备把"+give+"换出去。");
					
					//把自己的物品给对方,并拿回对方给的东西。
					String get = exchanger.exchange(give);
					
					System.out.println("卖家线程"+Thread.currentThread()+"换回的物品是:"+get);
					
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
		
		//开启第二个线程,与第一个线程进行数据交换
		executor.execute(new Runnable(){
			@Override
			public void run() {
				try {
					//定义一个要交换出去的数据
					String give = "money";
					
					//去交换地点的路上
					Thread.sleep((long)(Math.random()*10000));
					
					System.out.println("买家线程"+Thread.currentThread()+"到达目的地,准备把"+give+"换出去。");
					
					//把自己的物品给对方,并拿回对方给的东西。
					String get = exchanger.exchange(give);
					
					System.out.println("买家线程"+Thread.currentThread()+"换回的物品是:"+get);
					
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
		
		//交易完成,关闭线程池。
		executor.shutdown();
	}
}
运行结果:

买家线程Thread[pool-1-thread-2,5,main]到达目的地,准备把money换出去。

卖家线程Thread[pool-1-thread-1,5,main]到达目的地,准备把toy换出去。

卖家线程Thread[pool-1-thread-1,5,main]换回的物品是:money

买家线程Thread[pool-1-thread-2,5,main]换回的物品是:toy


并发 Collection集合

除队列外,此包还提供了设计用于多线程上下文中的 Collection 实现: ConcurrentHashMapConcurrentSkipListMapConcurrentSkipListSetCopyOnWriteArrayList  和  CopyOnWriteArraySet 。当期望许多线程访问一个给定 collection 时, ConcurrentHashMap  通常优于同步的  HashMap ConcurrentSkipListMap  通常优于同步的  TreeMap 。当期望的读数和遍历远远大于列表的更新数时, CopyOnWriteArrayList  优于同步的  ArrayList

此包中与某些类一起使用的"Concurrent"前缀;是一种简写,表明与类似的“同步”类有所不同。例如,java.util.Hashtable 和Collections.synchronizedMap(new HashMap()) 是同步的,但 ConcurrentHashMap 则是“并发的”。并发 collection 是线程安全的,但是不受单个排他锁的管理。在ConcurrentHashMap 这一特定情况下,它可以安全地允许进行任意数目的并发读取,以及数目可调的并发写入。需要通过单个锁不允许对 collection 的所有访问时,“同步”类是很有用的,其代价是较差的可伸缩性。在期望多个线程访问公共 collection 的其他情况中,通常“并发”版本要更好一些。当 collection 是未共享的,或者仅保持其他锁时 collection 是可访问的情况下,非同步 collection 则要更好一些。

大多数并发 Collection 实现(包括大多数 Queue)与常规的 java.util 约定也不同,因为它们的迭代器提供了弱一致的,而不是快速失败的遍历。弱一致的迭代器是线程安全的,但是在迭代时没有必要冻结 collection,所以它不一定反映自迭代器创建以来的所有更新。

不同步的集合,多个线程访问会出现死循环:

黑马程序员---线程并发库_第1张图片

一个线程走到next(), cursor++加到4了,下一个线程进来又加到5了,hasNext()返回true,会陷入死循环。


自己写的SynchronizedMap类,即使现在有了java5提供的并发集合,但是也要掌握设计思想:

package cn.itcast.heima;

import java.util.Collection;
import java.util.Map;
import java.util.Set;

public class SynchronizedMap implements Map  {
	
	private Map map = null;
	private Object lock = null;
	
	SynchronizedMap(Map m) {
		if(m==null){
			throw new NullPointerException();
		}
		this.map = m;
		lock = this;
	}
	
	SynchronizedMap(Map m, Object lock){
		if(m==null || lock==null){
			throw new NullPointerException();
		}
		this.map = m;
		this.lock = lock;
	}
	
	@Override
	public int size() {
		synchronized (lock) {
			return map.size();
		}
	}

	@Override
	public boolean isEmpty() {
		synchronized (lock) {
			return map.isEmpty();
		}
	}

	@Override
	public boolean containsKey(Object key) {
		synchronized (lock) {
			return map.containsKey(key);
		}
	}

	@Override
	public boolean containsValue(Object value) {
		synchronized (lock) {
			return map.containsValue(value);
		}
	}

	@Override
	public V get(Object key) {
		synchronized (lock) {
			return map.get(key);
		}
	}

	@Override
	public V put(K key, V value) {
		synchronized (lock) {
			return map.put(key, value);
		}
	}

	@Override
	public V remove(Object key) {
		synchronized (lock) {
			return map.remove(key);
		}
	}

	@Override
	public void putAll(Map m) {
		synchronized (lock) {
			map.putAll(m);
		}
	}

	@Override
	public void clear() {
		synchronized (lock) {
			map.clear();
		}
	}

	@Override
	public Set keySet() {
		synchronized (lock) {
			return map.keySet();
		}
	}

	@Override
	public Collection values() {
		synchronized (lock) {
			return map.values();
		}
	}

	@Override
	public Set> entrySet() {
		synchronized (lock) {
			return map.entrySet();
		}
	}

}

HashMapHashSet 有什么关系,说的底层一点?

其实HashSet的内部实现也是用的HashMap,只不过它没有使用value那部分而已。看部分源码:

public class HashSet
    extends AbstractSet
    implements Set, Cloneable, java.io.Serializable {
        
    	static final long serialVersionUID = -5024744406713321676L;

    	private transient HashMap map;

    	// Dummy value to associate with an Object in the backing Map
    	private static final Object PRESENT = new Object();

    	/**
     	* Constructs a new, empty set; the backing HashMap instance has
     	* default initial capacity (16) and load factor (0.75).
     	*/
    	public HashSet() {
        	map = new HashMap<>();
    	}
        //下面的代码就不复制了。。。
}

ConcurrentHashMap 这个和 HashMap 差不多。

ConcurrentSkipListMap 类似于 TreeMap 。是一个排序的 Map,传一个比较器进去。

ConcurrentSkipListSet 类似于 TreeSet 。


*java5之前有些集合是线程不安全的,除了线程不安全还有另外一个隐患。

下面看一份会报异常的错误代码,来认识这个隐患:

package cn.itcast.heima;

public class User implements Cloneable{
	private String name;
	private int age;
	
	public User(String name, int age) {
		this.name = name;
		this.age = age;
	}
	public boolean equals(Object obj) {
		if(this == obj) {
			return true;
		}
		if(!(obj instanceof User)) {
			return false;	
		}
		User user = (User)obj;
		//if(this.name==user.name && this.age==user.age)
		if(this.name.equals(user.name) 
			&& this.age==user.age) {
			return true;
		}
		else {
			return false;
		}
	}
	public int hashCode() {
		return name.hashCode() + age;
	}
	
	public String toString() {
		return "{name:'" + name + "',age:" + age + "}";
	}
	public Object clone()  {
		Object object = null;
		try {
			object = super.clone();
		} catch (CloneNotSupportedException e) {}
		return object;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public String getName() {
		return name;
	}
} 
package cn.itcast.heima;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class CollectionModifyExceptionTest {
	
	public static void main(String[] args) {
		
		Collection users = new ArrayList();
		users.add(new User("张三",28));	
		users.add(new User("李四",25));			
		users.add(new User("王五",31));	
		
		Iterator itrUsers = users.iterator();
		while(itrUsers.hasNext()){
			User user = (User)itrUsers.next();
			if("张三".equals(user.getName())){
				users.remove(user);
				//itrUsers.remove();
			} else {
				System.out.println(user);				
			}
		}
	}
}

//java.util.ConcurrentModificationException
//修改异常,之所以会报异常,是因为版本号改变了,只要是修改数据的方法,版本号就会有变化。
//在得到迭代器的时候,迭代器就会得到一个版本号,在每次next方法中都会检查版本号跟上次的一样不一样,如果不一样就会报修改异常。
//在迭代当中你就不能再改集合中的数据了,你一改,版本号就会增加。在next方法中就会核对版本号,发现版本号不对就会抛异常。
//也就是说在迭代的时候不能对集合进行修改。
//有一个奇怪的事情就是删除李四的时候不会抛异常。那是因为,删掉李四以后,size变成2,而当前指针是0、1、2的2,应该指向王五。而hasNext方法是看指针指向的脚标数等不等于size,如果等于size就返回false,连while循环也没有进去,就直接结束了。没有走到next方法那一步,刚才的异常是next方法抛的,所以这次不会抛异常。
//也就是说,只要是删掉倒数第二个,都不会报异常,而删掉最后一个,循环也不会结束,在下一次进入循环的时候报异常。
运行结果:

Exception in thread "main" java.util.ConcurrentModificationException

at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:886)

at java.util.ArrayList$Itr.next(ArrayList.java:836)

at cn.itcast.heima.CollectionModifyExceptionTest.main(CollectionModifyExceptionTest.java:18)


*下面我们把上面代码中的 ArrayList 修改为 CopyOnWriteArrayList 。

CopyOnWriteArrayList 意思是在写的时候会有一份拷贝,就不会有事。允许在迭代中多个线程修改不会出问题。

package cn.itcast.heima;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;

public class CollectionModifyExceptionTest {
	
	public static void main(String[] args) {
		
		Collection users = new CopyOnWriteArrayList();
		users.add(new User("张三",28));	
		users.add(new User("李四",25));			
		users.add(new User("王五",31));	
		
		Iterator itrUsers = users.iterator();
		while(itrUsers.hasNext()){
			User user = (User)itrUsers.next();
			if("张三".equals(user.getName())){
				users.remove(user);
				//itrUsers.remove();
			} else {
				System.out.println(user);				
			}
		}
	}
}
运行结果:

{name:'李四',age:25}

{name:'王五',age:31}


CopyOnWriteArraySet  在写的时候会有一份拷贝。


从以下版本开始:

1.5

你可能感兴趣的:(黑马日志)