u12-同步容器

257 阅读5分钟

1. 同步容器List

1.1 Vector

概念: Vector类称为向量类或矢量类,可以实现可扩容的数组,支持使用索引进行访问,Vector的容量可以根据需要增大或缩小。

  • 构造:可以在构造向量的时候指定初始容量和默认增量。
    • new Vector():默认初始容量为10,默认增量为0。
    • new Vector(10):指定初始容量为,默认增量为0。
    • new Vector(10, 5):指定初始容量和满容后的增量。
    • 增量越小,内存管理的效率越高,但执行开销越大,因为执行内存分配的次数将越多。
    • 增量越大,执行内存分配的次数将越少,但如果没有用完分配的所有空间,将浪费内存。
  • 方法:
    • boolean add(E e):尾部追加一个元素。
    • void add(int index, E element):在index位置插入一个元素。
    • E set(int index, E element):将index位置上的元素修改为E。
    • int capacity():返回向量的最大容量。
    • int size():返回向量中当前元素的个数。
    • boolean contains(Object o):返回向量中是否包含元素o。
    • int indexOf(Object o):返回向量中元素o所在的位置,不存在返回-1。
    • boolean removeElement(Object obj):删除向量中指定的元素。
    • E remove(int index):返回并删除向量中index位置上的元素。
    • void clear():删除向量中所有的元素。

源码: /javase-advanced/

  • src: c.y.thread.collection.SynchronizedListTest.vectorByDebug()
/**
 * @author yap
 */
public class SynchronizedListTest {

    @Test
    public void vectorByDebug() {
        Vector<String> vector = new Vector<>();
        System.out.println(vector.add("zhao-si"));
        vector.add(0, "liu-neng");
        vector.add(1, "da-jiao");
        System.out.println(vector.set(1, "chang-gui"));
        System.out.println(vector.get(1));
        System.out.println(vector.capacity());
        System.out.println(vector.size());
        System.out.println(vector.contains("da-jiao"));
        System.out.println(vector.indexOf("chang-gui"));
        System.out.println(vector.firstElement());
        System.out.println(vector.lastElement());
        System.out.println(vector.removeElement("liu-neng"));
        System.out.println(vector.remove(0));
        vector.clear();
    }
}

1.2 SynchronizedList

概念: Collections中提供了一个方法可以将异步的List容器转换成一个同步的List容器:

  • 方法:static <T> List<T> synchronizedList(List<T> list)

源码: /javase-advanced/

  • src: c.y.thread.collection.SynchronizedListTest.synchronizedList()
/**
 * @author yap
 */
public class SynchronizedListTest {
    @Test
    public void synchronizedList() {
        ArrayList<String> arrayList = new ArrayList<>();
        List<String> list = Collections.synchronizedList(arrayList);
        list.add("zhao-si");
        System.out.println(list.get(0));
    }
}

1.3 CopyOnWriteArrayList

概念: CopyOnWriteArrayList的特点是写时进行加锁复制,读时不加锁,适用于读线程远远多于写线程的情景。

源码: /javase-advanced/

  • src: c.y.thread.collection.SynchronizedListTest.copyOnWriteArrayList()
/**
 * @author yap
 */
public class SynchronizedListTest {

    @Test
    public void copyOnWriteArrayList() {
        List<String> list = new CopyOnWriteArrayList<>();
        list.add("zhao-si");
        System.out.println(list.get(0));
    }
}

2. 同步容器Map

2.1 Hashtable

概念: Hashtable中的大部分方法都是线程同步的,而且不支持null值,其余和HashMap的使用相似。

源码: /javase-advanced/

  • src: c.y.thread.collection.SynchronizedMapTest.hashtable()
/**
 * @author yap
 */
public class SynchronizedMapTest {

    @Test
    public void hashtable() {
        Hashtable<String, Object> hashtable = new Hashtable<>();
        hashtable.put("name", "zhao-si");
        hashtable.put("gender", "male");
        hashtable.put("age", 18);
        hashtable.forEach((key, value) -> {
            System.out.println(value);
        });
        System.out.println(hashtable.size());
    }
}

2.2 SynchronizedHashMap

概念: Collections中提供了一个方法可以将异步的Map容器转换成一个同步的Map容器:

  • 方法:static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)

源码: /javase-advanced/

  • src: c.y.thread.collection.SynchronizedMapTest.synchronizedHashMap()
/**
 * @author yap
 */
public class SynchronizedMapTest {

    @Test
    public void synchronizedHashMap() {
        HashMap<String, Object> hashMap = new HashMap<>(3);
        Map<String, Object> map = Collections.synchronizedMap(hashMap);
        map.put("name", "zhao-si");
        map.forEach((k, v) -> {
            System.out.println(v);
        });
    }

}

2.3 ConcurrentHashMap

