并发容器(2)

什么是同步容器

同步容器通过synchronized关键字修饰容器,保证同一时间内只有一个线程在使用容器,从而使得容器线程安全。

什么是并发容器

允许多线程同时使用容器,并且保证线程安全。核心:锁,cas,cow,分段锁。

同步容器

vector:
stack: Stack实现的是先进后出的栈,入栈出栈都使用synchronized
hashTable:
实现Map接口,实现的功能和HashMap基本一致(HashTable 不可出现null,HashMap键值可以使用null);
hashTable在使用的时候也用synchronized修饰了方法

Arraylist、HashMap 不是线程安全的。
Collections提供了同步集合类

	List list = Collections.synchronizedList(new ArrayList());
	Set set  =	Collections.synchronizedSet(new HashSet());

并发容器

CopyOnWriteArrayList:写的时候复制容器,添加元素的时候,先复制一个新的容器。读写分离。

读: 从原来的容器读
写:把数据写入到新的容器。
在这里插入图片描述

写数据过程:

    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

缺点

1.内存占有问题。
2.数据一致性问题:只能保证数据的最终一致性,不能保证数据的实时一致性。

ConcurrentHashMap

HashMap:是线程不安全的,在并发情况下会产生死锁。
HashTable:线程安全,代价大,简单粗暴,get/put所有的操作都是synchronized修饰。相当于给整个HashTable加了一把大锁。

ConcurrentHashMap: 可以支持并发的读写,支持高并发的检索和更新。
在JDK1.7: 数组+链表+分段锁
在JDK1.8: 数组+链表+红黑树
不能使用null作为key或者value
取消segments,采用HashEntry保存数据
1.当一个位置有多个元素的时候,ConcurrentHashMap优先采用链表的形式存储
2.当链表的元素个数大于8个,并且数组的长度小于64时,进行扩容。
3.当链表的元素个数大于8个,并且数组长度大于64时,链表转为红黑树。

原理:

1.第一次添加元素:

  初始长度:16

—>用hash算法算出元素存放在数组的哪个位置
—>如果出现放在同一个位置的时候,优先采用链表的形式存放
—>在同一个位置的个数达到8个以上的时候,如果数组的长度小于64的时候,扩容数组。
—>数组的长度大于等于64,会将该节点的链表转换为红黑树。

put:

  1. 如果没有初始化table先调用initTable()方法进行初始化过程
  2. 如果没有hash冲突就直接CAS插入。
  3. 如果数组在扩容操作就先进行扩容。
  4. 如果存在hash冲突,就加锁来保证线程安全(一种是链表形式,直接遍历到尾端插入,一种是红黑树就按照红黑树的结构插入)
  5. 最后一个如果链表的数量大于8,就要先转换成红黑树的结构
  6. 如果添加成功,就调用addCount()方法统计size,并且检查是否要扩容

代码:

 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;
            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, null)))
                    break;                   // no lock when adding to empty bin
            }
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            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, null);
                                    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;
                            }
                        }
                    }
                }
                if (binCount != 0) {
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }

队列 BlockingQueue

单项队列:只能向队尾添加元素,从对头删除元素。
双向队列:头和尾都支持元素的入队和出队。

放入数据:
add(Object)
offer(Object): 如果队列可以容纳,则返回true,否则返回false,该方法不阻塞当前执行方法的线程。
put(e): 把object加入到BlockingQueue,如果队列没有空间,则调用此方法的线程被阻塞直到队列有空间在继续。
获取数据:
poll(time); 取走队列排在首位的元素,如果不能立即取走,则可以等设置的time参数的时间,取不到返回false;
poll(timeout):
take(object):取走队首的元素,如果队列为空,当前线程阻塞,直到队列中有新的元素加入。

实现类

LinkedBlockingQueue

	final Object[] items;

ArrayBlockingQueue
DelayQueue: 元素只能当其指定的延迟时间到了,才能从队列中获取该元素,
priorityBlockingQueue: 基于优先级的阻塞队列。
SynchronousQueue: 无缓冲的等待队列。(只有一个元素)

ArrayBlockQueue

/** Main lock guarding all access */
    final ReentrantLock lock;

    /** Condition for waiting takes */
    private final Condition notEmpty;

    /** Condition for waiting puts */           
    private final Condition notFull;

添加元素:

   public void put(E e) throws InterruptedException {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == items.length)
                notFull.await();
            enqueue(e);
        } finally {
            lock.unlock();
        }
    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值