Java 多线程 常见并发队列

Java并发容器

并发容器概览

  • ConcurrentHashMap:线程安全的HashMap
  • CopyOnWriteArrayList:线程安全的List
  • BlockingQueue:这是一个接口,表示阻塞队列,非常适合用作数据共享的通道
  • ConcurrentLinkedQueue:高效的非阻塞并发队列,使用链表实现。可以看做一个线程安全的LinkedList。

集合类的历史

Vector和Hashtable

Vector中方法均被synchronized修饰,所以并发性能很差
Hashtable可以理解为线程安全的HashMap,很多方法也是被synchronized修饰的,所以并发性能也很差。

ArrayList和HashMap

这两个类是线程不安全的,可以使用Collections.synchronizedList(new ArrayList())和Collections.synchronizedMap(new HashMap<K,V>())使之变成线程安全的。这种策略和Vector和Hashtable几乎相同

ConcurrentHashMap和CopyOnWriteArrayList

  • 取代同步的HashMap和同步的ArrayList
  • 绝大多数情况下,ConcurrentHashMap和CopyOnWriteArrayList的性能都更好。

ConcurrentHashMap(重点)

HashMap

为什么HashMap是线程不安全的

  • 同时put碰撞导致数据丢失
  • 同时put扩容导致数据丢失
  • 死循环造成CPU100%

HashMap并发的特点

  1. 非线程安全
  2. 迭代时不允许修改内容
  3. 只读的并发是安全的
  4. 若一定要把HashMap用在并发环境,使用Collection.synchronizedMap(new HashMap()).

ConcurrentHashMap

在JDK1.7中的ConcurrentHashMap最外层是多个segment,每个segment的底层数据结构与HashMap类似,仍然是数组和链表组成的拉链法,每个segment独立上ReentrantLock锁,每个segment之间互不影响,提高了并发效率。
在JDK1.8中借鉴了HashMap的设计,采用拉链法。拉链由红黑树和链表共同组成。

源码分析

//put方法,和HashMap的put方法基本相同,使用synchronized+cas实现线程安全
final V putVal(K key, V value,boolean onlyIfAbsent){
    if(key == null || value == null) throw new NullPointerException();

}

ConcurrentHashMap在JDK1.7与8中的区别

在这里插入图片描述

在这里插入图片描述

https://www.jianshu.com/p/e694f1e868ec
https://www.cnblogs.com/lezon1995/p/11219555.html

concurrentHashMap的replace方法

public class OptionsNotSafe implements Runnable{
    private static ConcurrentHashMap<String,Integer> scores = new ConcurrentHashMap<String,Integer>();
    public static void main(String[] args) throws InterruptedException{
        scores.put("小明",0);
        Thread t1 = new Thread(new OptionsNotSafe());
        Thread t2 = new Thread(new OptionsNotSafe());
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(scores);
    }

    @Override
    public void run(){
        for(int i = 0; i < 1000;i++){
            while(true){
                Integer score = scores.get("小明");
                Integer newScore = score+1;
                boolean b = scores.replace("小明",score,newScore);
                if(b == true)
                    break;
            }
        }
    }
}

CopyOnWriteArrayList

  1. 诞生的历史原因
  2. 适用场景
  3. 读写规则
  4. 实现原理
  5. 缺点
  6. 源码分析

主要用于代替Vector和SynchronizedList,就和ConcurrentHashMap代替SynchronizedMap的原因一样,Vector和SynchronizedList的锁的粒度太大,并发效率相对较低,并且迭代时无法编辑。CopyOnWrite并发容器还包括CopyOnWriteArraySet,用来代替同步Set。
读操作可以尽可能快,而写即使慢一点也没关系。

CopyOnWriteArrayList使用场景

读多写少:黑名单,每日更新:监听器:迭代操作远多于修改操作。

CopyOnWriteArrayList读写规则

读写锁中,读读共享,其他互斥

CopyOnWriteArrayList读写规则的升级:读取时完全不加锁,并且更厉害的时,写入也不会阻塞读取操作,只有写入和写入之间需要进行同步等待。

public class CopyOnWriteArrayListDemo {
    public static void main(String[] args) {
        //ArrayList<String> list = new ArrayList<>();
        CopyOnWriteArrayList list = new CopyOnWriteArrayList();
        list.add("1");
        list.add("2");
        list.add("3");
        list.add("4");
        list.add("5");
        Iterator<String> iterator = list.iterator();
        while(iterator.hasNext()){
            System.out.println("list is" + list);
            String next = iterator.next();
            System.out.println(next);
            if(next.equals("2")){
                list.remove("5");
            }
        }
    }
}

CopyOnWriteArrayList实现原理

不对内存直接进行修改,把原始内容放到新的内容中拷贝出来,在新的地方添加删除,在新的地方写入,在原始数据删除。
创建新副本,读写分离。
"不可变"原理
迭代的时候

