java集合类与并发包内的对象使用说明

时间:2022-09-03 14:27:54


集合类

Collection接口

Collection的实现主要有ListSet,两者之间的主要区别是,List支持重复,Set不支持,

List的实现包括:ArrayList, LinkedList, Vector,Stacl.

Set的实现包括:HashSet, TreeSet

Collection的主要方法:

       add(E)添加元素

       remove(E):    删除

       get(index):     得到

       contains(E):   含有

       iterator():              得到遍历器

 

ArrayList

实现方式

创建ArrayList

              默认构造器通过调用ArrayList(int)来完成创建,传入值10。代码:

       java集合类与并发包内的对象使用说明

super()调用了默认构造器,是空的。这段代码最重要的就是,创建了一个Object数组,并赋给了当前elementData属性,数组大小是传入的initialCapacity, 因此newArrayList()将会创建一个大小10的数组。

 

插入对象:add(E)

       插入对象时,会根据Object数组当前已有元素属性+1得到一个minCapacity,如果大于

Object数组大小,则先将Object数组赋给另一个数组,然后得到一个新的数组大小=当前数组大小*1.5+1 如果这个值小于minCapacity,则minCapacity为新数组的大小,然后使用Arrays.copyOf来产生新的数组。

       add(E,index)方法:这个在指定位置插入值,首先要确保位置存在且Object数组大小足够,插入后,所有index之后的元素都要向后移一位,这就是多一次复制数组的代价

       其他插入方法:addAll(Collection<? extends E>), addAll(int, Collection<?extends E>)

 

删除对象:remove(E)

       判断E是否为null,是则遍历所有元素,null是否存在,存在,则所有其后元素向前复制一位,并将最后的设为null(释放)

       E不是null,则通过Eequals来判断对象是否存在。

 

获取单个对象:get(int)

       使用ArrayList最好的地方,get(int)直接得到数组位置的元素(判断int的范围)

 

遍历对象:iterator()

       ArrayList中的内部类ItrhasNext(),就是当前位置和数组元素大小比较,相等false,否则true

       next()方法,会比较创建此Iterator时的modCount属性和当前的modCount属性,如果不相等,说明在遍历时发生了改变Object数组元素的行为(addremove),抛出ConcurrentModificationException,相等,则取下一个元素(get方法),取不到,抛出IndexOutOfBoundsException

 

注意:

       ArrayList 基于数组实现,无容量限制

       增加元素时可能扩大数组,但是删除时不会减小数组大小。对于非null元素,用equals方法判断相等

       线程非安全

LinkedList

实现方式

基于双向链表的实现,内部有Entry类,代表元素,有element属性代表valuenext属性指向下一个Entryprevious属性指向上一个元素,这种机制有利于快速实现集合中元素的移动

所有的方法基于链表机制,不做解释

非线程安全。

 

Vector

数组实现。与ArrayList相似

不同处:

       add(E)方法在扩容时,如果capacityIncrement属性大于0 则数组大小为现有size+capacityIncrement,如果capacityIncrement <=0 , 则大小为现有size *2

       所以Vector可以通过传入capacityIncrement来控制容量的扩充

       add(E),remove(E),get(int)方法都是synchronized

       线程安全的

 

Stack

       继承与Vector,实现了LIFO

 

HashSet

       基于HashMap实现

add(E): 通过HashMapput(Object,Object)方法,将要添加的元素作为keyvalue传入一个之前已创建的Object

其他方法都是通过HashMap来实现的

非线程安全

 

TreeSet

       基于TreeMap实现,支持排序,非线程安全

 

 

Map接口

 

 

 

 

 

 

 

 

并发包(java.util.concurrent)

ConcurrentHashMap

线程安全的HashMap实现

 

ConcurrentHashMap()

       HashMap一样,有initialCapacity属性,loadFactor属性,还多一个concurrencyLevel属性。三个属性默认值是,16,0.7516

       以下方式计算ssize的值

int sshift = 0;

int ssize = 1;

while(ssize <concurrencyLevel){

       ++sshift;

       ssize <<= 1;

}

