java-并发-并发容器(1)

同步容器类有以上问题,导致这些类成了鸡肋,Java 5推出了并发容器类

队列Queue类型的BlockingQueue和ConcurrentLinkedQueue
Map类型的ConcurrentMap
Set类型的ConcurrentSkipListSet和CopyOnWriteArraySet
List类型的CopyOnWriteArrayList
Map对应的有ConcurrentHashMap

Map对应的有ConcurrentHashMap

更加细化的锁机制。同步容器直接把容器对象做为锁,这样就把所有操作串行化,其实这是没必要的,过于悲观,而并发容器采用更细粒度的锁机制,名叫分离锁,保证一些不会发生并发问题的操作进行并行执行。
附加了一些原子性的复合操作。比如putIfAbsent方法
迭代器的弱一致性,而非“及时失败”。它在迭代过程中不再抛出Concurrentmodificationexception异常,而是弱一致性。
在并发高的情况下,有可能size和isEmpty方法不准确,但真正在并发环境下这些方法也没什么作用
另外,它还有一些附加的原子操作,缺少即加入、相等便移除、相等便替换。

putIfAbsent(K key, V value),缺少即加入(如果该键已经存在,则不加入)
如果指定键已经不再与某个值相关联,则将它与给定值关联。
类似于下面的操作
If(!map.containsKey(key)){
return map.put(key,value);
}else{
return map.get(key);
}
remove(Object key, Object value),相等便移除
只有目前将键的条目映射到给定值时,才移除该键的条目。
类似于下面的:
if(map.containsKey(key) && map.get(key).equals(value)){
Map.remove();
return true;
}else{
return false;
}
replace(K key, V value)
replace(K key, V oldValue, V newValue),相等便替换。
只有目前将键的条目映射到某一值时,才替换该键的条目。
上面提到的三个,都是原子的。在一些缓存应用中可以考虑代替HashMap/Hashtable

ConcurrentHashMap
对应的非并发容器:HashMap
目标:代替Hashtable、synchronizedMap,支持复合操作
原理:JDK6中采用一种更加细粒度的加锁机制Segment“分段锁”,JDK8中采用CAS无锁算法
在JDK6中ConcurrentHashMap的的并发实现主要利用内部类Segment实现”分段加锁“的思想
ConcurrentHashMap融合了hashtable和hashmap二者的优势。
hashtable是做了同步的,hashmap未考虑同步。所以hashmap在单线程情况下效率较高。hashtable在的多线程情况下,同步操作能保证程序执行的正确性。但是hashtable每次同步执行的时候都要锁住整个结构.

源码

public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
        implements ConcurrentMap<K, V>, Serializable

继承了java.util.AbstractMap中已有的实现,这个在前面整理HashMap的时候已经提过了,重点看后面实现的接口ConcurrentMap和Serializable。Serializable是做序列化处理的,而ConcurrentMap的定义又如下:

ublic interface ConcurrentMap<K, V> extends Map<K, V> {

    V putIfAbsent(K key, V value);

    boolean remove(Object key, Object value);

    boolean replace(K key, V oldValue, V newValue);

    V replace(K key, V value);
}
V putIfAbsent(K key, V value);   如果没有这个key,则放入这个key-value,返回null,否则返回key对应的value。
boolean remove(Object key, Object value); 移除key和对应的value,如果key对应的不是value,移除失败
boolean replace(K key, V oldValue, V newValue); 替代key对应的值,仅当当前值为旧值
V replace(K key, V value); 替代key对应的值,只要当前有值

构造方法和ConcurrentHashMap的Segment实现

ublic ConcurrentHashMap(int initialCapacity,
                             float loadFactor, int concurrencyLevel)

有几个重载的方法,说这个参数最全的,有3个参数,除了HashMap中涉及到的loadFactor和initialCapacity外,还有一个concurrencyLevel,翻译过来就是并发级别或者并发度。与此对应,ConcurrentHashMap中有一个segments数组对象,元素类型是ConcurrentHashMap的内部类Segment,而concurrencyLevel就是这个segments数组的大小。

