Java编程思想-并发(5)
来源:互联网 发布:wbinairbook mac os 编辑:程序博客网 时间:2024/05/16 00:33
新类库中的构件
JDK1.5引入了java.util.concurrent包,它提供了大量的新类,用于安全而高效地解决并发问题。下面将通过例子一一介绍。
CountDownLatch-倒数计数器
CountDownLatch被称为倒数计步器,它是Java内置的同步器的一种(还有信号量、CyclicBarrier等同步器,后续将作介绍)。它的功能是阻塞一个或多个线程,这些阻塞的线程需要等待其他线程中的某一个或几个条件成立,一旦成立,这些阻塞的线程将并发执行。比如,有若干运动员等待着(若干线程阻塞)发令枪响起(使解除线程阻塞的条件成立),一旦枪声响起,运动员将开始起跑(解除阻塞),这里发令枪是条件,若干运动员是等待条件成立的线程。而当所有运动员冲过终点线时,或者说最后一个运动员冲过终点线时,计时器停止计时,在这里,所有的运动员又成了条件,而计时器成了达成条件的结果——当计时器在等待着最后一名运动员冲过终点线,条件达成,计时终止。
CountDownLatch最重要的方法是countDown()和await(),前者主要是倒数一次,后者是等待倒数到0,如果没有到达0,就只有阻塞等待了。
方法说明:
public void countDown()
- 递减锁存器的计数,如果计数到达零,则释放所有等待的线程。如果当前计数大于零,则将计数减少。如果新的计数为零,出于线程调度目的,将重新启用所有的等待线程。如果当前计数等于零,则不发生任何操作。
public boolean await(long timeout,
TimeUnit unit)
throws InterruptedException- 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。如果当前计数为零,则此方法立刻返回 true 值。
如果当前计数大于零,则出于线程调度目的,将禁用当前线程,且在发生以下三种情况之一前,该线程将一直处于休眠状态:1、由于调用 countDown() 方法的次数还不够计数到达零;2、其他某个线程中断当前线程;3、已超出指定的等待时间。如果计数到达零,则该方法返回 true 值。如果当前线程:
在进入此方法时已经设置了该线程的中断状态;或者
在等待时被中断,
则抛出 InterruptedException,并且清除当前线程的已中断状态。如果超出了指定的等待时间,则返回值为 false。如果该时间小于等于零,则此方法根本不会等待。
- 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。如果当前计数为零,则此方法立刻返回 true 值。
参数:
timeout - 要等待的最长时间
unit - timeout 参数的时间单位。
返回:
如果计数到达零,则返回 true;如果在计数到达零之前超过了等待时间,则返回 false
抛出:
InterruptedException - 如果当前线程在等待时被中断
public class CountDownLatchTest { // 模拟了100米赛跑,10名选手已经准备就绪,只等裁判一声令下。当所有人都到达终点时,比赛结束。 public static void main(String[] args) throws InterruptedException { // 开始的倒数锁 final CountDownLatch begin = new CountDownLatch(1); // 结束的倒数锁 final CountDownLatch end = new CountDownLatch(10); // 十名选手 final ExecutorService exec = Executors.newFixedThreadPool(10); for (int index = 0; index < 10; index++) { final int NO = index + 1; Runnable run = new Runnable() { public void run() { try { // 如果当前计数为零,则此方法立即返回。 // 等待 begin.await(); Thread.sleep((long) (Math.random() * 10000)); System.out.println("No." + NO + " arrived"); } catch (InterruptedException e) { } finally { // 每个选手到达终点时,end就减一 end.countDown(); } } }; exec.submit(run); } System.out.println("Game Start"); // begin减一,开始游戏 begin.countDown(); // 等待end变为0,即所有选手到达终点 end.await(); System.out.println("Game Over"); exec.shutdown(); }}
Game StartNo.9 arrivedNo.6 arrivedNo.8 arrivedNo.7 arrivedNo.10 arrivedNo.1 arrivedNo.5 arrivedNo.4 arrivedNo.2 arrivedNo.3 arrivedGame Over
CyclicBarrier
CyclicBarrier和CountDownLatch一样,都是关于线程的计数器。
用法略有不同:并发执行一组任务,它们并行地执行工作然后再进行下一个步骤前等待, 直到所有的任务都完成。它使得所有的并行任务都将在栅栏处排队,因此可以一致地向前移动。它与CountDownLatch的最主要区别是前者可以多次重用,而后者只能触发一次。
public class TestCyclicBarrier { 2 //并发线程数 3 private static final int THREAD_NUM = 5; 4 5 public static class WorkerThread implements Runnable{ 6 7 CyclicBarrier barrier; 8 9 public WorkerThread(CyclicBarrier b){10 this.barrier = b;11 }12 13 @Override14 public void run() {15 // TODO Auto-generated method stub16 try{17 System.out.println("Worker's waiting");18 //线程在这里等待,直到所有线程都到达barrier。19 barrier.await();20 System.out.println("ID:"+Thread.currentThread().getId()+" Working");21 }catch(Exception e){22 e.printStackTrace();23 }24 }25 26 }27 28 /**29 * @param args30 */31 public static void main(String[] args) {32 // TODO Auto-generated method stub33 CyclicBarrier cb = new CyclicBarrier(THREAD_NUM, new Runnable() {34 //当所有线程到达barrier时执行35 @Override36 public void run() {37 // TODO Auto-generated method stub38 System.out.println("Inside Barrier");39 40 }41 });42 43 for(int i=0;i<THREAD_NUM;i++){44 new Thread(new WorkerThread(cb)).start();45 }46 }47 48 }
//输出:51 Worker's waiting52 Worker's waiting53 Worker's waiting54 Worker's waiting55 Worker's waiting56 Inside Barrier57 ID:12 Working58 ID:8 Working59 ID:11 Working60 ID:9 Working61 ID:10 Working
CyclicBarrier初始化时规定一个数目,然后计算调用了CyclicBarrier.await()进入等待的线程数。当线程数达到了这个数目时,所有进入等待状态的线程被唤醒并继续。
CyclicBarrier就象它名字的意思一样,可看成是个障碍, 所有的线程必须到齐后才能一起通过这个障碍。
CyclicBarrier初始时还可带一个Runnable的参数, 此Runnable任务在CyclicBarrier的数目达到后,所有其它线程被唤醒前被执行。
DelayQueue
1、DelayQueue是一个无界的BlockingQueue,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走。这种队列是有序的,即队头对象的延迟到期的时间最长。如果没有任何延迟到期,那么不会有任何元素,并且poll()将返回null。(正是因为这样,你不能将null放置到这种个队列中。)
2、Delayed
种混合风格的接口,用来标记那些应该在给定延迟时间之后执行的对象。
此接口的实现必须定义一个 compareTo 方法,该方法提供与此接口的 getDelay 方法一致的排序。
3、DelayQueue队列中保存的是实现了Delayed接口的实现类,里面必须实现getDelay()和compareTo()方法,前者用于取DelayQueue里面的元素时判断是否到了延时时间,否则不予获取,是则获取。 compareTo()方法用于进行队列内部的排序
getDelay(TimeUnit unit){
return unit.convert(time - now(),TimeUnit.NANOSECONDES);//time为设定的间隔时间
}
compareTo(Object object){if(object instanceof SchuduledTask){SchuduledTask task = (SchuduledTask) object ; long l = this.time - task.time;if(l > 0) return 1 ; //比当前的小则返回1,比当前的大则返回-1,否则为0 else if(l < 0 ) return -1;else return 0;}}
下面是一个示例,其中的Delayed对象自身就是任务,而DelayedTaskConsumer将最“紧急”
的任务(到期时间最长的任务)从队列中取出,然后运行它。这样DelayQueue就成了优先级队列的一种变体。
class DelayedTask implements Runnable, Delayed { private static int counter = 0; private final int id = counter++; private final int delta; private final long trigger; protected static List<DelayedTask> sequence = new ArrayList<>(); public DelayedTask(int delayMilliseconds) { delta = delayMilliseconds; trigger = System.nanoTime() + NANOSECONDS.convert(delta, MILLISECONDS); sequence.add(this); } public long getDelay(TimeUnit unit) { return unit.convert(trigger - System.nanoTime(), NANOSECONDS); } public int compareTo(Delayed arg) { DelayedTask that = (DelayedTask)arg; if(trigger < that.trigger) { return -1; } if(trigger > that.trigger) { return 1; } return 0; } public void run() { system.out.print(this + " "); } public String toString() { return String.format("[%1$-4d]", delta) + " Task " + id; } public String summary() { return "(" + id + ":" + delta + ")"; } public static class EndSentinel extends EdlayedTask { private ExecutorService exec; public EndSentinel(int delay, ExecutorService e) { super(delay); exec = e; } public void run() { for(DelayTask pt : sequence) { System.out.print(pt.summary() + " "); } System.out.print(); System.out.print(this + "Calling shutdownNow()"); exec.shutdownNow(); } }}class DelayedTaskConsumer implements Runnable { private DelayQueue<DelayedTaks> q; public DelayedTaskConsumer(DelayQueue<DelayedTask> q) { this.q = q; } public void run() { try { while(!Thread.interrupted()) { q.take().run(); } } catch(InterruptedException e) { } System.out.print("Finished DelayedTaskConsumer"); }}public class DelayQueueDemo { public static void main(String[] args) { Random random = new Random(47); ExecutorService exec = Executors.newCachedThreadPool(); DelayQueue<DelayTask> queue = new DelayQueue<>(); for(int i = 0 ; i < 20; ++i) { queue.put(new DelayTask(random.nextInt(5000))); } queue.add(new DelayedTask.EndSentinel(5000,exec)); exec.execute(new DelayedTaskConsumer(queue)); }}
//输出[128 ] Task 11 [200 ] Task 7 [429 ] Task 5 [520 ] Task 18 [535 ] Task 1 [961 ] Task 4 [998 ] Task 16 [1207 ] Task 9 [1693 ] Task 2 [1809 ] Task 14 [1861 ] Task 3 [2278] Task 15 [3288 ] Task 10 [3551 ] Task 12 [4258 ] Task 0 [4258 ] Task 19 [4522 ] Task 8 [4589 ] Task 13 [4861 ] Task 17 [4868 ] Task 6(0:4258) (1:555) (2:1693) (3:1861) (4:961) (5:429) (6:4868) (7:200) (8:4522) (9:1207) (10:3288) (11:128) (12:3551) (13:4589) (14:1809) (15:2278) (16:998) (17:4861) (18:520) (19:4258) (20:5000)[5000] Task 20 Calling shutdownNow()Finished DelayedTaskConsumer
DelayTask包含一个被称为sequence的List<DelayedTask>
,他保存了任务创建的顺序,因此我们可以看到排序是按照实际发生的顺序执行的。
Delay接口有一个方法名为getDelay(),它可以用来告知延迟到期有多长时间,或者延迟在多长时间之前已经到期。这个方法将强制我们去使用TimeUnit类,因为这就是参数类型。这回产生一个非常方便的类,因为你可以很容易地转换单位而无需任何声明。例如,delta的值是以毫秒为单位存储的,但是Java SE5的方法System.nanoTime()产生的时间则是以纳秒为单位的。你可以转换delta的值,方法是声明它的单位以及你希望以什么单位来表示:
NANOSECONDS.convert(delta, MILLISECONDS);
在getDelay()中,希望使用的单位是作为unit参数传递进来的,你使用它将当前与处罚时间之间的差转换为调用者要求的单位,而无需知道这些单位是什么。
注意,因为DelayedTaskConsumer自身是一个任务,所以它有自己的Thread,它可以使用这个县城来运行从队列中获取的所有任务。由于任务是按照队列优先级的顺序执行的,因此在本例中不需要启动任何单独的线程来运行DelayedTask。
从输出中可以看到,任务创建的顺序没有任何影响,任务是按照所期望的延迟顺序执行的。
PriorityBlockingQueue
这是一个很基础的优先级队列,它具有可阻塞的读取操作。这面这个示例演示了PriorityBlockingQueue的用法,其中在优先级队列中的对象是按照优先级顺序从队列中出现的任务。PrioritizedTask被赋予了一个优先级数字,以此来提供这种顺序:
class PrioritizedTask implements Runnable, Comparable<PrioritizedTask> { private Random random = new Random(47); private static int counter = 0; private final int id = counter++; private final int priority; protected static List<PrioritizedTask> sequence = new ArrayList<>(); public PrioritizedTask(int priority) { this.priority = priority; sequence.add(this); } public int compareTo(PriorityTask arg) { return priority < arg.priority ? 1 : (priority > arg.priority ? -1 : 0); } public void run() { try { TimeUnit.MILLISECOND.sleep(rand.nextInt(250)); } catch(InterruptedException e) { } System.out.print(this); } public String toString() { return String.format("[%1$-3d]", priority) + " Task " + id; } public String summary() { return "(" + id + ":" + priority + ")"; } public static class EndSentinel extends PrioritizedTask { private ExecutorService exec; public EndSentinel(ExecutorService e) { super(-1); exec = e; } public void run() { int count = 0; for(PrioritizedTask pt : sequence) { System.out.print(pt.summary()); if(++count % 5 == 0) { System.out.print(); } System.out.print(); System.out.print(this + " Calling shutdownNow()"); exec.shutdownNow(); } } }}public PriorityTaskProducer implements Runnable { private Random random = new Random(47); private Queue<Runnable> queue; private ExecutorService exec; public PriorityTaskProducer(Queue<Runnable> q, ExecutorService e) { this.queue = q; this.exec = e; } public void run() { for(int i = 0; i < 20; ++i) { queue.add(new PrioritizedTask(random.nextInt(10))); Thread.yield(); } try { for(int i = 0; i < 10; ++i) { TimeUnit.MILLISECONDS.sleep(250); queue.add(new PriorityTask(10)); } for(int i = 0; i < 10; ++i) { queue.add(new PriorityTask(i)); } queue.add(new PrioritizedTask.EndSentinel(exec)); } catch(InterruptedException e) { } System.out.print("Finished PrioritizedTaskProducer"); }}class PrioritizedTaskConsumer implements Runnable { private PriorityBlockingQueue<Runnable> q; public PrioritizedTaskConsumer(PriorityBlockingQueue<Runnable> q) { this.q = q; } public void run() { try { while(!Thread.interrupted()) { q.take().run(); } } catch(InterruptedException e) { } System.out.print("Finished PrioritizedTaskConsumer"); }}public class PriorityBlockingQueueDemo { public static void main(String[] args) { Random random = new Random(47); ExecutorService exec = Executors.newCachedThreadPool(); PriorityBlockingQueue<Runnable> queue = new PriorityBlockingQueue<>(); exec.execute(new PrioritizedTaskProducer(queue, exec)); exec.execute(new PrioritizedTaskConsumer(queue)); }}
这与前一个示例相同,PrioritizedTask对象的创建序列被记录在sequence List中,用于和实际的执行顺序比较。run()方法将休眠一小段时间,然后打印对象。
在这里,不需要任何显式同步——不必考虑当你从这种队列中读取时,其中是否有元素,因为这个队列在没有元素时,将直接阻塞读取者。
Semaphore-信号量
正常的锁(如synchronized或Lock)在任何时刻都只允许一个任务访问同一项资源,而Semaphore(计数信号量)允许n个任务同时访问这个资源。你还可以将信号量看作是在向外分发使用资源的“许可证”,尽管实际上没有任何许可证对象。
下面演示了信号量的使用。并用到了一个概念——对象池,它管理这个数量有限的对象,当要使用对象时可以签出它们,而在用户使用完毕时,可以将它们签回。这种功能被封装在一个泛型类中:
public class Pool<T> { private int size; private List<T> items = new ArrayList<T>(); private volatile boolean[] checkOut; private Semapore available; public Pool(Class<T> classObject, int size) { this.size = size; this.checkOut = new boolean[size]; this.available = new Semaphore(size, true); for(int i = 0; i < size; ++i) { try { items.add(classObject.newInstance()); } catch(Exception e) { throw new RuntimeExecption(e); } } } public T checkOut() throws InterruptedException { available.acquire(); return getItem(); } public void checkIn(T x) { if(releaseItem(x)) { available.release(); } } private synchronized T getItem() { for(int i = 0; i < size; ++i) { if(!checkOut[i]) { checkOut[i] = true; return items.get(i); } return null; } } private synchronized boolean releaseItem(T item) { int index = items.indexOf(item); if(index == -1) { return false; } if(checkOut[index]) { checkOut[index] = false; return true; } return false; }}
在Pool中,构造方法使用newInstance把对象加载到池中,如果需要一个新的对象,可以调用checkOut(),并在使用完后,嫁给checkIn()。
在checkOut()中,如果没有任何信号量许可证可用——在池中没有更多的对象了,available将阻塞调用过程。在checkIn()中,如果被签入的对象有效,则会向信号量返回一个许可证。
下面使用Fat对象作为示例——Fat类的构造器执行起来很耗时:
public class Fat { private volatile double d; private static int counter = 0; private final int id = counter++; public Fat() { for(int i = 0;i < 10000; ++i) { d += (Math.PI + Math.E) / (double)i; } } public void operation() { System.out.println(this); } public String toString() { return "Fat id: " + id; }}
我们可以使用Pool在管理这个创建耗时的Fat对象,该任务将签出Fat对象,持有一段时间之后再将它们签入,以此来测试Pool这个类:
class CheckoutTask<T> implements Runnable { private static int counter = 0; private final int id = counter++; private Pool<T> pool; public CheckoutTask(Pool<T> pool) { this.pool = pool; } public void run() { T item = pool.checkOut(); System.out.print(this + "checked out " + item); TimeUnit.SECONDS.sleep(1); System.out.print(this + "checking in " + item); pool.checkIn(item); } catch(InterruptedException e) { } public String toString() { return "CheckoutTask " + id + " "; }}public class SemaphoreDemo { final static int SIZE = 25; public static void main(String[] args) { final Pool<Fat> pool = new Pool<Fat>(Fat.class, SIZE); ExecutorService exec = Executors.newCachedThreadPool(); for(int i = 0;i < SIZE; ++i) { exec.execute(new CheckoutTask<Fat>(pool)); } System.out.print("All CheckoutTasks created"); List<Fat> list = new ArrayList<>(); for(int i = 0;i < SIZE; ++i) { Fat f = pool.checkOut(); System.out.print(i + ": main() thread checked out "); f.operation(); list.add(f); } Future<?> bolcked = exec.submit(new Runnable() { public void run() { try { pool.checkOut(); } catch(InterruptedException e) { System.out.print("checkOut() Interrupted"); } } }); TimeUnit.SECONDS.sleep(2); blocked.cancel(true); System.out.print("Checking in objects in " + list); for(Fat f : list) { pool.checkIn(f); } for(Fat f : list) { pool.checkIn(f); } exec.shutdown(); }}
这个示例依赖于Pool客户端严格地并愿意签入所持有的的对象, 当其工作时,这是最简单的解决方案。
- Java编程思想-并发(5)
- java编程思想(读书笔记):21.并发
- Java编程思想-并发(1)
- Java编程思想-并发(2)
- Java编程思想-并发(3)
- Java编程思想-并发(4)
- Java编程思想-并发(6)
- 并发(java编程思想)笔记
- java编程思想--21 并发
- Java编程思想之并发
- Java编程思想-21并发
- JAVA编程思想笔记--并发
- Java编程思想-java中的并发(一)
- Java编程思想-java中的并发(二)
- Java编程思想-java中的并发(三)
- Java编程思想-java中的并发(四)
- Java编程思想——并发(1)
- Java编程思想——并发(2)
- java实现九九乘法表的打印
- Unix网络编程二:传输层:TCP、UDP、SCTP
- Bootstrap后台主区域
- CF容器安全
- 内网转外网方法 Sunny-Ngrok
- Java编程思想-并发(5)
- Project facet Java version 1.8 is not supported解决记录
- sql查询语句
- 使用Mybatis自动生成工具
- javaoop集合框架——List接口的常用方法
- nyoj ACM:部分和问题(DFS 回溯 递归)
- 游戏编程中的人工智能技术:前言、目录、序言等
- CodeForces
- Socket传输(下)----客户端