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

posted @ 2013-08-07 18:33  伊秋  阅读(480)  评论(0编辑  收藏  举报