static final class Segment<K,V> extends ReentrantLock implements Serializable

Segment扩展了ReentrantLock并实现了Serializable接口。除此之外,我们还发现这个类里实现的东西和java.util.HashMap非常相似。
实际上,这个类正是整个ConcurrentHashMap实现的关键。我想,作为这篇文章读者的您,应该会用到过各式各样的数据库,就拿Mysql的innoDB引擎来看,它除了支持表级锁意外,还支持行级锁,意义就在于这减小了锁粒度,当只对某行数据进行操作的时候,很可能没有必要限制同一个表中其它行的数据。在这个类中,这个Segment也是起到了同样的作用。每个Segment本身就是一个ReentrantLock,只有要修改的数据存在在同一个Segment,才有可能会需要锁定,这样就提高了多线程情况下效率,没必要所有线程全部等待锁。

public V get(Object key) {
        Segment<K,V> s; // manually integrate access methods to reduce overhead
        HashEntry<K,V>[] tab;
        int h = hash(key);
        long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
        if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
            (tab = s.table) != null) {
            for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
                     (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
                 e != null; e = e.next) {
                K k;
                if ((k = e.key) == key || (e.hash == h && key.equals(k)))
                    return e.value;
            }
        }
        return null;
    }

实际上,就是通过key计算得到的hash值,确定对应的Segment对象,并用原子操作获取到对应的table和table中hash值对应的对象。我们可以看到,在这个过程中,是没有显式用到锁的,仅仅是通过Unsafe类和原子操作,避免了阻塞,提高了性能。

public V put(K key, V value) {
        Segment<K,V> s;
        if (value == null)
            throw new NullPointerException();
        int hash = hash(key);
        int j = (hash >>> segmentShift) & segmentMask;
        if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck
             (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment
            s = ensureSegment(j);
        return s.put(key, hash, value, false);
    }

我们看到其中最后是使用Segment的put()方法的调用,而putIfAbsent()的方法的调用,仅仅是最后一个参数不同。
Segment的put()方法的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;
        }
时间: 2024-09-11 07:23:44

java-并发-并发容器(1)的相关文章

Java 高并发五:JDK并发包1详细介绍_java

在[高并发Java 二] 多线程基础中,我们已经初步提到了基本的线程同步操作.这次要提到的是在并发包中的同步控制工具. 1. 各种同步控制工具的使用 1.1 ReentrantLock ReentrantLock感觉上是synchronized的增强版,synchronized的特点是使用简单,一切交给JVM去处理,但是功能上是比较薄弱的.在JDK1.5之前,ReentrantLock的性能要好于synchronized,由于对JVM进行了优化,现在的JDK版本中,两者性能是不相上下的.如果是简

java 集合并发操作出现的异常ConcurrentModificationException_java

