Java并发编程四 并发容器

Java并发编程四 并发容器

java.util.concurrent包提供了Java多线程情况下保证线程安全的一系列容器,包括Map,List,Set,Queue接口下的并发类实现。

1.ConcurrentHashMap

ConcurrentMap接口下有两个实现,一个是ConcurrentHashMap和ConcurrentSkipListMap(支持并发排序)。
ConcurrentHashMap是一个经常被使用的数据结构,相比于Hashtable以及Collections.synchronizedMap(),ConcurrentHashMap在线程安全的基础上提供了更好的写并发能力,但同时降低了对读一致性的要求。ConcurrentHashMap的设计与实现非常精巧,大量的利用了volatile,final,CAS等lock-free技术来减少锁竞争对于性能的影响。

1.1 JDK1.6/1.7实现方式

ConcurrentHashMap采用了分段锁的设计,只有在同一个分段内才存在竞态关系,不同的分段锁之间没有锁竞争。相比于对整个Map加锁的设计,分段锁大大的提高了高并发环境下的处理能力。但同时,由于不是对整个Map加锁,导致一些需要扫描整个Map的方法(如size(), containsValue())需要使用特殊的实现,另外一些方法(如clear())甚至放弃了对一致性的要求。

ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。

1.2 JDK8中的实现

它摒弃了Segment(锁段)的概念,而是启用了一种全新的方式实现,利用CAS算法。它沿用了与它同时期的HashMap版本的思想,底层依然由“数组”+链表+红黑树的方式思想(JDK7与JDK8中HashMap的实现),但是为了做到并发,又增加了很多辅助的类.

2.CopyOnWriteArrayList

Copy-On-Write简称COW,是一种用于程序设计中的优化策略
JDK里的COW容器有两种:CopyOnWriteArrayList,CopyOnWriteArraySet
CopyOnWrite容器即写时复制的容器,通俗的理解是当我们往一个容器添加元素时,不直接往当前容器添加,而是先将容器进行Copy,复制出一个新的容器,然后往新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离思想。

使用CopyOnWriteArrayList 的 E remove(int index) 时
由于实现是通过修改拷贝方式,则每次根据下标删除元素时,会从通过System.arraycopy()方法将index下标的前后两段构建成一个新的数组。将指针替换。所以,如果通过remove(int index)删除元素时,记住 每次删除数组都是会变动的。(数组长度,和元素的下标)。

通过代码展示类的使用方式

public static void main(String[] args) {
        final ConcurrentHashMap<String,String> concurrentHashMap = new ConcurrentHashMap<String,String>();
        final ConcurrentLinkedQueue<String> concurrentLinkedQueue = new ConcurrentLinkedQueue<String>();
        final CopyOnWriteArrayList<String> copyOnWriteArrayList = new CopyOnWriteArrayList<String>();

        // CopyOnWriteArraySet 内部是通过CopyOnWriteArrayList 实现的 add() 方法内部调用了al.addIfAbsent()
        // 属于适配器模式(对象适配器)
        final CopyOnWriteArraySet<String> copyOnWriteArraySet = new CopyOnWriteArraySet<String>();
        //闭锁
        final CountDownLatch countDownLatch = new CountDownLatch(2);

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<10;i++){
                    String val = (i+1)+"";
                    copyOnWriteArrayList.add(val);
                    concurrentHashMap.put(val,val);
                    copyOnWriteArraySet.add(val);
                    concurrentLinkedQueue.add(val);
                    System.out.println(Thread.currentThread().getName()+" 添加元素 "+val);
                }
                System.out.println(Thread.currentThread().getName() +"countDown ");
                countDownLatch.countDown();
            }
        },"t1");

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=10;i<20;i++){
                    String val = (i+1)+"";
                    copyOnWriteArrayList.add(val);
                    concurrentHashMap.put(val,val);
                    copyOnWriteArraySet.add(val);
                    concurrentLinkedQueue.add(val);
                    System.out.println(Thread.currentThread().getName()+" 添加元素 "+val);
                }
                System.out.println(Thread.currentThread().getName() +"countDown ");
                countDownLatch.countDown();
            }
        },"t2");

        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println(Thread.currentThread().getName() +"进入await ");
                    countDownLatch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                while(!copyOnWriteArrayList.isEmpty()){
                    System.out.println(Thread.currentThread().getName()+" 删除CopyOnWriteArrayList元素 "+copyOnWriteArrayList.remove(0));
                }
                Iterator iteratorMap = concurrentHashMap.keySet().iterator();
                while(iteratorMap.hasNext()){
                    System.out.println(Thread.currentThread().getName()+" 删除ConcurrentHashMap元素 "+concurrentHashMap.remove(iteratorMap.next()));
                };
                Iterator iterator1Set = copyOnWriteArraySet.iterator();
                while(iterator1Set.hasNext()){
                    System.out.println(Thread.currentThread().getName()+" 删除CopyOnWriteArraySet元素 "+copyOnWriteArraySet.remove(iterator1Set.next()));
                }
                while(!concurrentLinkedQueue.isEmpty()){
                    System.out.println(Thread.currentThread().getName()+" 删除ConcurrentLinkedQueue元素 "+concurrentLinkedQueue.poll());
                    System.out.println(concurrentLinkedQueue);
                }
            }
        },"t3");



        t3.start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t1.start();
        t2.start();
    }

