Java并发包中常用类小结(一)

JDK1.5以后,Java为我们引入了一个并发包,用于解决实际开发中经常用到的并发问题,那我们今天就来简单看一下相关的一些常见类的使用情况。

1ConcurrentHashMap

ConcurrentHashMap其实就是线程安全版本的hashMap。前面我们知道HashMap是以链表的形式存放hash冲突的数据,以数组形式存放HashEntryhash出来不一致的数据。为了保证容器的数据一致性,需要加锁。HashMap的实现方式是,只有putremove的时候会引发数据的不一致,那为了保证数据的一致性,我在putremove的时候进行加锁操作。但是随之而来的是性能问题,因为key-value形式的数据,读写频繁是很正常的,也就意味着我有大量数据做读写操作时会引发长时间的等待。为了解决这个问题,Java并发包问我们提供了新的思路。在每一个HashEntry上加一把锁,对于hash冲突的数据,因为采用链表存储,公用一把锁。这样我才做不同hash数值的数据时,则是在不同的锁环境下执行,基本上是互不干扰的。在最好情况下,可以保证16个线程同时进行无阻塞的操作(HashMap的默认HashEntry16,亦即默认的数组大小是16)。

ConcurrentHashMap是如何保证数据操作的一致性呢?对于数据元素的大小,ConcurrentHashMap将对应数组(HashEntry的长度)的变量为voliate类型的,也就是任何HashEntry发生变更,所有的地方都会知道数据的大小。对于元素,如何保证我取出的元素的next不发生变更呢?(HashEntry中的数据采用链表存储,当读取数据的时候可能又发生了变更),这一点,ConcurrentHashMap采取了最简单的做法,hash值、keynext取出后都为final类型的,其next等数据永远不会发生变更。

另外ConcurrentHashMap采用的锁结构是将读和写分开的,大大的提升了性能,下面我们来看一下两者之间的性能差。

 

由于数据比较密集,我们分开来看一下

相关数据如下:

分析表

元素个数

10

100

1000

10000

线程数

容器类别

增加

删除

查找

增加

删除

查找

增加

删除

查找

增加

删除

查找

1

HashMap

2805

1743

1520

3004

1726

1579

1995

1846

1528

2032

1787

1501

ConcurrentHashMap

4947

2010

1699

5292

2005

1661

2322

1842

1243

2351

2113

1541

10

HashMap

29814

36539

28076

31180

55178

38156

31217

36756

31785

33314

30497

26488

ConcurrentHashMap

18364

22086

8064

21420

22805

9932

20164

20875

7800

19383

19483

10254

50

HashMap

233674

193918

230404

205577

221995

213651

343005

318603

343153

249921

229954

234555

ConcurrentHashMap

131573

98534

16778

152609

96412

24233

123199

108388

20156

134971

122927

18799

100

HashMap

313442

309336

302591

332389

314167

296360

343005

318603

343153

329171

352704

354593

ConcurrentHashMap

161866

122582

21369

141274

114333

21875

116758

97985

24098

140902

120459

18766

(神马情况 数据看不到了 弄一个图吧)

我们可以看到,在单线程下,ConcurrentHashMap的综合性能略低于HashMap,但是随着线程的增长,ConcurrentHashMap的优势就明显提现出来了,尤其是查找元素的性能。因此并发情况下,ConcurrentHashMap是代替HashMap的一个不错的选择。

2CopyOnWriteArrayList

同样的,CopyOnWriteArrayList是线程安全版本的ArrayList。和ArrayList不同的是,CopyOnWriteArrayList默认是创建了一个大小为0的容器。通过ReentrantLock来保证线程安全。CopyOnWriteArrayList其实每次增加的时候,需要新创建一个比原来容量+1大小的数组,然后拷贝原来的元素到新的数组中,同时将新插入的元素放在最末端。然后切换引用。

针对CopyOnWriteArrayList,因为每次做插入和删除操作,都需要重新开辟空间和复制数组元素,因此对于插入和删除元素,CopyOnWriteArrayList的性能远远不如ArrayList,但是每次读取的时候,CopyOnWriteArrayList在不加锁的情况下直接锁定数据,会快很多(但是可能会引发脏读),对于迭代,CopyOnWriteArrayList会生成一个快照数组,因此当迭代过程中出现变化,快照数据没有变更,因此读到的数据也是不会变化的。在读多写少的环境下,CopyOnWriteArrayList的性能还是不错的。

3CopyOnWriteArraySet

CopyOnWriteArraySet是基于CopyOnWriteArrayList实现的。但是CopyOnWriteArraySet鉴于不能插入重复数据,因此每次add的时候都要遍历数据,性能略低于CopyOnWriteArrayList

4ArrayBlockingQueue

ArrayBlockingQueue是基于数组实现的一个线程安全的队列服务,其相关的功能前面我们已经用到过了,这里就不多提了。

5Atomic类,如AtomicIntegerAtomicBoolean

我们来看以下对应的API文档

这种原子类是基于JDKCAS的无阻塞操作,比我们写同步的效率要高多了哦。

对于Atomic类我们在后面的示例中也会很频繁的使用,这里也就不多介绍了。

 

posted @ 2016-02-23 21:28  人生设计师  阅读(8763)  评论(2编辑  收藏  举报