Java高新技术8_多线程3(同步工具类:Semaphore,CyclicBarrier,CountdownLatch,Exchanger,BlockingQueue)
1.Semaphore:
使用Semaphore可以控制并发访问资源的线程个数,
例如,实现一个文件允许的并发访问数。
(这例子真心叼)
Semaphore实现的功能就类似厕所有5个坑(availablePermits=5),假如有十个人(Thread=10)要上厕所,那么同时能有多少个人去上厕所呢?
同时只能有5个人能够占用,当5个人中的任何一个人让开后(sp.release()),其中在等待的另外5个人中又有一个可以占用了。
另外等待的5个人中可以是随机获得优先机会,也可以是按照先来后到的顺序获得机会,这取决于构造Semaphore对象时传入的参数选项。
单个信号量(availablePermits=1)的Semaphore对象可以实现互斥锁的功能,(其实也就是每次只允许一个线程访问)
并且可以是由一个线程获得了“锁”,再由另一个线程释放“锁”,(因为可以通过信号量来释放,不在通过线程自身)
这可应用于死锁恢复的一些场合。示例:
class Resource{ private Semaphore sp=new Semaphore(3,true);//最多允许三个线程并发访问 public void runCode(){ try { sp.acquire();//从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。 //获取一个许可(如果提供了一个)并立即返回,将可用的许可数减 1。 } catch (InterruptedException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"...is coming..." +"当前已有"+(3-sp.availablePermits())+"个线程获得许可"); try{ Thread.sleep(10); } catch(Exception e){ } System.out.println(Thread.currentThread().getName()+"...is leaving"); sp.release(); System.out.println(Thread.currentThread().getName()+"...线程离开..." +"当前已有"+(3-sp.availablePermits())+"个线程获得许可"); } } public class SemaphoreDemo7 { /** * @param args */ private static Resource r=new Resource(); public static void main(String[] args) { // TODO 自动生成的方法存根 ExecutorService threadPool=Executors.newCachedThreadPool(); for(int i=0;i<5;++i) threadPool.execute(new Runnable(){ @Override public void run() { // TODO 自动生成的方法存根 r.runCode(); } }); } }结果分析:
/* pool-1-thread-2...is coming...当前已有3个线程获得许可 pool-1-thread-1...is coming...当前已有3个线程获得许可 pool-1-thread-3...is coming...当前已有3个线程获得许可 //以上结果可能是,thread-1,2,3均执行完sp.acquire(),在打印 也就是 3-0 pool-1-thread-1...is leaving pool-1-thread-3...is leaving pool-1-thread-1...线程离开...当前已有2个线程获得许可 //以上说明thread-1 release(),thread-2,thread-3持有许可,因此 3-1; pool-1-thread-2...is leaving//由下面的thread-4,5都能进来,说明thread-2已经release(还没有打印);此时 3-2,只有thread-3还持有许可 pool-1-thread-5...is coming...当前已有3个线程获得许可 pool-1-thread-4...is coming...当前已有2个线程获得许可 //以上可能thread-4执行完acquire后,3-1(Thread-3,Thread-4),然后thread-5,acquire,3-0(Thread-3,Thread-4,Thread-5) pool-1-thread-3...线程离开...当前已有2个线程获得许可 pool-1-thread-2...线程离开...当前已有2个线程获得许可 //以上,Thread-3,release(3-1) pool-1-thread-5...is leaving pool-1-thread-4...is leaving pool-1-thread-5...线程离开...当前已有1个线程获得许可 pool-1-thread-4...线程离开...当前已有0个线程获得许可 //以上只剩Thread-4和Thread-5在执行 */
2.CyclicBarrier:(循环障碍物)
示例:
表示大家彼此等待,大家集合好后才开始出发,分散活动后又在指定地点(cb.await())集合碰面,
这就好比整个公司的人员利用周末时间集体郊游一样,
先各自从家出发到公司集合后,再同时出发到公园游玩,在指定地点集合后再同时开始就餐public class CyclicBarrierTest8 { public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(); final CyclicBarrier cb = new CyclicBarrier(3); for(int i=0;i<3;i++){ Runnable runnable = new Runnable(){ public void run(){ try { Thread.sleep((long)(Math.random()*10000)); System.out.println("线程" + Thread.currentThread().getName() + "即将到达集合地点1,当前已有" + (cb.getNumberWaiting()+1) + "个已经到达," + (cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在等候")); cb.await(); Thread.sleep((long)(Math.random()*10000)); System.out.println("线程" + Thread.currentThread().getName() + "即将到达集合地点2,当前已有" + (cb.getNumberWaiting()+1) + "个已经到达," + (cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在等候")); cb.await(); Thread.sleep((long)(Math.random()*10000)); System.out.println("线程" + Thread.currentThread().getName() + "即将到达集合地点3,当前已有" + (cb.getNumberWaiting() + 1) + "个已经到达," + (cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在等候")); cb.await(); } catch (Exception e) { e.printStackTrace(); } } }; service.execute(runnable); } service.shutdown(); } } /* cb.getNumberWaiting():当前阻塞在 await() 中的参与者数目。 当某个线程在cb.await()阻塞后, cb.getNumberWaiting()+1 以上 cb.getNumberWaiting()+1为了更直观 cb.getNumberWaiting()取值:0,1,2,也就是说当第三个线程调用cb.await()后,将继续向下执行 */
3.CountdownLatch:
这个例子简单模拟了百米赛跑:
有3个运动员(Thread-0,1,2)和一个裁判,三个运动员都在等待裁判的命令(cdOrder.await();)
当裁判发出命令(cdOrder.countDown();计数器为0),三个运动员开始跑,裁判在终点等待三个运动员跑完
统计结果(cdAnswer.await()),当三个运动员均到达终点(cdAnswer.countDown();计数器为0)
裁判开始统计结果public class CountdownLatchTest9 { public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(); final CountDownLatch cdOrder = new CountDownLatch(1); final CountDownLatch cdAnswer = new CountDownLatch(3); for(int i=0;i<3;i++){ Runnable runnable = new Runnable(){ public void run(){ try { System.out.println("线程" + Thread.currentThread().getName() + "正准备接受命令"); cdOrder.await();//① System.out.println("线程" + Thread.currentThread().getName() + "已接受命令"); Thread.sleep((long)(Math.random()*10000)); System.out.println("线程" + Thread.currentThread().getName() + "回应命令处理结果"); cdAnswer.countDown(); } catch (Exception e) { e.printStackTrace(); } } }; service.execute(runnable); } try { Thread.sleep((long)(Math.random()*10000)); System.out.println("线程" + Thread.currentThread().getName() + "即将发布命令"); cdOrder.countDown(); System.out.println("线程" + Thread.currentThread().getName() + "已发送命令,正在等待结果"); cdAnswer.await();//② System.out.println("线程" + Thread.currentThread().getName() + "已收到所有响应结果"); } catch (Exception e) { e.printStackTrace(); } service.shutdown(); } } /*CountdownLatch作用: 犹如倒计时计数器,调用CountDownLatch对象的countDown方法就将计数器减1, 当计数到达0时,则所有等待者或单个等待者开始执行。 可以实现一个人(也可以是多个人)等待其他所有人都来通知他, 可以实现一个人通知多个人的效果,*/
4.Exchanger:
举例:毒品交易
A哥们拿着钱到指定地点(exchanger.exchange(money))等待B哥们的毒品
当B哥们也到大指定地点(exchanger.exchange(drug))两者完成交易,A拿到毒品,B拿到钱.public class ExchangerTest10 { public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(); final Exchanger exchanger = new Exchanger(); service.execute(new Runnable(){ public void run() { try { String data1 = "zxx"; System.out.println("线程" + Thread.currentThread().getName() + "正在把数据" + data1 +"换出去"); Thread.sleep((long)(Math.random()*10000)); String data2 = (String)exchanger.exchange(data1); System.out.println("线程" + Thread.currentThread().getName() + "换回的数据为" + data2); }catch(Exception e){ } } }); service.execute(new Runnable(){ public void run() { try { String data1 = "lhm"; System.out.println("线程" + Thread.currentThread().getName() + "正在把数据" + data1 +"换出去"); Thread.sleep((long)(Math.random()*10000)); String data2 = (String)exchanger.exchange(data1); System.out.println("线程" + Thread.currentThread().getName() + "换回的数据为" + data2); }catch(Exception e){ } } }); } }
5.BlockingQueue(阻塞队列):
package com.itheima.thread.current; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; public class BlockingQueueTest12 { public static void main(String[] args) { final BlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(3); for(int i=0;i<2;i++){ new Thread(){ public void run(){ while(true){ try { Thread.sleep((long)(Math.random()*1000)); System.out.println(Thread.currentThread().getName() + "准备放数据!"); queue.put(1);//指定的元素插入此队列的尾部,如果该队列已满,则等待可用的空间。 System.out.println(Thread.currentThread().getName() + "已经放了数据," + "队列目前有" + queue.size() + "个数据"); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start(); } new Thread(){ 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(); } }用Semaphore和ArrayBlockingQueue完成线程间通信:
package com.itheima.thread.current; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.Semaphore; /*子线程循环10次,接着主线程循环100,接着又回到子线程循环10次,接着再回到主线程循环 100,如此循环50次,请写出程序*/ //尝试用Semaphore和阻塞队列来模拟线程通信 //其实也就是两个Semaphore对象中许可数均为1,两个阻塞队列均只有一个元素,两者思想完全一样 class RunCode{ /*Semaphore sp1=new Semaphore(1); Semaphore sp2=new Semaphore(1);*/ ArrayBlockingQueue<Integer> blockQueue1 =new ArrayBlockingQueue<Integer> (1); ArrayBlockingQueue<Integer> blockQueue2 =new ArrayBlockingQueue<Integer> (1); { /*try { sp2.acquire();//先把sp2的一个许可拿走 } catch (InterruptedException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); }*/ try { blockQueue2.put(6);//放一个数据,导致2队列已满 } catch (InterruptedException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } } public void subThreadCode(){ /* try { sp1.acquire(); } catch (InterruptedException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } */ try { blockQueue1.put(12); } catch (InterruptedException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } for(int i=0;i<5;++i) System.out .println(Thread.currentThread().getName() + "..." + i); // sp2.release(); try { blockQueue2.take(); } catch (InterruptedException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } } public void mainThreadCode(){ /* try { sp2.acquire(); } catch (InterruptedException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); }*/ try { blockQueue2.put(12); } catch (InterruptedException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } for(int i=0;i<3;++i) System.out .println(Thread.currentThread().getName() + "..." + i); //sp1.release(); try { blockQueue1.take(); } catch (InterruptedException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } } } public class ThreadInterviewQuestion11 { public static void main(String[] args) { final RunCode rc=new RunCode(); new Thread(new Runnable(){ @Override public void run() { for(int i=0;i<5;++i){ rc.subThreadCode(); } } }).start(); for(int i=0;i<5;++i){ rc.mainThreadCode(); } } }
6.同步集合:
传统方式下用Collections工具类提供的synchronizedCollection方法来获得同步集合,分析该方法的实现源码:其实就是把方法放到同步代码块中,锁为当前集合对象
传统方式下的Collection在迭代集合时,不允许对集合进行修改。
Java5中提供了如下一些同步集合类:
通过看java.util.concurrent包下的介绍可以知道有哪些并发集合
ConcurrentHashMap
CopyOnWriteArrayList
CopyOnWriteArraySet分析下为什么在使用迭代器遍历时,使用集合的add/remove会出现ConcurrentmodificationException
自定义User类:
package com.itheima.thread.current.collection; 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 com.itheima.thread.current.collection; import java.util.ArrayList; import java.util.Collection; import java.util.ConcurrentModificationException; import java.util.Iterator; public class Ch15_Demo13 { public static void main(String[] args) { Collection<User> users = new ArrayList<User>();//使用CopyOnWriteArrayList,在迭代时可以remove/add users.add(new User("张三",28)); users.add(new User("李四",25)); users.add(new User("王五",31)); Iterator<User> 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); } } } }/* 在用迭代器遍历时,采用集合的remove方法 经过调试: 1.remove("张三 ")抛出ConcurrentModificationException原因 第一次执行循环后,张三的确被删除了,但是此时modCount++ -> 4 cursor=1 第二次循环(cursor(1)!=size(2)) itrUsers.next();由于expectedModCount(3) != modCount(4) 导致并发修改异常 2.remove("李四")打印出了{name:'张三',age:28}没有抛出任何异常 第一次循环后,张三被打印 cursor=1 第二次循环,李四被删除,modCount=4,cursor=2,size=2 第三次循环,cursor==size导致循环结束,没有执行itrUsers.next()导致没有抛出异常 3.remove("王五")打印出了{name:'张三',age:28},{name:'李四',age:25}抛出了异常 第一,二次循环,张三李四被打印 cursor=2 第三次循环王五被删除,modCount=4,cursor=3,size=2; 第四次循环cursor!=size->itrUsers.next()抛出异常 在用迭代器遍历是,采用迭代器的remove方法: public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet;//重置了cursor为 cursor-1; lastRet = -1; expectedModCount = modCount;//迭代器和集合两个变量保持一致 } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } */关于迭代时,使用集合方法修改数据另一种说法:http://java.chinaitlab.com/base/821113_2.html
Iterator 是工作在一个独立的线程中,并且拥有一个 mutex 锁。 Iterator 被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出 java.util.ConcurrentModificationException 异常。
所以 Iterator 在工作的时候是不允许被迭代的对象被改变的。但你可以使用 Iterator 本身的方法 remove() 来删除对象, Iterator.remove() 方法会在删除当前迭代对象的同时维护索引的一致性。