concurrencyLevel16时,ssize16

ssize参数用于创建Segment对象数组,确定了数组的大小

       以下方式计算cap参数

int c =initialCapacity / ssize;

if(c*ssize <initialCapacity)

       ++ c;

int cap = 1;

while(cap < c)

       cap <<= 1;

根据以上参数,cap1

caploadFactor用于为Segment对象数组创建Segment对象。Segment对象继承ReentrantLock, 在创建时,动作为创建一个大小为capHashEntry数组,基于caploadFactor计算thresholdthreshold = (int)(newTable.length *loadFactor);

 

put(Objectkey, Object value)

       value 不能为null

       方法没有加上synchronized

       根据传入的keyhashCode, 得到Segment数组中的Segment对象,然后调用Segmentput方法。

       对于Segmentput方法,进行lock,完成释放锁

       由此可见ConcurrentHashMap根据concurrencyLevel,分成了对个Segment对象,对key-value进行存储,避免了每次put都锁住整个数组。默认情况下,可有16个线程并发无阻塞的操作对象。

 

remove(Objectkey)

remove方法同样根据key,找到Segment,调用Segmentremove(Object key)

Segmentremoveput一样lock,然后向HashMap删元素一样操作

 

get(Objectkey)

get方法一般来说是没有锁的,在并发的情况下加快了数据的获得。

什么情况下加锁,put方法生成HashEntry对象后,value值还没有设置完成时,并发读取了,就会得到nullvalue值,这是调用readValueUnderLock方法lock后,返回value

get方法不加锁,怎么保证数据的一致性?

HashEntry数组对象是voliate的,保存在main memory中,一切putremove的并发操作对于get是马上可见的,当然也有脏数据的可能,比如在get的过程中一次putremove的完成,会导致不一致,当然这种可能性很低,会了性能是值得的

 

containsKey(Objectkey)

get操作一样,没有加锁,步骤也相似

CopyOnWriteArrayList

是一个线程安全的,在读操作时无锁的ArrayList

 

add(E)

没有使用synchronized关键字,而是使用ReentrantLock来保证线程安全。

ArrayList不同的是,每次都会创建一个新的Object数组,大小为当前size+1 将之前的所有元素复制到新的数组中,新的对象加入数组的最后

remove(E)

add一样,没有在方法上加synchronized关键字,而是使用ReentrantLock来保证线程安全。

get(int)

为了提高性能,没有加锁

 

iterator()

在调用iterator之后,创建一个新的COWIterator对象实例,并保存一个当前数组的快照next对此快照数组进行遍历,所以遍历时不会抛出concurrentModificatiedException

 

性能比较

ArrayList比,随着元素数量和线程的增加,CopyOnWriteArrayList在增加元素和删除元素性能下降很严重,比ArrayList低,但是随着线程增加,查找元素比ArrayList好很多。

 

CopyOnWriteArraySet

基于CopyOnWriteArrayList实现,不同的是add时,调用了addIfAbsent,确保唯一性。

 

 

 

ArrayBlockingQuene

是个基于数组,FIFO,线程安全的集合类特色是:可实现指定时间的阻塞读写,并且容量可限制。

ArrayBlockQuene(int)

没有默认构造器,传入的参数即为对象数组的大小,同时初始化锁和两个锁上的Condition,一个为notEmpty,    一个为notFull

offer(E,long,TimeUnit)

用于插入元素到数组尾部,如数组已满,则等待,直到一下三种情况才继续:被唤醒,到达指定时间,当前线程被中断。

此方法,加锁,判断数组是否满,未满则插入元素,满了,且超过指定时间,返回false。如果未超过指定时间,调用notFullconditionawaitNanos方法进行等待,如被唤醒或超时,则继续判断数组是否满;如线程被interrupt,则抛出InterruptException

另一个不带时间的offer方法在数组满时,不等待直接返回false

put方法,在数组满时一直等待,直到数组不为空或线程被中断

 

poll(E,long TimeUnit)

用于获取队列中的第一个元素,如没有元素,则等待,与offer相同,也在三种情况后继续。