3.阻塞队列 BlockingQueue

BlockingQueue 通常用于一个线程生产对象,而另外一个线程消费这些对象的场景。下图是对这个原理的阐述:
BlockingQueue
一个线程往里边放,另外一个线程从里边取的一个 BlockingQueue。
一个线程将会持续生产新对象并将其插入到队列之中,直到队列达到它所能容纳的临界点。也就是说,它是有限的。如果该阻塞队列到达了其临界点,负责生产的线程将会在往里边插入新对象时发生阻塞。它会一直处于阻塞之中,直到负责消费的线程从队列中拿走一个对象。
负责消费的线程将会一直从该阻塞队列中拿出对象。如果消费线程尝试去从一个空的队列中提取对象的话,这个消费线程将会处于阻塞之中,直到一个生产线程把一个对象丢进队列。

BlockingQueue 具有 4 组不同的方法用于插入、移除以及对队列中的元素进行检查。如果请求的操作不能得到立即执行的话,每个方法的表现也不同。这些方法如下:
blockingQueue

1.抛异常:如果试图的操作无法立即执行,抛一个异常。
2.特定值:如果试图的操作无法立即执行,返回一个特定的值(常常是 true / false)。
3.阻塞:如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行。
4.超时:如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行,但等待时间不会超过给定值。返回一个特定值以告知该操作是否成功(典型的是 true / false)。

3.1 ArrayBlockingQueue(有界队列)

ArrayBlockingQueue 是一个有界的阻塞队列,其内部实现是将对象放到一个数组里。有界也就意味着,它不能够存储无限多数量的元素。它有一个同一时间能够存储元素数量的上限。你可以在对其初始化的时候设定这个上限,但之后就无法对这个上限进行修改了(因为它是基于数组实现的,也就具有数组的特性:一旦初始化,大小就无法修改)。
ArrayBlockingQueue 内部以 FIFO(先进先出)的顺序对元素进行存储。队列中的头元素在所有元素之中是放入时间最久的那个,而尾元素则是最短的那个。

3.2 LinkedBlockingQueue(无界队列)

LinkedBlockingQueue 内部以一个链式结构(链接节点)对其元素进行存储。如果需要的话,这一链式结构可以选择一个上限。如果没有定义上限,将使用 Integer.MAX_VALUE 作为上限。
LinkedBlockingQueue 内部以 FIFO(先进先出)的顺序对元素进行存储。队列中的头元素在所有元素之中是放入时间最久的那个,而尾元素则是最短的那个。


/**
 * ArrayBlockingQueue/LinkedBlockingQueue 示例
 * Created by lyyz on 18-5-9.
 */
public class ArrayLinkedBlockQueue_eg {

    public static void main(String[] args) throws InterruptedException {
        ShouFeiZhan shouFeiZhan = new ShouFeiZhan(true);
        Thread shouFeiZhanThread = new Thread(shouFeiZhan);
        shouFeiZhanThread.start();

        for(int i=1;i<=20;i++){
            Thread.sleep(100);
            shouFeiZhan.gotoShouFeiZhan(new Car(i+"","大众"));
        }

    }
}

class ShouFeiZhan implements Runnable{
    private BlockingQueue<Car> blockingQueue;
    private boolean jieJiaRi = false;