概念: ConcurrentHashMap采用分段锁的技术,适用于并发量比较高的情况。

  • ConcurrentHashMap底层使用的是Node数组 + 链表 + 红黑树的结构完成的。
  • ConcurrentHashMap在添加元素的时候,会根据key值的hash值决定放到Node数组的哪个位置,取出元素的时候也会在相应的位置上取出值。
    • key值的hash值相同的时候,会在Node数组的某位置上形成链表。
    • Node数组的某位置上的链表过长时(大于8),会被转换成红黑树结构。
  • put(K, V):
    • 计算key的哈希值,并得到对应数组的索引i。
    • 如果table为空,初始化table(Node数组)。
    • 判断是否发生hash冲突:
      • 如果不冲突:直接创建新节点,并CAS插入数组对应位置。
      • 如果冲突且hash值为MOVED,则使用多线程对该链进行扩容(提高效率),并返回扩容后的table。
      • 否则加锁,将新节点插入到数组对应位置上的链表或红黑树的尾部。
    • 计数器自增1,有可能出发扩容操作。

源码: /javase-advanced/

  • src: c.y.thread.collection.SynchronizedMapTest.concurrentHashMap()
/**
 * @author yap
 */
public class SynchronizedMapTest {

    @Test
    public void concurrentHashMap() {
        Map<Object, Object> map = new ConcurrentHashMap<>(3);
        map.put("name", "zhao-si");
        map.forEach((k, v) -> {
            System.out.println(v);
        });
    }
}

2.4 ConcurrentSkipListMap

概念: ConcurrentSkipListMap底层使用的是跳表数据结构,在高并发情况下可以更快速的找到元素,且它可以排序,用于替代concurrentTreeMap(不存在,因为树结构的CAS太复杂)。

源码: /javase-advanced/

  • src: c.y.thread.collection.SynchronizedMapTest.concurrentSkipListMap()
/**
 * @author yap
 */
public class SynchronizedMapTest {

    @Test
    public void concurrentSkipListMap() {
        Map<Object, Object> map = new ConcurrentSkipListMap<>();
        map.put("name", "zhao-si");
        map.forEach((k, v) -> {
            System.out.println(v);
        });
    }
}

3. 同步容器Queue

3.1 ConcurrentLinkedQueue

概念: ConcurrentLinkedQueue底层使用的是CAS操作,高并发时效率比较高。

源码: /javase-advanced/

  • src: c.y.thread.collection.SynchronizedQueueTest.concurrentLinkedQueue()
    @Test
    public void concurrentLinkedQueue() {
        Queue<String> queue = new ConcurrentLinkedQueue<>();
        queue.add("zhao-si");
        System.out.println(queue.poll());
    }

3.2 LinkedBlockingQueue

概念: LinkedBlockingQueue是一个由单链表结构构成的单向阻塞队列,LinkedBlockingQueue是读写分离的,添加和读取元素分别有各自的锁。

  • 构造:
    • LinkedBlockingQueue():默认队列容量为Integer.MAX_VALUE。
      • 尽量不要使用这种方式,因为容易导致队列还没满,内存已经满了。
    • LinkedBlockingQueue(int capacity):指定队列容量,推荐指定。
  • 方法:
    • E take():获取元素,当队列为空时阻塞。
    • void put(E e):添加元素,当队列满时阻塞。
    • boolean isEmpty():返回队列是否为空。