如Java中的容器Map: for(Person person : pList){ if(person.getGender()==Gender.MALE){ pList.remove(person); //不能在遍历期间进行 remove这个操作 } } Map在遍历时候通常 现获得其键值的集合Set,然后用迭代器Iterator来对Map进行遍历. 注意在遍历的过程中,只能对Map中的元素进行相应的处理,不能把Map元素增加或者把Map元素减少,也就是说,不能改变Map的size大小,就会出现

解决Java多线程并发的计数器问题

问题描述 解决Java多线程并发的计数器问题 3C public class Counter { public static int count = 0; public synchronized static void inc() { count++; } public static void main(String[] args) { //同时启动1000个线程,去进行i++计算,看看实际结果 for (int i = 0; i < 1000; i++) { new Thread(new Ru

《Java 7并发编程实战手册》第六章并发集合

由人民邮电出版社出版的<Java 7并发编程实战手册>终于出版了,译者是俞黎敏和申绍勇,该书将于近期上架.之前并发编程网组织翻译过此书,由于邮电出版社在并发网联系他们之前就找到了译者,所以没有采用并发网的译稿,但邮电出版社将于并发网展开合作,发布该书的样章(样章由并发网挑选,你也可以回帖告诉我们你想看哪一章的样章),并组织赠书活动回馈给活跃读者.活动详情请时刻关注并发网的微博和微信(微信号:ifeves),最后祝各位用餐愉快!:) 本章将介绍下列内容: 使用非阻塞式线程安全列表 使用阻塞式线程

Java 高并发十: JDK8对并发的新支持详解_java

1. LongAdder 和AtomicLong类似的使用方式,但是性能比AtomicLong更好. LongAdder与AtomicLong都是使用了原子操作来提高性能.但是LongAdder在AtomicLong的基础上进行了热点分离,热点分离类似于有锁操作中的减小锁粒度,将一个锁分离成若干个锁来提高性能.在无锁中,也可以用类似的方式来增加CAS的成功率,从而提高性能. LongAdder原理图: AtomicLong的实现方式是内部有个value 变量,当多线程并发自增,自减时,均通过CA

Java 高并发九:锁的优化和注意事项详解_java

摘要 本系列基于炼数成金课程,为了更好的学习,做了系列的记录. 本文主要介绍: 1. 锁优化的思路和方法 2. 虚拟机内的锁优化 3. 一个错误使用锁的案例 4. ThreadLocal及其源码分析 1. 锁优化的思路和方法 在[高并发Java 一] 前言中有提到并发的级别. 一旦用到锁,就说明这是阻塞式的,所以在并发度上一般来说都会比无锁的情况低一点. 这里提到的锁优化,是指在阻塞式的情况下,如何让性能不要变得太差.但是再怎么优化,一般来说性能都会比无锁的情况差一点. 这里要注意的是,在[高并

Java 高并发八:NIO和AIO详解_java

IO感觉上和多线程并没有多大关系,但是NIO改变了线程在应用层面使用的方式,也解决了一些实际的困难.而AIO是异步IO和前面的系列也有点关系.在此,为了学习和记录,也写一篇文章来介绍NIO和AIO. 1. 什么是NIO NIO是New I/O的简称,与旧式的基于流的I/O方法相对,从名字看,它表示新的一套Java I/O标 准.它是在Java 1.4中被纳入到JDK中的,并具有以下特性: NIO是基于块(Block)的,它以块为基本单位处理数据 (硬盘上存储的单位也是按Block来存储,这样性能

《Java 7并发编程实战手册》第五章Fork/Join框架

感谢人民邮电大学授权并发网发布此书样章,新书已上市,购买请进当当网 本章内容包含: 创建Fork/Join线程池 合并任务的结果 异步运行任务 在任务中抛出异常 取消任务 5.1 简介 通常,使用Java来开发一个简单的并发应用程序时,会创建一些Runnable对象,然后创建对应的Thread 对象来控制程序中这些线程的创建.执行以及线程的状态.自从Java 5开始引入了Executor和ExecutorService接口以及实现这两个接口的类(比如ThreadPoolExecutor)之后,使

java多线程并发问题求解

问题描述 java多线程并发问题求解 父类中定义了几个成员变量String类型 a,b,c,这个父类被几个子类共同继承了, 各个子类中在构造器内初始化了a,b,c变量,问多线程调用每一个子类时会 产生并发问题吗? 父类: 解决方案 首先,需要看你的这类是如何设计的,如果只是提供 了构造函数来初始化这几个成员变量,而没有提供外界修改方法如setA...等方法的话,那么你这个类就是线程安全的,因为对象的信息不可能被外界改变. 如果提供了修改方法,那么对于同一个对象,置于多线程访问条件下,就有可能出现

Java 高并发六:JDK并发包2详解_java

1. 线程池的基本使用 1.1.为什么需要线程池 平时的业务中,如果要使用多线程,那么我们会在业务开始前创建线程,业务结束后,销毁线程.但是对于业务来说,线程的创建和销毁是与业务本身无关的,只关心线程所执行的任务.因此希望把尽可能多的cpu用在执行任务上面,而不是用在与业务无关的线程创建和销毁上面.而线程池则解决了这个问题,线程池的作用就是将线程进行复用. 1.2.JDK为我们提供了哪些支持  JDK中的相关类图如上图所示. 其中要提到的几个特别的类. Callable类和Runable类相似,