同步(并发)类容器都是线程安全的,但在某些场景下可能需要加锁来保护复合操作,如迭代(反复访问元素,遍历容器所有元素)、跳转(根据指定的顺序找到当前元素的下一个元素),以及条件运算。
这些复合操作在多线程并发的修改容器时,可能会表现出意外的行为,最典型的就是之前解析集合源码时,讲到的Fast-Fast机制,会抛出ConcurrentModificationException异常,这是早期迭代器设计的时候并没有考虑并发修改的问题。
早期的同步(并发)类容器,如已经弃用的Vector、Hashtable,这些容器的同步功能其实都是用JDK底层的Collections.synchronized*方法去创建实现的。底层的机制无非都是用传统的synchronized关键字对每个方法都进行同步,使得每次只能由一个线程访问容器的状态,状态都是串行化,虽然实现了线程安全,但是严重降低了并发性,在多线程环境时,严重降低了吞吐量。
这显然无法满足现在系统的高并发需求,在保证线程安全的同时,也必须有足够好的性能。
/** * 在之前的集合源码解析中,可以得知有fail-fast机制,这是早期设计时,没有考虑并发修改的问题。 * 所以就提供了Vector、Hashtable这些容器,底层都是用synchronized关键字对每个公用方法进行同步的。 */ public class C01Old { public static void main(String[] args) { Vector for (int i = 0; i < 100; i++) { //这个方法底层就是加了synchronized vector.add("Test " + i); }
//fail-fast机制(复合型操作) // for (Iterator iterator = vector.iterator(); iterator.hasNext();) { // Object next = iterator.next(); // vector.remove(10); // }
//不存在并发问题 for (int i = 0; i < 10; i++) { new Thread("Thread " + i) { @Override public void run() { while (true) { if (vector.isEmpty()) { break; } System.out.println(Thread.currentThread().getName() + "---" + vector.remove(0)); } } }.start(); }
//包装成线程安全 Map } } |
/** * Appends the specified element to the end of this Vector. * * @param e element to be appended to this Vector * @return {@code true} (as specified by {@link Collection#add}) * @since 1.2 */ public synchronized boolean add(E e) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; } |
在JDK1.5,提供了多种同步(并发)类容器是专门针对并发设计的,如ConcurrentHashMap替代基于散列的传统的Hashtable,而且ConcurrentHashMap中,添加了一些常用的复合操作支持。以及CopyOnWriteArayList代替Vector,和CopyWriteArraySet。还有并发的Queue(队列),ConcurrentLinkedQueue、LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue等。
CopyOnWrite同步(并发)容器
/** * CopyOnWrite容器:这是一种用于程序设计中的优化策略。 * CopyOnWrite容器有两种:CopyOnWriteArrayList和CopyOnWriteArraySet。 * * CopyOnWrite容器是即写时复制的容器,当往容器添加元素时,不是直接往容器添加的, * 而是先将当前容器进行Copy,复制出一个新的容器,然后往新的容器内添加元素,添加完元素之后,在将原容器的引用指向新的容器。 * 这样做的好处是我们可以对CopyConWrite容器进行并发的读,而不需要加锁。 * 这是因为当前容器不会添加任何容器,所以CopyOnWrite容器也是一种读写分离的思想,读和写是不同的容器。 * 写时,是复制出来新的容器,在新容器内操作,而旧容器依然还是可以多线程的并发读,在执行完写的操作就把指针指向新的容器,旧容器就进行回收。 * 写都是加锁的,不会出现数据不一致的情况下,即读无锁,写有锁。 * 场景:读多写少。 */ public class C02CopyOnWrite { public static void main(String[] args) { //替代Vector CopyOnWriteArrayList copyOnWriteArrayList.add("123"); copyOnWriteArrayList.add("323"); copyOnWriteArrayList.add("123"); System.out.println(copyOnWriteArrayList);
//去重 CopyOnWriteArraySet copyOnWriteArraySet.add("333"); copyOnWriteArraySet.add("222"); copyOnWriteArraySet.add("333");
System.out.println(copyOnWriteArraySet); } } |
ConcurrentMap同步(并发)容器
/** * ConcurrentMap接口有两个重要的实现: * ConcurrentHashMap:替代Hashtable。 * ConcurrentSkipListMap:支持并发排序功能,弥补了ConcurrentHashMap的功能,类似TreeMap。 * * ConcurrentMap:内部使用段(segment)来表示这些不同的部分,每个段其实就是一个小的Hashtable,它们有自己的锁。 * 只要修改操作发生在不同的段上,就可以并发进行,就是把一个整体分为16个段(segment),即最高支持16个线程并发修改操作。 * 这也是多线程的优化,减少锁的粒度,从而降低锁竞争的一种方案,并且代码中大多共享变量使用volatile关键字声明,目的是第一时间可以获取修改的内容,从而性能也非常好。 * * 备注:ConcurrentSkipListMap是CopyOnWrite(类List和Set)的补充,支持排序功能,默认是升序(正序)。 */ public class C03ConcurrentMap { public static void main(String[] args) { //替代Hashtable ConcurrentHashMap concurrentHashMap.put("c1", 1); concurrentHashMap.put("c2", 2); concurrentHashMap.putIfAbsent("c3", 3);
System.out.println(concurrentHashMap.size()); System.out.println(concurrentHashMap.get("c3"));
for (Entry System.out.println(entry.getKey() + " - " + entry.getValue()); }
System.out.println("==========================");
//支持排序,默认是升序 ConcurrentSkipListMap concurrentSkipListMap.put(3, "b"); concurrentSkipListMap.put(6, "c"); concurrentSkipListMap.put(1, "a"); for (Entry System.out.println(entry.getKey() + " - " + entry.getValue()); }
ConcurrentSkipListSet concurrentSkipListSet.add(3); concurrentSkipListSet.add(1); concurrentSkipListSet.add(2); System.out.println(concurrentSkipListSet); } } |
Queue同步(并发)队列
/** * 在并发队列上,JDK提供了两套实现: * 一是ConcurrentLinkedQueue(非阻塞)为代表的高性能队列。 * 二是BlockingQueue(阻塞)为代表的队列。 * 无论哪一种队列都是继承自Queue。 * * 备注:无界队列是指没有设置固定大小的队列,特点是可以直接入列,直到溢出。有界队列是指有长度限制的,即固定大小的队列。 */ public class C04UseQueue { public static void main(String[] args) throws InterruptedException { //=============================== 非阻塞队列 =============================== /* * ConcurrentLinkedQueue(非阻塞、无界): * 是一种适用于高并发场景下的队列,通过无锁的方式,实现了高并发状态下的高性能, * 通常ConcurrentLinkedQueue性能浩宇BlockingQueue, * 它是一个基于链接节点的无界线程安全队列,该队列的元素遵循先进先出的原则。 * 头是最先加入的,尾是最近加入的,该队列不允许null元素存在,类似压栈。 */ ConcurrentLinkedQueue concurrentLinkedQueue.offer("1"); concurrentLinkedQueue.offer("2"); concurrentLinkedQueue.add("3");
System.out.println(concurrentLinkedQueue.size()); System.out.println(concurrentLinkedQueue.poll());//取出首个元素并从队列中删除 System.out.println(concurrentLinkedQueue.size()); System.out.println(concurrentLinkedQueue.peek());//取出首个元素,不从队列中移除。 System.out.println(concurrentLinkedQueue.size());
System.out.println("----------------------------------");
//=============================== 阻塞队列 =============================== /* * ArrayBlockingQueue(阻塞、有界):基于数组的阻塞队列实现的,在ArrayBlockingQueue内部维护了一个定长数组, * 以便缓存队列中的数据对象,其内部没实现读写分离,也意味着生产和消费不能完全并行,长度是需要定义的,可以指定先进先出或先进后出。 * 在很多场合下非常适合使用。 */ ArrayBlockingQueue arrayBlockingQueue.put("1"); //没有可用空间,会根据maxTime进行等待队列的位置 arrayBlockingQueue.add("2"); //没有可用空间,直接报错 boolean offer = arrayBlockingQueue.offer("3"); //有可用空间返回true,无可用空间返回false // boolean offer = arrayBlockingQueue.offer("3",3,TimeUnit.SECONDS); //等待N时后执行添加,有可用空间返回true,无可用空间返回false
System.out.println(offer); System.out.println(arrayBlockingQueue.size());
System.out.println("----------------------------------");
/* * LinkedBlockingQueue(阻塞、无界):基于链表的阻塞队列,和ArrayBlockingQueue类似,在其内部维持着一个数据缓冲队列(链表构成)。 * LinkedBlockingQueue之所以能够高性能的处理数据是因为其内部实现了分离锁(读写分离锁),从而实现生产者和消费者操作的完全并行运行。 */ LinkedBlockingQueue linkedBlockingQueue.offer("1"); linkedBlockingQueue.offer("2"); linkedBlockingQueue.add("3"); System.out.println(linkedBlockingQueue.size());
for (Iterator iterator = linkedBlockingQueue.iterator();iterator.hasNext();) { System.out.println(iterator.next()); }
System.out.println("----------------------------------");
//取出元素放到新的集合中 ArrayList System.out.println(linkedBlockingQueue.drainTo(arrayList, 2)); System.out.println(arrayList);
System.out.println("----------------------------------");
/* * SynchronousQueue(阻塞、无界):没有缓冲的队列,生产者产生的数据直接被消费者获取并消费。 */ SynchronousQueue
//获取元素 new Thread(new Runnable() { @Override public void run() { try { System.out.println(synchronousQueue.take()); } catch (InterruptedException e) { e.printStackTrace(); } } }).start();
//必须要有先有个线程获取元素,如果先add会报错 new Thread(new Runnable() { @Override public void run() { synchronousQueue.add("6"); } }).start();
System.out.println("----------------------------------");
/* * PriorityBlockingQueue(阻塞、无界):基于优先级的阻塞队列,优先级判断通过构造函数传入的Compator对象来决定, * 也就是说传入队列的对象必须实现Comaparable接口,在实现PriorityBlockingQueue时,内部控制线程同步的锁采用的是公平锁。 */ PriorityBlockingQueue priorityBlockingQueue.add(new Person("A", 1)); priorityBlockingQueue.add(new Person("C", 3)); priorityBlockingQueue.add(new Person("B", 2));
//可以看到是每次取元素时才进行排序,而不是每天add进去时就排序 System.out.println(priorityBlockingQueue); System.out.println(priorityBlockingQueue.take());
System.out.println("----------------------------------");
/* * LinkedBlockingDeque(阻塞、无界):链表结构组成的双向阻塞队列,可以从队列两端插入和移除, * 由于多了一个入口,在多线程同时入队时,也就减少了一半竞争。 */ LinkedBlockingDeque linkedBlockingDeque.addFirst("1"); linkedBlockingDeque.addFirst("2"); linkedBlockingDeque.addFirst("3"); linkedBlockingDeque.addLast("a"); linkedBlockingDeque.addLast("b"); linkedBlockingDeque.addLast("c"); System.out.println(linkedBlockingDeque.peekFirst()); System.out.println(linkedBlockingDeque.pollLast());
System.out.println("=========="); Object[] array = linkedBlockingDeque.toArray(); for (int i = 0; i < array.length; i++) { System.out.println(array[i]); } } }
class Person implements Comparable
private String name;
private Integer age;
public Person(String name, Integer age) { super(); this.name = name; this.age = age; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
@Override public int compareTo(Person o) { return this.age > o.age ? 1 : (this.age < o.age ? -1 : 0); }
@Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } } |
/** * DelayQueue(阻塞、无界):是带有延迟的Queue,其中元素只有其指定的延迟时间到了,才能够从队列中获取到该元素。 * DelayQueue中的元素必须实现Delayed接口,DelayedQueue是一个没有大小限制的队列。 * 应用场景很多,如对缓存超时的数据进行移除、任务超时处理、空闲链接的关闭等。 */ public class C05DelayQueue { public static void main(String[] args) { try{ System.out.println("开业..."); WangBa siyu = new WangBa(); Thread shangwang = new Thread(siyu); shangwang.start();
// siyu.shangji("大", 10); siyu.shangji("大", 1); siyu.shangji("中", 10); siyu.shangji("小", 5); } catch(Exception e){ e.printStackTrace(); } } }
class WangBa implements Runnable {
/** * 每一个上网的队列 */ private DelayQueue
public boolean yinye = true;
public void shangji(String name,int money) { long endTime = 1000 * money + System.currentTimeMillis(); long timeSize = endTime - System.currentTimeMillis(); WangMin wangMin = new WangMin(name,endTime); System.out.println(name + ":" + "交了" + money + "块钱,上" + timeSize / 1000 + "秒"); this.delayQueue.add(wangMin); }
public void xiaji(WangMin wangMin) { System.out.println(wangMin.getName() + ":下机了..."); }
@Override public void run() { while (yinye) { try { WangMin wangMin = delayQueue.take(); xiaji(wangMin); } catch (InterruptedException e) { e.printStackTrace(); } } } }
class WangMin implements Delayed {
private String name;
private long endTime;
private TimeUnit timeUnit = TimeUnit.SECONDS;
public WangMin(String name, long endTime) { super(); this.name = name; this.endTime = endTime; }
public String getName() { return name; }
/* * 判断是否到了截止时间 */ @Override public long getDelay(TimeUnit unit) { long timeSize = endTime - System.currentTimeMillis(); return timeSize; }
/* * 相互比较排序 */ @Override public int compareTo(Delayed o) { WangMin wangMin = (WangMin) o; return this.getDelay(this.timeUnit) - wangMin.getDelay(this.timeUnit) > 0 ? 1 : 0; } } |