源码: /javase-advanced/

  • src: c.y.thread.collection.SynchronizedQueueTest.linkedBlockingQueue()
 @Test
    public void linkedBlockingQueue() {
        BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(5);
        System.out.println(queue.isEmpty());
        new Thread(() -> {
            try {
                while (true) {
                    TimeUnit.SECONDS.sleep(2L);
                    int data = new Random().nextInt(100);
                    queue.put(data);
                    System.out.println("produce: " + data);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "producer").start();

        for (int i = 0, j = 5; i < j; i++) {
            new Thread(() -> {
                while (true) {
                    try {
                        System.out.println(Thread.currentThread().getName() + " : " + queue.take());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "consumer-" + i).start();
        }
    }

3.3 LinkedBlockingDeque

概念: LinkedBlockingDeque是由一个双链表结构构成的双向阻塞队列,因为多了一个操作队列的入口,所以在多线程同时入队时,减少了一半的竞争。

  • 构造:
    • LinkedBlockingDeque():默认队列容量为Integer.MAX_VALUE。
      • 尽量不要使用这种方式,因为容易导致队列还没满,内存已经满了。
    • LinkedBlockingDeque(int capacity):指定队列容量,推荐指定。
  • 方法:LinkedBlockingDeque相对于其他阻塞队列,多出了一些首尾相关方法,比如:
    • void addFirst(E e):头部添加元素。
    • void addLast(E e):尾部添加元素。
    • E peekFirst():查看但不移除头。
    • E peekLast():查看但不移除尾。

源码: /javase-advanced/

  • src: c.y.thread.collection.SynchronizedQueueTest.linkedBlockingDeque()
    @Test
    public void linkedBlockingDeque() {
        LinkedBlockingDeque<String> queue = new LinkedBlockingDeque<>();
        queue.addFirst("zhao-si");
        queue.addLast("liu-neng");
        System.out.println(queue.peekFirst());
        System.out.println(queue.peekLast());
        System.out.println(queue);
    }

3.4 ArrayBlockingQueue

概念: ArrayBlockingQueue底层是数组实现的。

  • 构造:ArrayBlockingQueue(int capacity):必须指定初始容量,且这个容量值永远不变。
  • 方法:
    • E take():获取元素,当队列为空时阻塞。
    • void put(E e):添加元素,当队列满时阻塞。

源码: /javase-advanced/

  • src: c.y.thread.collection.SynchronizedQueueTest.arrayBlockingQueue()
  @SneakyThrows
    @Test
    public void arrayBlockingQueue() {
        BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
        for (int i = 0, j = 10; i < j; i++) {
            queue.put(i);
        }
        System.out.println(queue.size());

        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(3L);
                System.out.println(queue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        // blocking and wait for consumer
        queue.put(250);
        System.out.println("over...");
    }

3.5 DelayQueue

概念: DelayQueue可以按照延迟时间来进行任务调度,声明的时候需要泛型指定一个实现了Delayed接口的任务类,并重写:

  • long getDelay(TimeUnit unit):负责根据时间单位来获取延迟的值。
  • int compareTo(Delayed o):负责根据延迟的值来进行比较和排序。

源码: /javase-advanced/

  • src: c.y.thread.collection.SynchronizedQueueTest.delayQueue()
/**
 * @author yap
 */
public class SynchronizedQueueTest {

    private static class MyTask implements Delayed {

        private String taskName;
        private Long timestamp;

        MyTask(String taskName, Long timestamp) {
            this.taskName = taskName;
            this.timestamp = timestamp;
        }

        @Override
        public long getDelay(TimeUnit unit) {
            return unit.convert(timestamp - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
        }

        @Override
        public int compareTo(Delayed o) {
            return Long.compare(getDelay(TimeUnit.MILLISECONDS), o.getDelay(TimeUnit.MILLISECONDS));
        }

        @Override
        public String toString() {
            return taskName + ": " + timestamp;
        }
    }

    @SneakyThrows
    @Test
    public void delayQueue() {
        BlockingQueue<MyTask> queue = new DelayQueue<>();
        long now = System.currentTimeMillis();
        queue.put(new MyTask("task1", now + 3));
        queue.put(new MyTask("task4", now + 1));
        queue.put(new MyTask("task5", now + 2));
        for (int i = 0, j = queue.size(); i < j; i++) {
            System.out.println(queue.take());
        }
    }

    @SneakyThrows
    @After
    public void after() {
        System.out.println(System.in.read());
    }
}

3.6 SynchronousQueue

概念: SynchronousQueue的初始容量为0,它不是用来装数据的,是用来向其他线程传递数据的。

  • 构造:
    • SynchronousQueue():初始容量为0。
    • SynchronousQueue(boolean fair):指定公平/非公平模式。
  • 方法:
    • E take():获取元素,当队列为空时阻塞,什么时候有人put值,它什么时候take到值。
    • void put(E e):添加元素,当队列满时阻塞。
  • SynchronousQueue不能调用 add(),直接报错。

源码: /javase-advanced/

  • src: c.y.thread.collection.SynchronizedQueueTest.synchronousQueue()
 @SneakyThrows
    @Test
    public void synchronousQueue() {
        BlockingQueue<String> queue = new SynchronousQueue<>();
        new Thread(() -> {
            try {
                System.out.println(queue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        TimeUnit.SECONDS.sleep(2L);
        queue.put("zhao-si");
        System.out.println("size: " + queue.size());
    }

3.7 LinkedTransferQueue

概念: LinkedTransferQueue是一个由链表构成的无界队列,采用预占位模式。

  • 预占位模式下的 take()
    • 如果队列不为空,直接take走数据。
    • 如果队列为空,生成一个null值节点入队,消费者在这个节点上等待。
    • 当生产者生产数据并入队时,发现队列中存在一个null值节点,则将数据直接填充到这个节点中。
    • 唤醒该节点上的消费者,消费者取走数据,从调用的方法返回。
  • 方法:
    • void transfer(E e):向队列传递数据并阻塞,直到有人将数据take()走,它才继续运行。
    • void put(E e):向队列传递数据但不阻塞,继续向下运行。

源码: /javase-advanced/

  • src: c.y.thread.collection.SynchronizedQueueTest.linkedTransferQueue()
  @Test
    public void linkedTransferQueue() {
        TransferQueue<String> queue = new LinkedTransferQueue<>();
        new Thread(() -> {
            try {
                System.out.println("consumer start...");
                TimeUnit.SECONDS.sleep(2L);
                System.out.println(queue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "consumer").start();

        new Thread(() -> {
            try {
                System.out.println("producer start...");
                queue.transfer("zhao-si");
                // queue.put("zhao-si");
                System.out.println("producer继续运行...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "producer").start();
    }