java并发容器

为什么要使用ConcurrentHashMap?

  在多线程环境下,会用HashMap进行put操作会引起死循环,导致CPU利用率接近100%。因为多线程会导致HashMap的Entry链表形成环形数据结构,一旦形成环形数据结构,Entry的next节点用不为空,就会产生死循环获取Entry。

  HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低。因为当一个线程访问HashTable的同步方法,其他线程也访问HashTable的同步方法时,会进入阻塞或轮询状态。如线程1使用put进行元素添加,线程2不但不能使用put方法添加元素,也不能使用get方法来获取元素,所以竞争越激烈效率越低。

 

ConcurrentHashMap分析

  Hash是一种散列,数据结构是以数组+链表的结构组成,输入元素通过固定算法分配到相应链表上,在ConcurrentHashMap中使用的是wang/jenkins算法分配元素。

  在JDK1.7中使用的是分段锁的设计思想。该思想是由Segment数据结构额+HashEntry数据结构组成。Segment实际是一种可重入锁(ReentrantLock),HashEntry则用于存储键值对数据。当写入数据时,首先获取Segment的锁,这样才能操作相应的hash,而读取则不需要加锁,这样保证线程安全性。

  在JDK1.8中取消了Segments字段,直接采用transient volatile HashEntry<K,V>[] table保存数据,采用table数组元素作为锁,从而实现对每一行数据进行加锁,进而减少并发冲突的概率。将原先table数组+单向链表的数据结构,变为table数组+单向链表+红黑树的结构。默认链表为8,超过后则变为红黑树结构,保证在读写时提高效率。

 

常用方法 

V putIfAbsent(K key, V value);

如果key对应的value不存在,则put进去,返回null;否则什么都不做,返回value值。

boolean remove(Object key, Object value);

如果key对应的value存在,则移除K-V,返回true;否则不移除,返回false。

boolean replace(K key, V oldValue, V newValue);

如果key对应的当前是oldValue,则替换为newValue,返回true;否则不替换,返回false。

 

ConcurrentSkipListMap和ConcurrentSkipListSet

前者是TreeMap的并发实现,后者是TreeSet的并发实现

 

ConcurrentLinkedQueue

无界非阻塞队列,LinkedList并发实现

常用方法

add/offer:添加元素

peek:get头元素并不把元素拿走

poll:get头元素把元素拿走

 

CopyOnWriteArrayList和CopyOnWriteArrayList

写的时候进行复制,可以并发的读。

 

常用阻塞队列

ArrayBlockingQueue:数组结构组成有界阻塞队列。

先进先出原则,初始化必须传大小,take和put时候用的同一把锁

LinkedBlockingQueue:链表结构组成的无界阻塞队列。

先进先出原则,初始化可以不传大小,put、take锁分离

PriorityBlockingQueue:支持优先级排序的无界阻塞队列。

排序,自然顺序为升序,改变顺序实现compareTo方法,初始化时指定一个比较器comparetor。

DelayQueue:延迟队列。

支持延时获取,队列里的元素要实现Delayed接口,可以在元素到期是进行回调

DelayQueue结合了PrioriyBlockingQueue,把时间间隔最短的放在前面,然后死循环验证元素是否到期,到期则调用回调方法。

应用场景:1、缓存设计,可以用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从DelayQueue中获取元素时,表示缓存有效期到了。2、订单到期。3、限时支付等。

public class User {
    private int id;
    private String name;
    
    public User(String name) {
        this.name = name;
    }
}

 

public class CacheBean<T> implements Delayed {
    private int id;
    private String name;
    private T data;
    private long activeTime;    //到期时间
    
    public CacheBean(int id, String name, T data, long activeTime) {
        this.id = id;
        this.name = name;
        this.data = data;
        this.activeTime = TimeUnit.NANOSECONDS.convert(activeTime, TimeUnit.MILLISECONDS) + System.nanoTime();
    }

    @Override
    public int compareTo(Delayed o) {
        long d = this.getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS);
        return (0 == d) ? 0 : (0 > d) ? -1 : 1;
    }

    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(this.activeTime - System.nanoTime(), TimeUnit.NANOSECONDS);
    }
}

 

public class PutInCache implements Runnable {

    private DelayQueue<CacheBean<User>> queue;
    
    public PutInCache(DelayQueue<CacheBean<User>> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        CacheBean cacheBean = new CacheBean(1, "5秒", new User("张三"), 5000);
        CacheBean cacheBean2 = new CacheBean(1, "3秒", new User("李四"), 3000);
        CacheBean cacheBean3 = new CacheBean(1, "10秒", new User("退出"), 10000);
        this.queue.offer(cacheBean);
        System.out.println("put in cache:" + cacheBean.getId() + ":" + cacheBean.getName());
        this.queue.offer(cacheBean2);
        System.out.println("put in cache:" + cacheBean2.getId() + ":" + cacheBean2.getName());
        this.queue.offer(cacheBean3);
    }

}

 

public class GetFromCache implements Runnable {

    private DelayQueue<CacheBean<User>> queue;
    
    public GetFromCache(DelayQueue<CacheBean<User>> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true) {
            try {
                CacheBean<User> item = queue.take();
                System.out.println("GetFromCache" + item.getId() + ":" + item.getData().getName());
                if ("退出".equals(item.getData().getName())) {
                    System.out.println("获取器退出");
                    break;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

 

public static void main(String[] args) throws InterruptedException {
  DelayQueue<CacheBean<User>> queue = new DelayQueue<CacheBean<User>>();
  new Thread(new PutInCache(queue)).start();
  new Thread(new GetFromCache(queue)).start();
        
  for (int i = 0; i < 22; i ++) {
    Thread.sleep(500);
    System.out.println(i * 500);
  }
}

 

输出:

put in cache:1:5秒
put in cache:1:3秒
0
500
1000
1500
2000
2500
GetFromCache1:李四
3000
3500
4000
GetFromCache1:张三
4500
5000
5500
6000
6500
7000
7500
8000
8500
9000
GetFromCache1:退出
获取器退出
9500
10000
10500

 

SynchronousQueue:不存储元素的阻塞队列。

每个put操作必须要等take操作。

LinkedTransferQueue:链表结构组成的无界阻塞队列。

LinkedBlockingDeque:链表结构组成的双向阻塞队列。

可以在队列两端插入和移除。

posted @ 2018-01-09 22:26  huanStephen  阅读(319)  评论(0编辑  收藏  举报