并发容器

一、解释

1.1 早期

Vector 和 Hashtable

1.1.1 Vector

Vector synchronized修饰方法:

1.1.2 Hashtable

Hashtable synchronized修饰方法:

1.1.3 Collections.synchronizedList()方法

Collections.synchronizedList() 修饰对应的ArrayList、HashMap。

查看源码:

总之,是比历史的容器类优良点,使用的同步代码块。

1.2 如今

1.2.1 ConcurrentHashMap:线程安全的HashMap

1.2.1.1解释

这个是Map的结构图

下面是HashMap的put源码

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

可以从上面看出,使用的是Node类进行数据的保存。

查看ConCurrentHashMap 的put源码:

final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh; K fk; V fv;
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
                    break;                   // no lock when adding to empty bin
            }
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else if (onlyIfAbsent // check first node without acquiring lock
                     && fh == hash
                     && ((fk = f.key) == key || (fk != null && key.equals(fk)))
                     && (fv = f.val) != null)
                return fv;
            else {
                V oldVal = null;
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        if (fh >= 0) {
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                Node<K,V> pred = e;
                                if ((e = e.next) == null) {
                                    pred.next = new Node<K,V>(hash, key, value);
                                    break;
                                }
                            }
                        }
                        else if (f instanceof TreeBin) {
                            Node<K,V> p;
                            binCount = 2;
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                        else if (f instanceof ReservationNode)
                            throw new IllegalStateException("Recursive update");
                    }
                }
                if (binCount != 0) {
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }

从上面可以看出,使用了CAS 和 synchronized 实现了该put方法。 HashMap结构缺少这2个实现机制。

而查看JDK1.7版本源码:

final V put(K key, int hash, V value, boolean onlyIfAbsent) {
            HashEntry<K,V> node = tryLock() ? null :
                scanAndLockForPut(key, hash, value);
            V oldValue;
            try {
                HashEntry<K,V>[] tab = table;
                int index = (tab.length - 1) & hash;
                HashEntry<K,V> first = entryAt(tab, index);
                for (HashEntry<K,V> e = first;;) {
                    if (e != null) {
                        K k;
                        if ((k = e.key) == key ||
                            (e.hash == hash && key.equals(k))) {
                            oldValue = e.value;
                            if (!onlyIfAbsent) {
                                e.value = value;
                                ++modCount;
                            }
                            break;
                        }
                        e = e.next;
                    }
                    else {
                        if (node != null)
                            node.setNext(first);
                        else
                            node = new HashEntry<K,V>(hash, key, value, first);
                        int c = count + 1;
                        if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                            rehash(node);
                        else
                            setEntryAt(tab, index, node);
                        ++modCount;
                        count = c;
                        oldValue = null;
                        break;
                    }
                }
            } finally {
                unlock();
            }
            return oldValue;
        }

可以看出使用的是Segment,而Segment继承至 ReentrantLock。

1.2.2 对比

转红黑树需要预值为8,且容量大于64。

 

1.2. 3 注意点

组合操作有可能造成线程不安全,并不是ConCurrentHashMap不安全,而是过程,贴个代码:


public class OptionsNotSafe implements Runnable {

    private static ConcurrentHashMap<String, Integer> scores = new ConcurrentHashMap<>();

    public static void main(String[] args) throws InterruptedException {
        scores.put("key", 0);
        Thread thread = new Thread(new OptionsNotSafe());
        Thread thread1 = new Thread(new OptionsNotSafe());
        Thread thread2 = new Thread(new OptionsNotSafe());
        thread.start();
        thread1.start();
        thread2.start();
        thread.join();
        thread1.join();
        thread2.join();
        System.out.println(scores);
    }
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            Integer score = scores.get("key");
            int newScores = score + 1;
            scores.put("key", newScores);
        }
    }
}

运行结果:

解析:

替换方法 replace

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

 1.3 CopyOnWriteArrayList线程安全的ArrayList

1.3.1 适用场景

