1.同步器Synchronizer
synchronizer是一个对象,它根据自身状态调节线程的控制流。阻塞队列可以扮演一个synchronizer,其他类型的synchronizer包括:信号量(semaphore)、关卡(barrier)和闭锁(latch)。
1.1闭锁(latch)
闭锁是可以延迟线程的进度直到线程到达终点状态的一种同步器。每个闭锁就相当于是一道大门,在闭锁到达重点状态之前,门一直是关闭的,没有线程可以通过,当到达终点状态时,门打开了,允许所有的线程通过。一旦闭锁到达终点状态,就不能再改变状态了,也就是永远保持开放的状态。闭锁用来确保特定活动直到其他活动都完成后才发生。
TestHarness使用两个阀门,开始阀门将计数器初始化为1,结束阀门将计数器初始化为工作线程的数量。每个工作线程要做的第一件事是等待阀门打开。每个线程最后一件事是将结束阀门减一,这样做使得工作线程有效的等待,直到最后一个工作线程完成了自己的任务,这个就可以计算整个过程的耗时。
1.2 FutureTask
FutureTask也可以作为闭锁。FutureTask描述了一个抽象的可携带结果的计算。FutureTask的计算是通过Callable实现的,它等价于一个可以携带结果的Runnable。它有三个状态:等待、运行和完成。完成包括:正常结束、取消和异常结束。一旦FutureTask进入完成状态,他就会永远停止在这个状态上。
Future.get的行为依赖于任务的状态。如果它已经完成,get立刻返回得到结果,否则会阻塞直到任务进入完成状态。FutureTask把计算的结果从当前的线程传送到需要该结果的线程。
利用FutureTask来完成异步任务,并在真正需要的计算结果之前就启动异步任务,这样可以减少等待的时间。
1.3 信号量(semaphore)
信号量用来控制能够同时访问某特定资源的活动的数量,或者是同时执行某一给定操作的数量。信号量可以用来实现资源池或者某一容器的限定的边界。
一个信号量管理一个有效的许可集合,活动能够获得许可,并在使用完后,释放许可。如果没可用的许可了,那么acquire方法会被阻塞,只有有可用的许可为止。Release方法向信号量返回一个许可。
信号量可以用来实现资源池,比如数据库连接池。每个池子有固定的长度,当池子为空,向池子申请资源将会失败,需要阻塞请求,一旦池子非空,解除阻塞。
用信号量将容器转化为有界容器。信号量被初始化为指定的大小,每次添加操作,向底层容器执行add操作之前,获取一个许可。同样的,每次删除的时候,在底层容器删除之后,释放一个许可。
1.4 关卡(barrier)
关卡类似于闭锁,它能阻塞一组线程,知道某些事件的发生。关卡与闭锁的关键不同在于,所有的线程必须同时到达关卡点,才能继续处理。闭锁等待的是事件,关卡等待的是其他线程。就是说闭锁用来等待的事件就是countDown事件,只有该countDown事件执行后所有之前在等待的线程才有可能继续执行;而栅栏没有类似countDown事件控制线程的执行,只有线程的await方法能控制等待的线程执行。CyclicBarrier强调的是n个线程,大家相互等待,只要有一个没完成,所有人都得等着。
当线程到达关卡点,调用await,await会被阻塞,直到所有线程都到达关卡点。如果所偶线程都到达了关卡点,关卡被成功突破,这样所有的被阻塞的线程会被释放,此时关卡被重置,以备下一次使用。如果调用await超时,或者阻塞中的线程被中断,那么关卡就被置为失败。所有的对await未完成的调用,都会通过抛出BrokenBarrierException而终止。
10个人去春游,规定达到一个地点后才能继续前行:
synchronizer是一个对象,它根据自身状态调节线程的控制流。阻塞队列可以扮演一个synchronizer,其他类型的synchronizer包括:信号量(semaphore)、关卡(barrier)和闭锁(latch)。
1.1闭锁(latch)
闭锁是可以延迟线程的进度直到线程到达终点状态的一种同步器。每个闭锁就相当于是一道大门,在闭锁到达重点状态之前,门一直是关闭的,没有线程可以通过,当到达终点状态时,门打开了,允许所有的线程通过。一旦闭锁到达终点状态,就不能再改变状态了,也就是永远保持开放的状态。闭锁用来确保特定活动直到其他活动都完成后才发生。
/** * @Title: TestHarnessTest.java * @Package synchronizer * @Description: TODO * @author 落叶飞翔的蜗牛 * @date 2017年12月16日 上午10:40:11 * @version V1.0 */ package synchronizer; import java.util.concurrent.CountDownLatch; /** * @author 落叶飞翔的蜗牛 * @date 2017年12月16日 上午10:40:11 */ public class TestHarness { public static long timeTasks(int nThread, final Runnable task) { long start = System.currentTimeMillis(); final CountDownLatch startGate = new CountDownLatch(1); final CountDownLatch endGate = new CountDownLatch(nThread); for (int i = 0; i < nThread; i++) { Thread thread = new Thread() { @Override public void run() { try { startGate.await(); try { task.run(); } finally { System.out.println("单个任务结束"); endGate.countDown(); } } catch (InterruptedException e) { e.printStackTrace(); } } }; thread.start(); } startGate.countDown(); try { endGate.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("所有任务结束"); long end = System.currentTimeMillis(); return end - start; } }
/** * @Title: TestHarnessTest.java * @Package synchronizer * @Description: TODO * @author 落叶飞翔的蜗牛 * @date 2017年12月16日 上午10:46:05 * @version V1.0 */ package synchronizer; import org.junit.Test; /** * @author 落叶飞翔的蜗牛 * @date 2017年12月16日 上午10:46:05 */ public class TestHarnessTest { @Test public void test() { int nThread = 2; Runnable task = new Runnable() { @Override public void run() { System.out.println("单个任务开始"); try { Thread.sleep(1000*5); } catch (InterruptedException e) { e.printStackTrace(); } } }; long time = TestHarness.timeTasks(nThread, task); System.out.println("运行耗时:" + time); } }
TestHarness使用两个阀门,开始阀门将计数器初始化为1,结束阀门将计数器初始化为工作线程的数量。每个工作线程要做的第一件事是等待阀门打开。每个线程最后一件事是将结束阀门减一,这样做使得工作线程有效的等待,直到最后一个工作线程完成了自己的任务,这个就可以计算整个过程的耗时。
1.2 FutureTask
FutureTask也可以作为闭锁。FutureTask描述了一个抽象的可携带结果的计算。FutureTask的计算是通过Callable实现的,它等价于一个可以携带结果的Runnable。它有三个状态:等待、运行和完成。完成包括:正常结束、取消和异常结束。一旦FutureTask进入完成状态,他就会永远停止在这个状态上。
Future.get的行为依赖于任务的状态。如果它已经完成,get立刻返回得到结果,否则会阻塞直到任务进入完成状态。FutureTask把计算的结果从当前的线程传送到需要该结果的线程。
利用FutureTask来完成异步任务,并在真正需要的计算结果之前就启动异步任务,这样可以减少等待的时间。
/** * @Title: PreLoader.java * @Package synchronizer.future * @Description: TODO * @author 落叶飞翔的蜗牛 * @date 2017年12月16日 下午2:24:46 * @version V1.0 */ package synchronizer.future; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; /** * @author 落叶飞翔的蜗牛 * @date 2017年12月16日 下午2:24:46 */ public class PreLoader { /** * future:FutureTask<Object> TODO(模拟异步任务). * @author: 落叶飞翔的蜗牛 * @date: 2017年12月16日 下午3:17:41 */ private final FutureTask<Object> future = new FutureTask<>(new Callable<Object>() { /** * TODO 异步任务,睡眠5s * @see java.util.concurrent.Callable#call() */ @Override public Object call() throws Exception { Thread.sleep(5000); return "SUCCESS"; } }); private final Thread thread = new Thread(future); public void start() { thread.start(); } /** * get:(获取异步线程执行结果). <br/> * @author 落叶飞翔的蜗牛 * @date:2017年12月16日 下午3:24:52 * @return Object */ public Object get() { try { return future.get(); } catch (InterruptedException | ExecutionException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } }
/** * @Title: PreLoaderTest.java * @Package synchronizer.future * @Description: TODO(用一句话描述该文件做什么) * @author 落叶飞翔的蜗牛 * @date 2017年12月16日 下午3:34:56 * @version V1.0 */ package synchronizer.future; import org.junit.Test; /** * ClassName: PreLoaderTest <br/> * Function: 测试PreLoader. <br/> * date: 2017年12月16日 下午3:34:56 <br/> * @author 落叶飞翔的蜗牛 * @version * @since JDK 1.6 */ public class PreLoaderTest { /** * test:(测试PreLoader). <br/> * @author 落叶飞翔的蜗牛 * @date:2017年12月16日 下午3:47:29 */ @Test public void test() { PreLoader preLoader = new PreLoader(); preLoader.start(); long start = System.currentTimeMillis(); /** * 当程序需要用到异步数据时,可以调用get方法. * 如果异步任务完成,这直接返回. * 否则等待加载结束后返回 */ System.out.println("获取结果:" + preLoader.get()); long end = System.currentTimeMillis(); System.out.println("执行耗时: " + (end - start)); } }
1.3 信号量(semaphore)
信号量用来控制能够同时访问某特定资源的活动的数量,或者是同时执行某一给定操作的数量。信号量可以用来实现资源池或者某一容器的限定的边界。
一个信号量管理一个有效的许可集合,活动能够获得许可,并在使用完后,释放许可。如果没可用的许可了,那么acquire方法会被阻塞,只有有可用的许可为止。Release方法向信号量返回一个许可。
信号量可以用来实现资源池,比如数据库连接池。每个池子有固定的长度,当池子为空,向池子申请资源将会失败,需要阻塞请求,一旦池子非空,解除阻塞。
用信号量将容器转化为有界容器。信号量被初始化为指定的大小,每次添加操作,向底层容器执行add操作之前,获取一个许可。同样的,每次删除的时候,在底层容器删除之后,释放一个许可。
/** * @Title: BoundedHashSet.java * @Package synchronizer.semaphore * @Description: TODO(有界HashSet) * @author 落叶飞翔的蜗牛 * @date 2017年12月16日 下午4:56:20 * @version V1.0 */ package synchronizer.semaphore; import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.concurrent.Semaphore; /** * ClassName: BoundedHashSet <br/> * @author 落叶飞翔的蜗牛 * date: 2017年12月16日 下午4:56:20 <br/> * Function: TODO 有界HashSet. <br/> * Reason: TODO ADD REASON(可选). <br/> * @version V1.0 */ public class BoundedHashSet<T> { private Set<T> set = null; private Semaphore semaphore = null; public BoundedHashSet(int bound) { this.set = Collections.synchronizedSet(new HashSet<>()); semaphore = new Semaphore(bound); } /** * add:(添加元素). <br/> * TODO (添加元素之前,先获取一个许可,如果没有剩余许可,阻塞).<br/> * @author 落叶飞翔的蜗牛 * @date: 2017年12月16日 下午5:11:14 * @param t * @return * @throws InterruptedException */ public boolean add(T t) throws InterruptedException { semaphore.acquire(); boolean isAdded = false; try { isAdded = set.add(t); return isAdded; } finally { if(!isAdded) { semaphore.release(); } } } /** * remove:(删除操作). <br/> * TODO (删除成功后释放许可).<br/> * @author 落叶飞翔的蜗牛 * @date: 2017年12月16日 下午5:14:35 * @param t * @return */ public boolean remove(T t) { boolean isRemoved = set.remove(t); if(isRemoved) { semaphore.release(); } return isRemoved; } }
/** * @Title: BoundedHashSetTest.java * @Package synchronizer.semaphore * @Description: 测试BoundedHashSet * @author 落叶飞翔的蜗牛 * @date 2017年12月16日 下午5:18:12 * @version V1.0 */ package synchronizer.semaphore; import org.junit.Test; /** * @ClassName: BoundedHashSetTest * @Description: (测试BoundedHashSet) * @author 落叶飞翔的蜗牛 * @date 2017年12月16日 下午5:18:12 */ public class BoundedHashSetTest { /** * testAdd:(测试添加). <br/> * @Description: (第四个元素加入失败).<br/> * @author 落叶飞翔的蜗牛 * @date: 2017年12月16日 下午5:27:06 * @throws InterruptedException */ @Test public void testAdd() throws InterruptedException { BoundedHashSet<String> boundedHashSet = new BoundedHashSet<>(3); boundedHashSet.add("1"); System.out.println("加入第1个元素成功"); boundedHashSet.add("2"); System.out.println("加入第2个元素成功"); boundedHashSet.add("3"); System.out.println("加入第3个元素成功"); boundedHashSet.add("4"); System.out.println("加入第4个元素成功"); } /** * testAddAndRemove:(测试添加删除). <br/> * @Description: (删除元素1后,元素4就可以加入).<br/> * @author 落叶飞翔的蜗牛 * @date: 2017年12月16日 下午5:29:45 * @throws InterruptedException */ @Test public void testAddAndRemove() throws InterruptedException { BoundedHashSet<String> boundedHashSet = new BoundedHashSet<>(3); boundedHashSet.add("1"); System.out.println("加入第1个元素成功"); boundedHashSet.add("2"); System.out.println("加入第2个元素成功"); boundedHashSet.add("3"); System.out.println("加入第3个元素成功"); boundedHashSet.remove("1"); System.out.println("删除第1个元素成功"); boundedHashSet.add("4"); System.out.println("加入第4个元素成功"); } }
1.4 关卡(barrier)
关卡类似于闭锁,它能阻塞一组线程,知道某些事件的发生。关卡与闭锁的关键不同在于,所有的线程必须同时到达关卡点,才能继续处理。闭锁等待的是事件,关卡等待的是其他线程。就是说闭锁用来等待的事件就是countDown事件,只有该countDown事件执行后所有之前在等待的线程才有可能继续执行;而栅栏没有类似countDown事件控制线程的执行,只有线程的await方法能控制等待的线程执行。CyclicBarrier强调的是n个线程,大家相互等待,只要有一个没完成,所有人都得等着。
当线程到达关卡点,调用await,await会被阻塞,直到所有线程都到达关卡点。如果所偶线程都到达了关卡点,关卡被成功突破,这样所有的被阻塞的线程会被释放,此时关卡被重置,以备下一次使用。如果调用await超时,或者阻塞中的线程被中断,那么关卡就被置为失败。所有的对await未完成的调用,都会通过抛出BrokenBarrierException而终止。
10个人去春游,规定达到一个地点后才能继续前行:
/** * @Title: CyclicBarrierWorker.java * @Package synchronizer.barrier * @Description: (用一句话描述该文件做什么) * @Author 落叶飞翔的蜗牛 * @Date 2017年12月16日 下午6:19:50 * @Version V1.0 */ package synchronizer.barrier; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; /** * @ClassName: CyclicBarrierWorker * @Description: (这里用一句话描述这个类的作用) * @Author 落叶飞翔的蜗牛 * @Date 2017年12月16日 下午6:19:50 */ public class CyclicBarrierWorker implements Runnable { private int id; private CyclicBarrier barrier; public CyclicBarrierWorker(int id, final CyclicBarrier barrier) { this.id = id; this.barrier = barrier; } @Override public void run() { // TODO Auto-generated method stub try { System.out.println("第" + id + "个人到达,并等待"); barrier.await(); // 大家等待最后一个线程到达 } catch (InterruptedException | BrokenBarrierException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
/** * @Title: CyclicBarrierWorkerTest.java * @Package synchronizer.barrier * @Description: (测试类) * @Author 落叶飞翔的蜗牛 * @Date 2017年12月16日 下午6:27:04 * @Version V1.0 */ package synchronizer.barrier; import java.util.concurrent.CyclicBarrier; import org.junit.Test; /** * @ClassName: CyclicBarrierWorkerTest * @Description: (测试类) * @Author 落叶飞翔的蜗牛 * @Date 2017年12月16日 下午6:27:04 */ public class CyclicBarrierWorkerTest { @Test public void test() { int num = 10; CyclicBarrier barrier = new CyclicBarrier(num, new Runnable() { @Override public void run() { //关卡通过后执行以下 System.out.println("一起走啊!"); } }); for (int i = 1; i <= num; i++) { new Thread(new CyclicBarrierWorker(i, barrier)).start(); } } }