public class CopyOnWriteArraListDemo2 {
    public static void main(String[] args) {
        CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>(new Integer[]{1,2,3});
        System.out.println(list);
        Iterator<Integer> itr1 = list.iterator();
        list.add(4);
        System.out.println(list);
        Iterator<Integer> itr2 = list.iterator();
        itr1.forEachRemaining(System.out::println);
        itr2.forEachRemaining(System.out::println);
    }
}

CopyOnWriteArrayList缺点

数据一致性问题:CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的数据,马上就能读到,就不要适用CopyOnWrite容器。
内存占用问题,因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个内存的占用。

public class CopyOnWriteArraListDemo2 {
    public static void main(String[] args) {
        CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>(new Integer[]{1,2,3});
        System.out.println(list);
        Iterator<Integer> itr1 = list.iterator();
        list.add(4);
        System.out.println(list);
        Iterator<Integer> itr2 = list.iterator();
        itr1.forEachRemaining(System.out::println);
        itr2.forEachRemaining(System.out::println);
    }
}

CopyOnWriteArrayList源码分析

public boolean add(E e) {
    synchronized (lock) {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    }
}
public E get(int index) {
    return elementAt(getArray(), index);
}
static <E> E elementAt(Object[] a, int index) {
    return (E) a[index];
}

并发队列

  1. 为什么要用队列
  2. 并发队列简介
  3. 各并发队列关系图
  4. 阻塞队列BlockingQueue
  5. 非阻塞队列
  6. 如何选择适合自己的队列

为什么要适用队列

  • 队列可以在线程间传递数据:生产者消费者模式,银行转账
  • 考虑锁等线程安全问题的重任从你转移到了队列上

并发队列简介

BlockingQueue

  • put,take
  • add,remove,element
  • offer,pool,peek

ArrayBlockingQueue

使用案例,生产者消费者模式

public class ArrayBlockQueueDemo {
    static BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
    public static void main(String[] args) {
        Producer r1 = new Producer(queue);
        Consumer r2 = new Consumer(queue);
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r2);
        t1.start();
        t2.start();
    }
}
class Producer implements Runnable{
    BlockingQueue<String> queue;
    public Producer(BlockingQueue blockingQueue){
        this.queue = blockingQueue;
    }
    @Override
    public void run() {
        for(int i = 0; i < 10; i++){
            System.out.println("我生成了"+ i);
            queue.offer("Consumer"+i);

        }
        queue.offer("stop");
    }
}
class Consumer implements Runnable{
    BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
    public Consumer(BlockingQueue blockingQueue){
        this.queue = blockingQueue;
    }
    @Override
    public void run() {
        String msg="";
        while(!msg.equals("stop")){
            try {
                msg = queue.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(msg);
        }
    }
}

BlockQueue有add,offer,put3个方法,下面讲述3个方法的不同

add方法调用offer方法。

public boolean offer(E e) {
    //检查元素不为null
    Objects.requireNonNull(e);
    //加锁,独占锁保护静态资源
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        //队列已满,返回false
        if (count == items.length)
            return false;
        else {
            //插入元素返回true
            enqueue(e);
            return true;
        }
    } finally {
        //释放锁
        lock.unlock();
    }
}
public void put(E e) throws InterruptedException {
    //同上
    Objects.requireNonNull(e);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == items.length)
        //当队列满是,陷入阻塞。
            notFull.await();
        enqueue(e);
    } finally {
        lock.unlock();
    }
}

弹出元素有两个

public E poll() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        return (count == 0) ? null : dequeue();
    } finally {
        lock.unlock();
    }
}
public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == 0)
            notEmpty.await();
        return dequeue();
    } finally {
        lock.unlock();
    }
}

LinkedBlockingQueue

容量是无界的,容量时Integer.MAX_VALUE,Node是链表中的节点

public void put(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    int c = -1;
    Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    putLock.lockInterruptibly();
    try {
        while (count.get() == capacity) {
            notFull.await();
        }
        enqueue(node);
        c = count.getAndIncrement();
        if (c + 1 < capacity)
            notFull.signal();
    } finally {
        putLock.unlock();
    }
    if (c == 0)
        signalNotEmpty();
}
public boolean offer(E e) {
    if (e == null) throw new NullPointerException();
    final AtomicInteger count = this.count;
    if (count.get() == capacity)
        return false;
    int c = -1;
    Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    putLock.lock();
    try {
        if (count.get() < capacity) {
            enqueue(node);
            c = count.getAndIncrement();
            if (c + 1 < capacity)
            //用于唤醒一个被wait的线程
                notFull.signal();
        }
    } finally {
        putLock.unlock();
    }
    if (c == 0)
        signalNotEmpty();
    return c >= 0;
}

PriorityBlockQueue

支持优先级,自然顺序,而不是先进先出。PriorityQueue是无界的

SynchronousQueue

  • SynchronousQueue没有peek等函数,因为peek的含义是取出头节点,但是SynchronousQueue的容量是0,所以连头节点都没有,也就没有peek方法。同理,没有iterate相关方法。是一个极好的用来直接传递的并发数据结构。
  • SynchronousQueue是线程池Executors.newCachedThreadPool()使用的阻塞队列。

DelayQueue

非阻塞并发队列

ConcurrentLinkedQueue

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值