专栏名称: 爬蜥
目录
相关文章推荐
51好读  ›  专栏  ›  爬蜥

java对线程安全支持有哪些?

爬蜥  · 掘金  ·  · 2018-09-18 02:14

正文

阅读 27

java对线程安全支持有哪些?

java中常见的线程安全模块

  1. 同步容器。它的原理是将状态封装起来,并对每个公有方法都实行同步,使得每次只有1个线程能够访问容器的状态。

    • Vector和HashTable
    • Collections.synchronizedXXX方法

    同步容器的问题

    1. 这种方式使得对容器的访问都串行化,严重降低了并发性,如果多个线程来竞争容器的锁时,吞吐量严重降低
    2. 对容器的多个方法的复合操作,是线程不安全的,比如一个线程负责删除,另一个线程负责查询,有可能出现越界的异常
  2. 并发容器。java.util.concurrent包里面的一系列实现

    • Concurrent开头系列。以ConcurrentHashMap为例,它的实现原理为分段锁。默认情况下有16个,每个锁守护1/16的散列数据,这样保证了并发量能达到16

    分段锁缺陷在于虽然一般情况下只要一个锁,但是遇到需要扩容等类似的事情,只能去获取所有的锁

    ConcurrentHashMap一些问题

    1. 需要对整个容器中的内容进行计算的方法,比如size、isEmpty、contains等等。由于并发的存在,在计算的过程中可能已进过期了,它实际上就是个估计值,但是在并发的场景下,需要使用的场景是很少的。
      以ConcurrentHashMap的size方法为例:
    /**
        * Returns the number of key-value mappings in this map.  If the
        * map contains more than <tt>Integer.MAX_VALUE</tt> elements, returns
        * <tt>Integer.MAX_VALUE</tt>.
        *
        * @return the number of key-value mappings in this map
        */
       public int size() {
           //为了能够算准数量,会算2次,如果两次算的不准,就锁住再算
           final Segment<K,V>[] segments = this.segments;
           int size;
           boolean overflow; // true if size overflows 32 bits
           long sum;         // sum of modCounts
           long last = 0L;   // previous sum
           int retries = -1; // 第一轮的计算总数不重试
           try {
               for (;;) {
                   if (retries++ == RETRIES_BEFORE_LOCK) {
                   //RETRIES_BEFORE_LOCK 默认是2
                       for (int j = 0; j < segments.length; ++j)
                           ensureSegment(j).lock(); // force creation
                   }
                   sum = 0L;
                   size = 0;
                   overflow = false;
                   for (int j = 0; j < segments.length; ++j) {
                       Segment<K,V> seg = segmentAt(segments, j);
                       if (seg != null) {
                           sum += seg.modCount;
                           int c = seg.count;
                           if (c < 0 || (size += c) < 0)
                               overflow = true;
                       }
                   }
                   //第一次计算的时候
                   if (sum == last)
                       break; //如果前后两次数数一致,就认为已经算好了
                   last = sum;
               }
           } finally {
               if (retries > RETRIES_BEFORE_LOCK) {
                   for (int j = 0; j < segments.length; ++j)
                       segmentAt(segments, j).unlock();
               }
           }
           return overflow ? Integer.MAX_VALUE : size;
       }
    复制代码
    1. 不能提供线程独占的功能
    • CopyOnWrite系列。以CopyOnWriteArrayList为例,只在每次修改的时候,进行加锁控制,修改会创建并重新发布一个新的容器副本,其它时候由于都是事实上不可变的,也就不会出现线程安全问题

    CopyOnWrite的问题

    每次修改都复制底层数组,存在开销,因此使用场景一般是迭代操作远多于修改操作

    CopyOnWriteArrayList的读写示例

     /**
         * 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) {
            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






请到「今天看啥」查看全文