Java中的集合和线程安全

1. 为什么大多数的集合类不是线程安全的???

为什么多数基本集合实现类都不是线程安全的?比如:ArrayList, LinkedList, HashMap, HashSet, TreeMap, TreeSet等等。
事实上,所有的集合类(除了Vector和HashTable以外)在java.util包中都不是线程安全的,只遗留了两个实现类(Vector和HashTable)是线程安全的为什么?
原因是:线程安全消耗十分昂贵!

你应该知道,Vector和HashTable在Java历史中,很早就出现了,最初的时候他们是为线程安全设计的。
(如果你看了源码,你会发现这些实现类的方法都被synchronized修饰)而且很快的他们在多线程中性能表现的非常差。如你所知的,同步就需要锁,有锁就需要时间来监控,所以就降低了性能。
这就是为什么新的集合类没有提供并发控制,为了保证在单线程中提供最大的性能。

至此我们明白了,为了确保在单线程环境下的性能最大化,所以基础的集合实现类都没有保证线程安全。那么如果我们在多线程环境下如何使用集合呢?
当然我们不能使用线程不安全的集合在多线程环境下,这样做会导致出现我们期望的结果。

2. 如何解决多线程下的集合线程不安全的问题 ??

(1) 添加synchronized代码块

我们可以手动自己添加synchronized代码块来确保安全,但是使用自动线程安全的线程比我们手动更为明智。

(2)同步封装器

Java集合框架提供了工厂方法创建线程安全的集合,这些方法的格式如下:
这个工厂方法封装了指定的集合并返回了一个线程安全的集合。
XXX可以是Collection、List、Map、Set、SortedMap和SortedSet的实现类。比如下面这段代码创建了一个线程安全的列表:


Collections.synchronizedXXX(collection)

List<String> safeList = Collections.synchronizedList(new ArrayList<>());

Map<Integer, String> unsafeMap = new HashMap<>();
Map<Integer, String> safeMap = Collections.synchronizedMap(unsafeMap);

如你锁看到的,工厂方法封装指定的集合,返回一个线程安全的集合。
事实上接口基本都一直,只是实现上添加了synchronized来实现。所以被称之为:同步封装器。后面集合的工作都是由这个封装类来实现。
提示:
在我们使用iterator来遍历线程安全的集合对象的时候,我们还是需要添加synchronized字段来确保线程安全,因为Iterator本身并不是线程安全的,请看代码如下:


List<String> safeList = Collections.synchronizedList(new ArrayList<>());

// adds some elements to the list

Iterator<String> iterator = safeList.iterator();

synchronized (safeList) {
    while (iterator.hasNext()) {
        String next = iterator.next();
        System.out.println(next);
    }
}

同时提醒下,Iterators也是支持快速失败的。
尽管经过类的封装可保证线程安全,但是他们依然有着自己的缺点,具体见下面部分。

(3)并发集合 --->【写时复制集合 & 比对交换集合 & 集合采用了特殊的对象锁】

一个关于同步集合的缺点是,用集合的本身作为锁的对象。
这意味着,在你遍历对象的时候,这个对象的其他方法已经被锁住,导致其他的线程必须等待。
其他的线程无法操作当前这个被锁的集合,只有当执行的线程释放了锁。这会导致开销和性能较低。

这就是为什么jdk1.5+以后提供了并发集合的原因,因为这样的集合性能更高。
并发集合类并放在java.util.concurrent包下,根据三种安全机制被放在三个组中。

*   第一种为:写时复制集合:
这种集合将数据放在一成不变的数组中;任何数据的改变,都会重新创建一个新的数组来记录值。
这种集合被设计用在,读的操作远远大于写操作的情景下。
有两个如下的实现类:CopyOnWriteArrayList 和 CopyOnWriteArraySet.
需要注意的是,写时复制集合不会抛出ConcurrentModificationException异常。
因为这些集合是由不可变数组支持的,Iterator遍历值是从不可变数组中出来的,不用担心被其他线程修改了数据。

*   第二种为:比对交换集合也称之为CAS(Compare-And-Swap)集合:
这组线程安全的集合是通过CAS算法实现的。CAS的算法可以这样理解:
为了执行计算和更新变量,在本地拷贝一份变量,然后不通过获取访问来执行计算。
当准备好去更新变量的时候,他会跟他之前的开始的值进行比较,如果一样,则更新值。
如果不一样,则说明应该有其他的线程已经修改了数据。
在这种情况下,CAS线程可以重新执行下计算的值,更新或者放弃。使用CAS算法的集合有:
ConcurrentLinkedQueue and ConcurrentSkipListMap.
需要注意的是,CAS集合具有不连贯的iterators,
这意味着自他们创建之后并不是所有的改变都是从新的数组中来。
同时他也不会抛出ConcurrentModificationException异常。

*   第三种为:这种集合采用了特殊的对象锁(java.util.concurrent.lock.Lock):
这种机制相对于传统的来说更为灵活,可以如下理解:
这种锁和经典锁一样具有基本的功能,但还可以再特殊的情况下获取:
如果当前没有被锁、超时、线程没有被打断。
不同于synchronization的代码,当方法在执行,Lock锁一直会被持有,
直到调用unlock方法。有些实现通过这种机制把集合分为好几个部分来提供并发性能。
比如:LinkedBlockingQueue,在队列的开后和结尾,所以在添加和删除的时候可以同时进行。
其他使用了这种机制的集合有:ConcurrentHashMap 和绝多数实现了BlockingQueue的实现类.
同样的这一类的集合也具有不连贯的iterators,也不会抛出ConcurrentModificationException异常。

我们来总结下今天我们所学到的几个点:

  1. 大部分在java.util包下的实现类都没有保证线程安全为了保证性能的优越,除了Vector和Hashtable以外。
  2. 通过Collection可以创建线程安全类,但是他们的性能都比较差。
  3. 同步集合既保证线程安全也在给予不同的算法上保证了性能,他们都在java.util.concurrent包中。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,198评论 6 514
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,334评论 3 398
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,643评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,495评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,502评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,156评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,743评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,659评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,200评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,282评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,424评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,107评论 5 349
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,789评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,264评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,390评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,798评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,435评论 2 359

推荐阅读更多精彩内容

  • 本系列出于AWeiLoveAndroid的分享,在此感谢,再结合自身经验查漏补缺,完善答案。以成系统。 Java基...
    济公大将阅读 1,529评论 1 6
  • Java SE 基础: 封装、继承、多态 封装: 概念:就是把对象的属性和操作(或服务)结合为一个独立的整体,并尽...
    Jayden_Cao阅读 2,111评论 0 8
  • Java-Review-Note——4.多线程 标签: JavaStudy PS:本来是分开三篇的,后来想想还是整...
    coder_pig阅读 1,655评论 2 17
  • 概念 在Java中List有两个,一个是java.util下的接口,另一个是java.awt下的类,这里只讨...
    still_loving阅读 1,318评论 0 1
  • Collection ├List │├LinkedList │├ArrayList │└Vector │└Stac...
    AndyZX阅读 880评论 0 1