• 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();
                  }
                    
                });
         
        }
    
    }

    Semaphore

    结果分析:

    /*
    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()后,将继续向下执行
     */

    CyclicBarrier

    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时,则所有等待者或单个等待者开始执行。
        
        可以实现一个人(也可以是多个人)等待其他所有人都来通知他,
        可以实现一个人通知多个人的效果*/

    CountdownLatch

    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){
                        
                    }                
                }    
            });        
        }
    }

    Exchanger


     

    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();            
        }
    }

    ArrayBlockingQueue

    用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() 方法会在删除当前迭代对象的同时维护索引的一致性。

  • 相关阅读:
    疑问:遍历器 Iterator 接口和生成器函数 Generator 之间的关系?
    2020-03-05:JSX、透传、函数式组件、createElement渲染函数
    疑问:现代浏览器是如何组织模块的?
    2020-03-04:各种遍历方法的区别和 Iterator 遍历器
    2020-03-04:vue-styled-components
    操作系统学习笔记(十一)-- 文件系统管理(下)
    操作系统学习笔记(十)-- 文件系统管理(上)
    操作系统学习笔记(九)-- 虚拟内存管理
    操作系统学习笔记(八)-- 内存管理
    操作系统学习笔记(七)-- 死锁
  • 原文地址:https://www.cnblogs.com/yiqiu2324/p/3243828.html
Copyright © 2020-2023  润新知