过程:加锁,如果数组元素个数不为0,取最后一个元素,获取后在该位置设null。如数组元素个数为0,则判断等待时间是否大于0 小于0,则返回null;大于0,调notEmpty conditionawaitNanos方法进行等待(和offer一样)

另一个不带时间的poll,元素个数为0,直接返回null

take方法,在数组为空时一直等待,直到数组不为空或线程被interrupt

 

iterator

先加锁,然后创建一个Itr实例,Itr在创建时获取数组元素尾部的index以及尾部元素,调用完释放锁

 

 

小结

ArrayBlockingQueue为一个基于固定大小数组,ReentrantLock以及Condition实现的可阻塞的FIFOQueue

 

AtomicInteger

是一个支持原子操作的Integer类。实现顺序获取ID的代码

java集合类与并发包内的对象使用说明

 

incrementAndGet()

此方法先获取value属性,然后value+1,赋给一个局部的next,明显都是非线程安全的

关键是

if (compareAndSet(current, next))

       return next;

compareAndSet调用SununSafecompareAndSwapInt方法,此方法基于CPUCAS原语来实现。CAS简单说就是由CPU比较内存位置上的值是否为当前值,如是则将其设置为next,不是返回false。因此上面代码片段是在一个无限循环的代码段中执行,保证并发时ID 的顺序。

       基于CAS的操作是可认为无阻塞的,性能好于同步锁的方式。

 

ThreadPoolExecutor

提供线程池的服务,很容易将一个Runnable接口的任务放入线程池执行

execute(Runnable)

1如果当前线程数小于配置的corePoolSize, 调用addIfUnderCorePoolSize方法,这个方法:

先调用mainLock加锁,线程池处于RUNNING状态,addThread增加线程,addThread方法先创建Worker对象,调用threadFactory创建线程,如果创建的线程不为null,将Worker对象的thread设置为创建出来的线程,并将此Worker对象放入workers中,最后增加当前线程数,释放mainLock,最后启动新建的线程,完成addIfUnderCorePoolSize的执行。

2.如果当前线程数大于corePoolSize,或addIfUnderCorePoolSize执行失败:

       如线程池RUNNING,且往workQueue中成功放入Runnable任务后,则执行后续。

       如果线程池不运行或当前运行的线程数0,则调用ensureQueuedTaskHandled方法执行,此方法用于确保workQueue中的command被拒绝或被处理,

       如线程池RUNNING或线程池中当前运行线程数不为0,则不做任何动作

3.如不符合以上条件,则调用addIfUnderMaximumPoolSize方法,做法和addIfUnderCorePoolSize方法基本一样,不同点在最大线程数进行比较,超过最大线程数返回false       addIfUnderMaximumPoolSize返回false,则执行reject方法,调用设置的RejectedExecutionHandlerrejectedException方法,ThreadPoolExecutor提供了4RejectedExecutionHandler的实现:

       a)CallerRunsPolicy

              当线程池中的线程数=最大线程数后,则交由调用者线程来执行此Runnable任务

       b)AbortPolicy

              当线程池中的线程数=最大线程数时,抛出RejectedExecutionException

       c)DiscardPolicy

              当线程池中的线程数=最大线程数时,不做任何动作

       d)DiscardOldestPolicy

              当线程池中的线程数=最大线程数时,抛弃要执行的最后一个Runnable任务

 

由以上分析来看JDK提供了要使用ThreadPoolExecutor,要配置corePoolSize,最大线程数,任务缓冲队列,以及线程池满时的处理策略。这些需要根据实际项目需求来决定,常见的需求有如下两种:

  1. 高性能

    高性能的执行Runnable任务,即当线程池中线程数尚未到达最大数,则立即提交给线程执行或在最大线程数量的保护下创建线程来执行。可选方式为使用SynchronousQueue作为任务缓冲队列,SynchronousQueue在进行offer时,如没有其他线程调用poll,则直接返回false,按照ThreadPoolExecutor的实现,此时就会在最大线程数允许下创建线程;

    如有其它线程调用poll,则返回true,按照ThreadPoolExecutor execute方法的实现,采用这样的Queue就可实现要执行的任务不会再队列里缓冲,而直接交由线程执行。在这种情况下ThreadPoolExecutor能支持最大线程数的任务数执行。

     

  2. 缓冲执行

    如希望Runnable任务尽量呗corePoolSize范围内的线程执行,可选方式为使用ArrayBlockingQueueLinkedBlockingQueue来作为任务缓冲队列。这样,线程数超过corePoolSize后,会先加入缓冲队列,而不是直接交由线程执行。这种情况下,ThreadPoolExecutor最多能支持最大线程数+BlockingQueue大小的任务数执行。

     