。读操作可以尽可能地块,而写即使慢一线也没有太大关系。

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

1.3.2 读写规则

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

1.3.3 缺点

。数据一致性问题:CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。希望写入数据,马上读到,CopyOnWrite不支持。

。内存占用问题:CopyOnWrite的写是复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存。

下面是源码演示:

功能演示:


public class CopyOnWriteArrayListDemo {

    public static void main(String[] args) {
//        ArrayList<Object> objects = new ArrayList<>();
        CopyOnWriteArrayList<Object> objects = new CopyOnWriteArrayList<>();
        objects.add("1");
        objects.add("2");
        objects.add("3");
        objects.add("4");
        objects.add("5");

        Iterator<Object> iterator = objects.iterator();
        while (iterator.hasNext()) {
            System.out.println("list is " + objects);
            String next = (String) iterator.next();
            if (next.equals("2")) {
                objects.remove("5");
            }
            if (next.equals("3")) {
                objects.add("3 found");
            }
            System.out.println(next);
        }
    }
}

 运行结果:

1.4 并发队列

队列应用场景

。用队列可以在线程间传递数据:生产者消费者模式、银行转账。

1.4.1 BlockingQueue 阻塞队列

。阻塞队列是具有阻塞功能的队列,所以它首先是一个队列,其次是具有阻塞功能。

。通常,阻塞队列的一端是给生产者放数据用,另一端给消费者拿数据用。阻塞队列是线程安全的,所以生产者和消费者都可以是多线程。

。是否有界(容量有多大) : 这是一个非常重要的属性,无界队列意味着里面可以容纳非常多.

。阻塞队列和线程池的关系:阻塞队列是线程池的重要组成部分。

1.4.2 阻塞队列重要的方法

。take() 方法:获取并移除队列的头结点,一旦如果执行take的时候,队列里无数据,则阻塞,直到队列里有数据。

。put() 方法:插入元素。但是如果队列已满,那么就无法继续插入,则阻塞,直到队列里有了空闲空间。

代码演示:


public class ArrayBlockingQueueDemo {

    public static void main(String[] args) {
        ArrayBlockingQueue queue = new ArrayBlockingQueue(3);

        Interviewer r1 = new Interviewer(queue);
        Consumer r2 = new Consumer(queue);
        new Thread(r1).start();
        new Thread(r2).start();
    }

}

class Interviewer implements Runnable {

    BlockingQueue<String> queue;

    public Interviewer(BlockingQueue queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        System.out.println("10个候选人都来了");
        for (int i = 0; i < 10; i++) {
            String candidate = "Candidate" + i;
            try {
                queue.put(candidate);
                System.out.println("安排好了" + candidate);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
        try {
            queue.put("stop");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class Consumer implements Runnable{
    BlockingQueue<String> queue;

    public Consumer(BlockingQueue queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String msg;
        try {
            while (!(msg = queue.take()).equals("stop")) {
                System.out.println(msg + "到了");
            }
            System.out.println("所有候选人都结束了");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

运行结果:

源码分析:

 

1.4.3 注意点

ArrayBlockingQueue 使用的是同一把锁。

LinkedBlockingQueue使用的是两把锁.

PriorityBlockingQueue 
.支持优先级
.自然顺序(而不是先进先出)
.PriorityQueue 的 线程安全的版本
SynchronousQueue
.它的容量为0
。需要注意的是,SynchronousQueue的容量不是1而是0,因为SynchronousQueue不需要去持有元素,它所做的就是直接传递(direct handoff)
。效率很高
SynchronousQueue注意点
。SynchronousQueue没有peek等函数,因为peek的含义是取出头结点,但是SynchronousQueue的容量是0,所有连头结点都没有,同理,没有iterrate相关方法
。是一个极好用来直接传递的并发数据结构
。SynchronousQueue是线程池
Executors.newCachedThreadPool()使用的阻塞队列
DelayQueue
。延迟队列,根据延迟时间排序
。元素需要实现Delayed接口,规定排序规则

总结:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值
>