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:链表结构组成的双向阻塞队列。
可以在队列两端插入和移除。