Executors

Executors 提供了一些方法创建ThreadPoolExecutor

newFixedThreadPool(int)

创建固定大小的线程池,线程keepAliveTime0,默认情况下,ThreadPoolExecutor中启动的corePoolSize数量的线程启动后就一直运行,并不会由于keepAliveTime时间到达后仍没有任务需要执行就退出。缓冲任务队列LinkedBlockingQueue,大小为整型的最大数。传入的task超过了线程池大小后,放入队列,当队列中task数量超过整形的最大值后抛出RejectedExecutionException.

 

newSingleThreadExecutor()

建大小为1的线程池,同时只有1个线程执行,其它都在LinkedBlockingQueue队列中

 

newCachedThreadPool()

创建corePoolSize0,最大线程数位整型最大值,keepAliveTime1分钟,缓存队列为SynchronousQueue的线程池。在运行时,放入线程池的task,都会复用线程或启动新线程来执行,当启动的线程数达到整型最大值后抛出RejectedExecutionException,启动后线程存活时间1分钟。

 

newScheduledThreadPool(int)

corePoolSize为传入参数,最大线程数位整型最大值,keepAliveTime0,缓存队列DelayedWorkQueue 在需要延时或定时执行的业务,如异步超时回调等,使用这个ExecutorJDK5之前,用Timer实现这个功能,两者区别:

  1. Timer单线程,一旦一个task运行缓慢,其它的task也会推迟,ScheduledThreadPool并发

  2. Timer中的task抛出RunntimeException时,其它task也不会执行

  3. ScheduledThreadPool可以执行callabletask

     

Semaphore

用于控制某资源被访问的个数的类。

比如控制连接池连接各数的传统方法:

java集合类与并发包内的对象使用说明

而改用Semaphore后,不那么复杂了。

java集合类与并发包内的对象使用说明

tryAcquire方法中,只有有可用的资源时,返回true,当可用资源小于0时,等待,知道超时返回false

 

CountDownLatch

可用于控制多个线程同时开始某动作的类,采用的方式为减计数方式。

 

CyclicBarrier

用于当await的数量达到了设定的数量后,才继续往下执行。

 

ReentrantLock

更为方便的控制并发资源的类。基于AbstractQueuedSynchronizer的实现,AbstractQueuedSynchronizer为基于整数状态值实现资源的并发控制访问提供了很好的支持。

 

Condition

接口,典型实现ReentrantLockReentrantLock提供了一个newCondition方法,以便用户在同一个锁的情况下可以根据不同的情况执行等待或唤醒动作。

ReentrantLock.newCondition().await()

ReentrantLock..newCondition().signal()

 

ReentrantReadWriteLock

ReentrantLock没有继承关系,它提供了读锁(ReadLock)和写锁(WriteLock),比ReentrantLock的一把锁,读写锁分离对读多写少的情况,提高性能。

当调用读锁的lock方法时,没有线程持有写锁,就可获得读锁,意味着只要进行读操作,就没有其它线程进行写操作,读操作时无阻塞的;

当调用写锁的lock时,如果此时没有线程持有读锁或写锁,则可继续,意味着要进行写时,如果有其它线程进行读或写,就会被阻塞,

读写锁使用时的升级和降级机制:

同一线程中,持有读锁后,不能直接调用写锁的lock

同一线程中,持有写锁后,可调用读锁的lock方法,之后如果调用写锁的unlock方法,那么当前锁将降级为读锁。