Java并发常用类

1、分类

Atomic包,locks包,线程池&并发集合。

2、并发集合

(1)ConcurrentHashMap

    建立在HashMap基础上,jdk1.7时是由多个Segement组成,每个Segment里面类似HashMap结构,默认是有16个Segement,此时最多可以允许16个线程并发操作,Segement指定后不可扩容;jdk1.8时抛弃了分段锁,结构上和HashMap一直,链表加红黑树。

    jdk1.7的结构:

jsk1.7的concurrentHashMap

    每一个Segment都是一个HashEntry[] table。

    public class ConcurrentHashMap extends AbstractMap implements ConcurrentMap,Serializable{

        // 将整个hashmap分成几个小的map,每个segment都是一个锁;与hashtable相比,这么设计的目的是对于put, remove等操作,可以减少并发冲突,对不属于同一个片段的节点可以并发操作,大大提高了性能

        final Segment[] segments;

        // 本质上Segment类就是一个小的hashmap,里面table数组存储了各个节点的数据,继承了ReentrantLock, 可以作为互拆锁使用

        static final class Segment extends ReentrantLock implements Serializable{ 

         transient volatile HashEntry[] table;

         transient int count;

        }

        // 基本节点,存储Key, Value值

        static final class HashEntry{

            final int hash;

            final K key; 

           volatile V value; 

           volatile HashEntry next;

        }

    }

    jdk1.8的结构:

    取消了segements字段,直接采用 transient volatile HashEntry[] table 保存数据,采用table数组元素作为锁,从而实现了对每一行数据进行加锁,进一步减少并发冲突的概率。

    将table数组+单向链表的数据结构,变更为table数组+单向链表+红黑树的结构。单向链表的时间复杂度为O(n),红黑树的时间复杂度为O(logN)。

(2)CopyOnWriteArrayList

    CopyOnWriteArrayList是一个线程安全的ArrayList,读操作无锁,add操作时采用拷贝一个新数组来操作,读效率很高,但是有可能有脏读。

    原理:

    在CopyOnWriteArrayList中,读操作不同步,因为他们在内部数据的快照上工作,所以多个迭代器可以同时遍历而不会相互阻塞。

    所有的写操作都是同步的,他们在备份数组的副本上工作,写操作完成后,将原阵列替换为复制的阵列,并释放锁定。支持数据变得易变,所以替换数组是原子操作。

    写操作后创建的迭代器将能够看到修改的结构。

    写时复制集合返回的迭代器不会抛出ConcurrentModificationException,因为他们在数组的快照上工作,并且无论后续怎么修改,都会像迭代器创建时那样完全返回元素,这里就可能出现脏读现象。

    源码解析:

       /** The lock protecting all mutators ,执行写时复制操作,需要使用可重入锁加锁*/

        final transient ReentrantLock lock =newReentrantLock();

       /** The array, accessed only via getArray/setArray. 对象数组,用于存放元素 */

        private transient volatile Object[] array;

    添加操作:

    finalReentrantLock 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();

    }

(3)CopyOnWriteArraySet

    基于CopyOnWriteArrayList实现,但是添加时需要遍历一下元素存在与否,所以效率会低一点;hashSet也是在添加的时候遍历,但是不是顺序遍历,hashSet是基于hashMap实现的,所以根据hashcode确定位置后,直接定位元素。

(4)BlockingQueue

    实现类有ArrayBlockingQueue基于数组先进先出,有界;LinkedBlockingQueue基于链表,默认无界,可指定容量;PriorityBlockingQueue 无界。

3、lock

(1)AQS

    提供基于Lock接口的并发基础类,主要使用CAS实现。目的是在并发状态下管理被阻塞的线程。提供了一套通用的机制来管理同步状态,阻塞/唤醒线程,管理等待队列等。提供了acquire()、acquireShared()、release()、releaseShared()、hasWaiters()、cas改状态等方法。核心是等待队列(CLH队列)。

(2)CLH队列

    双向链表,节点是对线程的包装,分独占和共享两种类型;用前一节点的某一属性表示后一节点的状态;自旋方式不断cas插入包含当前线程的节点到尾部。

(3)Condition

    一般配合lock使用,await(),signal(),signalAll()。

(4)ReentrantLock

    基于AQS,里面有一个state值,先CAS设置state,如果state!=0,但发现拿锁的是自己则state++,获取失败的加到CLH队列尾,内部自选继续获取锁。

4、Atomic

(1)AtomicInteger

AtomicInteger源码

    里面由一个volatile修饰的value属性,为了保证值在线程间可见,并发操作采用CAS,可实现原子递增。

    源码解析:

getAndIncrement方法
unsafe.getAndAddInt方法

       其中var表示要被更新的对象,var2是原始值在内存中的偏移地址,通过getIntVolatile方法拿到现在的值var5,现在的var5是一个期望值。compareAndSwapInt是一个native方法,compareAndSwapInt(var1, var2, var5, var5 + var4)是根据var1和var2拿到内存中的值,和期望值var5比较,如果与期望值相等,就将其更新为var5+var4,该方法一直在尝试,直到内存中的值和期望值一样时,才能进行修改,并返回修改前内存的值。

    同时还提供了一个compareAndSet方法,当且仅当期望值和内存中的值相等时,才能执行更新操作,可以保证多线程同时修改共享变量时,只有一个线程可以修改成功。

(2) AtomicStampedReference

    CAS可能发生ABA问题,即一个变量原来是A,先被修改成B后,又修改回了A,由于CAS操作知识比较当前值和预期值是否一样,在其他线程来看,该变量就好像没有发生过改变。

    这种问题可以为数据添加一个时间戳,每次成功修改数据时,不仅修改数据的值,同时要更新时间戳的值,CAS操作时,不仅要比较当前值和预期值,还需要比较当前时间戳和预期时间戳,两者都满足时才能修改成功。

    AtomicStampedReference就是这样做的,它不仅维护对象之,还维护一个时间戳,当AtomicStampedReference对应的值被修改时,不仅要更新数据本身,还要更新时间戳(版本号)。

5、线程池

Executor接口,只有一个execute方法;Executors封装了一些常用线程池的方法;ExecutorService接口,继承Executor,新增shutdown,submit,invokeAll方法;ThreadPoolExcutor实现了ExecutorService,可用于创建线程池;Future接口,含cancel,get,isDone方法。

常用线程池,可以通过Executor调用创建,newFixedThreadPool,固定线程数量;newSingleThreadExecutor,单个线程;newCachedThreadPool,可调节线程数量线程池,有核心数量和最大数量;newSingleThreadSceduledExecutor,可以执行定时任务的线程池。

6、同步容器

同步容器主要包含Vector,Stack,HashTable,Collections类中提供静态工厂方法创建的类(Collections.synchronizedXxxx等方法)。

Vector实现了List接口,Vector实际上就是一个数组,和ArrayList类似,但是Vector中的方法都是synchronized方法。

Stack也是一个同步容器,它的方法也用了synchronized进行了同步,它实际上是继承了Cector类。

HashTable实现了Map接口,它和HashMap类似,但是HashTable进行了同步处理。

同步容器的缺陷:同步容器的原理就是在方法上使用synchronized修饰,那么这些方法每次只允许一个线程调用。所以其他视图调用这个方法的线程只能等待,那么性能就比非同步容器差。

你可能感兴趣的:(Java并发常用类)