    public ShouFeiZhan(boolean jieJiaRi) {
        this.jieJiaRi = jieJiaRi;
        //节假日 不收费 使用无界队列
        if(jieJiaRi)
            blockingQueue = new LinkedBlockingDeque<Car>();
        //非节假日 收费 使用有界队列 收费站最多能容纳5辆车
        else
            blockingQueue= new ArrayBlockingQueue<Car>(5);
    }
    //车辆要通过收费站 阻塞的项队列里添加元素
    public void gotoShouFeiZhan(Car car){
        try {
            blockingQueue.put(car);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    @Override
    public void run() {
        try {
            System.out.println("收费站运行了-------------");
            //收费站运行状态 1秒钟能通过一个车辆  阻塞的项队列里拿去元素
            while (true){
                Thread.sleep(1000);
                System.out.println(blockingQueue.size());
                Car car = blockingQueue.take();
                car.byebye();
            }


        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class Car{
    private String id;
    private String type;

    public Car(String id, String type) {
        this.id = id;
        this.type = type;
    }
    public void byebye(){
        System.out.println(type+"汽车"+id+"    通过收费站!");
    }
}

3.3 PriorityBlockingQueue(优先级的阻塞队列)

PriorityBlockingQueue 是一个无界的并发队列。它使用了和类 java.util.PriorityQueue 一样的排序规则。你无法向这个队列中插入 null 值。
所有插入到 PriorityBlockingQueue 的元素必须实现 java.lang.Comparable 接口。因此该队列中元素的排序就取决于你自己的 Comparable 实现。
注意 PriorityBlockingQueue 对于具有相等优先级(compare() == 0)的元素并不强制任何特定行为。
同时注意,如果你从一个 PriorityBlockingQueue 获得一个 Iterator 的话,该 Iterator 并不能保证它对元素的遍历是以优先级为序的。

/**
 * PriorityBlockingQueue 有序队列 
 * 添加元素时候不排序
 * 当调用take取元素时候 调用 dequeue() 方法对内部数组进行排序
 * Created by lyyz on 2018/5/11.
 */
public class PriorityBlockingQueue_eg {
    public static void main(String[] args) throws InterruptedException {
        PriorityBlockingQueue<Student> priorityBlockingQueue = new PriorityBlockingQueue<Student>();
        priorityBlockingQueue.put(new Student("zhangsan",3));
        priorityBlockingQueue.put(new Student("wangwu",5));
        priorityBlockingQueue.put(new Student("lisi",4));
        System.out.println(priorityBlockingQueue);
        System.out.println(priorityBlockingQueue.take().toString());
        System.out.println(priorityBlockingQueue);
    }
}

class Student implements Comparable<Student>{
    String name;
    int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return this.name+":"+this.age;
    }

    @Override
    public int compareTo(Student student) {
        return this.age > student.age ? 1 : (this.age < student.age ? -1 : 0);
    }
}

3.4 DelayQueue( 延迟队列)

DelayQueue 对元素进行持有直到一个特定的延迟到期。注入其中的元素必须实现 java.util.concurrent.Delayed 接口。

/**
 * 延迟队列
 * 交通工具通过1000km路程分别需要的时间
 * Created by lyyz on 2018/5/11.
 */
public class DelayQueue_eg {
    public static void main(String[] args) throws InterruptedException {
        DelayQueue<Vehicle> delayQueue = new DelayQueue<Vehicle>();
        delayQueue.put(new Vehicle("奥迪",100,1000));
        delayQueue.put(new Vehicle("法拉利",250,1000));
        delayQueue.put(new Vehicle("驴车",50,1000));


        while (true){
            System.out.println(delayQueue.take().toString());
        }
    }
}

class Vehicle implements Delayed{
    private String type;
    private long speed;
    private long kilometre;
    private long endTime;
    //定义时间工具类
    private TimeUnit timeUnit = TimeUnit.SECONDS;
    public Vehicle(String type, long speed, long kilometre) {
        this.type = type;
        this.speed = speed;
        this.kilometre = kilometre;
        this.endTime = (kilometre/speed)*1000+System.currentTimeMillis();
    }

    @Override
    public String toString() {
        return this.type+"行驶 "+kilometre+"千米 速度 "+speed;
    }
    //getDelay() 方法返回的值的时间段之后才释放掉该元素。如果返回的是 0 或者负值,延迟将被认为过期,该元素将会在 DelayQueue 的下一次 take  被调用的时候被释放掉。
    @Override
    public long getDelay(TimeUnit unit) {

        return endTime-System.currentTimeMillis();
    }
    //Delayed 接口也继承了 java.lang.Comparable 接口,这也就意味着 Delayed 对象之间可以进行对比。这个可能在对 DelayQueue 队列中的元素进行排序时有用,因此它们可以根据过期时间进行有序释放。
    @Override
    public int compareTo(Delayed o) {
        return this.getDelay(this.timeUnit) - o.getDelay(this.timeUnit) > 0 ? 1:0;
    }

3.5 SynchronousQueue(同步队列)

SynchronousQueue 是一种没有缓冲的队列,生产者生产的数据会直接被消费者获取并消费。
SynchronousQueue最大的特点就是put/take是成对调用的:
先调put,线程会阻塞在那;直到另外一个线程调用了take,2个线程才同时解锁。反之亦然。
SynchronousQueue 构造方法有两种模式 公平模式(队列模式) 和 非公平模式(栈模式)。
队列模式 :先进先出
栈模式:后进先出

/**
 * SynchronousQueue 同步阻塞队列
 *
 * Created by lyyz on 2018/5/11.
 */
public class SynchronousQueue_eg {

    public static void main(String[] args) throws InterruptedException {
        // transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
        SynchronousQueue<String> synchronousQueue = new SynchronousQueue<String>(true);
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    synchronousQueue.put("t1");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        },"t1");
        t1.start();
        Thread.sleep(10);
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    synchronousQueue.put("t2");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        },"t2");
        t2.start();
        Thread.sleep(10);
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println(synchronousQueue.take());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        },"t3");
        t3.start();
    }
}

文章来自:
https://my.oschina.net/hosee/blog/675884
https://blog.csdn.net/defonds/article/details/44021605/

猜你喜欢

转载自blog.csdn.net/oyueyang1/article/details/80268460
今日推荐