多线程高级讲解一:常见的线程安全的类有哪些 ?

Vector 与 ArrayList的区别:

他们的原理都是通过数组实现的,增删慢,查询快。

Vector是线程安全的,ArrayList线程不安全,效率高。

 

我们去看源码:

Vector 的add方法是加了synchronized关键字,所以他是一个同步方法,线程是安全的,效率低,工作中即使要使用线程安全的List集合,也不使用Vector,而是用Collections工具类中的   Collections.synchronizedList(arrayList);他可以将线程不安全的ArrayList转换为线程安全的List。

    /**
     * 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;
    }

ArrayList的add方法源码,没有使用任何多线程的锁,所以他是线程不安全的。

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

 

 

HashMap 与 HashTable的区别:

还是看源码:

HashMap: 线程不安全,可以有null键,也可以有null值。

    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

 

HashTable的put方法被synchronized修饰,所以HashTable是线程安全的。从第一段源码可以看出不能有null值。 当然也不能有null键。

    public synchronized V put(K key, V value) {
        // Make sure the value is not null
// 这段源码可以看出,值不能为null
        if (value == null) {
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];
        for(; entry != null ; entry = entry.next) {
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }

        addEntry(hash, key, value, index);
        return null;
    }

HashTable的get方法,也是线程安全的。

    public synchronized V get(Object key) {
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                return (V)e.value;
            }
        }
        return null;
    }

 

通常情况下,也不会使用HashTable,因为在多线程的环境下,HashTable的get方法,只能让一个线程同时操作,所以他的效率根本就不高。所以在jdk1.5的时候就出现了 ConcurrentHashMap。

还有 java 提供了 Collectiions的类,有一个  Collections.synchronizedMap(hashMap); 他是可以将线程不安全的HashMap转换为线程安全的Map的集合。

 

接下来就说一下,为什么多线程不用HashTable,而使用ConcurrentHashMap。

原因就是HashTable虽然线程安全,但是效率低。而ConcurrentHashMap底层原理就是HashTable,只不过做了分段处理。

上面的源码也看见了,HashTable使用了Synchronized所以不管是put数据,还是get数据,都是加锁了的,所以多线程在操作map集合的时候,也是一条一条执行的,这样效率特别低。

在jdk1.5时,工程师就想了个办法,把这个ConcurrentHashMap集合,按照索引进行拆分。比如ConcurrentHashMap里有100条数据,就将这160条数据,按照索引拆分为16个HashTable,这16个HashTable就是16个不同的线程,专业名词叫:分段锁,通过16个线程操作ConcurrentHashMap来提高程序的效率。但是拆分出来的16个HashTable,每个HashTable中还有16条数据,这16条数据是同一把锁。所以如果是查询整个集合或者查询数据较多的情况下,推荐使用ConcurrentHashMap。如果查询某1个2个的话,还是可以直接使用HashTable。还有上面我故意说成16个HashTable,是因为ConcurrentHashMap最高支持分成16段。

在jdk1.8以前,这个ConcurrentHashMap底层原理是 :数组 + 分段锁。

 

但是在jdk1.8之后,java重写了HashMap 和 ConcurrentHashMap。所以他们的底层原理又有所改变。

1.8以后的ConcurrentHashMap 和 HashMap 底层原理是:数组+链表+红黑树, 只不过一个线程安全,一个不安全。

有兴趣的可以看看这篇文章:彻底搞清楚ConcurrentHashMap的实现原理(含JDK1.7和JDK1.8的区别)

我觉得大概原理也都差不多,只不过性能更优化了,比如说1.8以前的ConcurrentHashMap是直接分段,这样如果数据量较大的情况下,其实效率也还是比较低。1.8之后,加入了红黑树的数据结构,当数据量达到一定程度之后,就使用红黑树存储数据,为后面查询,提高效率。

 

这是ConcurrentHashMap的put方法。

public V put(K key, V value) {
        return putVal(key, value, false);
    }

    /** Implementation for put and putIfAbsent */
    final V putVal(K key, V value, boolean onlyIfAbsent) {

        /底层是用的HashTable,这里也说明了,不能有null健 和 null值。
        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;
    }

 

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值