为什么80%的码农都做不了架构师?>>>   

同步集合

接下来讲,java5中提供的同步集合。传统的集合在并发访问时是有问题的。比如HashSet,HashMap,ArrayList,像这样的类,如果是多个线程在操作,多个线程在往它里面取数据,放数据,会出问题的。它们是线程不安全的,会把它里面的数据搞得乱七八糟的。

ConcurrentHashMap

下面我们来看一个关于HashMap同步的问题:

Race Condition引起的性能问题

Race Condition(也叫做资源竞争),是多线程编程中比较头疼的问题。特别是Java多线程模型当中,经常会因为多个线程同时访问相同的共享数据,而造成数据的不一致性。为了解决这个问题,通常来说需要加上同步标志“synchronized”,来保证数据的串行访问。但是“synchronized”是个性能杀手,过多的使用会导致性能下降,特别是扩展性下降,使得你的系统不能使用多个CPU资源。  这是我们在性能测试中经常遇见的问题。

可是上个星期我却遇见了相反的情况:因为缺少同步标志也同样会使性能受影响。

那是一个ERP系统,运行在我们的T2000服务器(8核32线程)上。当500个并发用户的时候居然把所有的CPU都压得满满的(90%以上的忙碌)。这是很少有的现象,在我测试的所有项目中很少有扩展性这么好的系统能把T2000的32个线程都占满的。我狠狠的夸了他们的应用。话音没落,却发现测试结果很差,平均响应时间很长。不可能呀,所有的CPU都在干活,而且都在用户态(如果在系统态干太多的活就有问题了),结果怎么还会差呢。CPU都在干嘛呢?

通过工具发现(Dtrace for Java),我们发现很多的CPU都在做一件事情,那就是不停的执行一条Java语句(HashMap.get())。象是进入了死循环。我们进行了进一步试验,让并发用户数量为1,不停的运行10分钟,结果没有发现这种情况;接着我们让50个并发用户同时运行,但是只运行在一个CPU上(通过psrset),结果也没有出现死循环状态。只要并发用户数量超过10个,运行的CPU超过两个,不到2分钟就出现死循环。一旦死循环出现,大量CPU资源被白白浪费,性能自然很差。

通过上面的试验我们可以很肯定的判断,是由于并发控制不好,导致数据的不一致,引起的死循环。值得一提的是,HashMap不是一个线程安全的数据结构,要用到多个线程中去,需要自己加上同步标志,为什么会死循环呢,看看下面HashMap中get函数的源代码:

public V get(Object key) {
 if (key == null)
     return getForNullKey();
        int hash = hash(key.hashCode());
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
                return e.value;
        }
        return null;
    }

get函数会根据key的hashcode来锁定多个对象,并且遍历这些对象来找到key所对应的对象。当多个线程不安全的修改HanshMap数据结构的时候,有可能使得这个函数进入死循环。

我们建议客户使用ConcurrentHashMap或在使用HanshMap的时候加上同步标志,问题得到解决!

在以前没有并发库的时候,没有ConcurrentHashMap的时候,人们是怎么解决map的同步问题的呢?人们用的是这个方法Collections.synchronizedMap(null);参数是一个Map<K,V> m,作用是返回由指定map支持的同步(线程安全的)map。他得到的是一个new SynchronizedMap<>(m);这个SynchronizedMap对象对Map做了装饰,把所有的方法中的代码都用synchronized代码块包起来了。对象锁是this,当有一个线程调用了这个对象里面的任何方法,其它的线程就不可以再调用这个对象里面的任何方法了,因为锁已经被再执行的线程拿走了,其它线程只能阻塞,等待锁,拿到锁后再操作。你原来的map是线程不安全的,我返回的map是线程安全的。当然,当有了java5的并发库之后,我们不再建议使用这个工具方法,而是建议使用ConcurrentHashMap,因为它内部做了优化,减小了锁的粒度,提高了多线程并发访问的运行效率。它将hashMap分为了若干个Segment<K, V>段,我们知道,hashMap内部其实就是一个数组Entry<K,V>[] table,那么,我们拿一个数组实现hashMap,对hashMap对象加锁的时候,就会对那个大数组加锁,每次只有一个线程可以进去操作这个数组,如果我用多个数组去维持一个hashmap,每一次进去,我们只对这个数组的一部分进行加锁,这样就减小了锁的粒度。ConcurrentHashMap会维护若干个Segment,每一个Segment都可以理解成是一个小的hashMap,它里面就会获得hashMap的Entry(表),做同步操作的时候,是先定位到这个Segment,然后锁定这一个Segment,执行put,如果有多个线程要来操作,比如说有两个线程,那么这两个线程分别定位到Segment1和Segment2,那么,这时候它们之间的操作是互不影响的。它们可以同时做这个操作,而不需要进行等待,这个竞争相对来说也就小了很多。

这里讲个题外话,讨论一下HashMap和HashSet的关系,HashSet是单列的,HashMap是双列的,有Key和value.在底层一点,HashMap和HashSet之间的关系,其实HashSet内部的实现用的就是一个HashMap。只是它只是用了HashMap的Key,就够了,我这个value部分从来不考虑,我从来不使用它。就把HashMap完全可以当作HashSet用。Key不能重复,完全符合HashSet的要求。所以说,HashSet内部使用的是HashMap.

Java5以后,提供了各种集合相关的同步类。如果你要用map,它提供了并发的HashMap,名叫ConcurrentHashMap.

还提供了ConcurrentSkipListMap, ConcurrentSkipListSet, CopyOnWriteArrayList, CopyOnWriteArraySet.等同步集合。 下面我们来介绍一下这几个类:

ConcurrentSkipListMap

ConcurrentSkipListMap:它实现了SortedMap接口,它是一个排序的map,就是说,往这个map里面存的东西是有顺序的,排序要看比较规则。排序一定要传一个比较器进去的,告诉它排序的比较规则是什么。该map可以根据键的自然顺序进行排序,也可以根据创建映射时所提供的 Comparator 进行排序,具体取决于使用的构造方法。这个就类似于TreeMap. 但是是线程安全的。

ConcurrentSkipListSet

ConcurrentSkipListSet:set 的元素可以根据它们的自然顺序进行排序,也可以根据创建 set 时所提供的 Comparator 进行排序,具体取决于使用的构造方法。这个就类实于TreeSet,但是是线程安全的。

CopyonWriteArrayListCopyonWriteArraySet

接下来CopyOnWriteArrayList, CopyOnWriteArraySet这两个要好好介绍一下。

再说这两个类之前,我们先看一下另外一个问题,就是说,我们说再java5以前的某些集合是线程不安全的,除了线程不安全,他还有另外一个隐患,在集合迭代的过程中不可以执行remove操作,在读的过程中不能进行写操作。当我们执行集合的迭代器的next方法时,会检查一个变量modCount,我们把它当作一个版本号,它会去检查这个版本号是否等于expectedModCount预期的版本号的值,如果不相等会抛出ConcurrentModificationException的异常,当我们刚获得迭代器对象的时候,它们二者是相等的,但是当我们调用了集合的增加,删除,删除所有等修改集合数据的方法之后,modCount的值就会++,因此它的值就改变了。modCount的值等于集合的操作次数,集合操作了几次,这个modelCount就等于几。也就是说,在迭代集合的过程中,不能对集合进行修改。修改的话,后果很严重。当然,如果我们调用的时迭代器的remove方法是不会有问题的,程序正常执行。

public class CollectionModifyExceptionTest {

public static void main(String[] args) {

Collection users = new ArrayList();

users.add(new User("张三", 28));

users.add(new User("李四", 25));

users.add(new User("王五", 31));

Iterator itrUsers = users.iterator();//当得到迭代器的时候,这时modelCount等于3

while (itrUsers.hasNext()) {

System.out.println("aaaa");

User user = (User) itrUsers.next();//当循环回来检查时modelCount与预期的值不符合,抛异常

if ("张三".equals(user.getName())) {

users.remove(user);//执行了这次remove操作之后,此时modelCount等于4

// itrUsers.remove();//调用迭代器的remove方法时没有问题的,不会抛异常,正常执行

} else {

System.out.println(user);

}

}

}

}

public class User implements Cloneable {

private String name;

private int age;

public User(String name, int age) {

this.name = name;

this.age = age;

}

public boolean equals(Object obj) {

if (this == obj) {

return true;

}

if (!(obj instanceof User)) {

return false;

}

User user = (User) obj;

// if(this.name==user.name && this.age==user.age)

if (this.name.equals(user.name) && this.age == user.age) {

return true;

} else {

return false;

}

}

public int hashCode() {

return name.hashCode() + age;

}

public String toString() {

return "{name:'" + name + "',age:" + age + "}";

}

public Object clone() {

Object object = null;

try {

object = super.clone();

} catch (CloneNotSupportedException e) {

}

return object;

}

public void setAge(int age) {

this.age = age;

}

public String getName() {

return name;

}

}

接下来我们要解决集合在迭代的时候不能修改的问题,怎么解决呢?

这里我们把ArrayList对象换成CopyOnWriteArrayList,就可以了。它在我们对集合进行写操作的时候会保留一份拷贝,在remove操作时,它会创建一个新的数组对象,这个新数组对象的引用赋给了集合的数组成员变量,这个新数组中移除了要移除的元素。也就是说,其实它并不担心一边迭代,一边执行写操作。它迭代的是一个不变的对象,而写操作会保留到另一个对象。然后它又是支持并发的。它允许迭代的时候修改集合,并且是线程安全的。在不能或不想进行同步遍历,但又需要从并发线程中排除冲突时,它也很有用。“快照”风格的迭代器方法在创建迭代器时使用了对数组状态的引用。此数组在迭代器的生存期内不会更改,因此不可能发生冲突,并且迭代器保证不会抛出 ConcurrentModificationException。创建迭代器以后,迭代器就不会反映列表的添加、移除或者更改。在迭代器上进行的元素更改操作(remove、set 和 add)不受支持。这些方法将抛出 UnsupportedOperationException。

public class CollectionModifyExceptionTest {

public static void main(String[] args) {

Collection users = new CopyOnWriteArrayList();

// new ArrayList();

users.add(new User("张三", 28));

users.add(new User("李四", 25));

users.add(new User("王五", 31));

Iterator itrUsers = users.iterator();//当得到迭代器的时候,这时modelCount等于3

while (itrUsers.hasNext()) {

System.out.println("aaaa");

User user = (User) itrUsers.next();//当循环回来检查时modelCount与预期的值不符合,抛异常

if ("张三".equals(user.getName())) {

users.remove(user);//执行了这次remove操作之后,此时modelCount等于4

// itrUsers.remove();//这里就不能调用迭代器的remove方法了,否则会抛出异常

} else {

System.out.println(user);

}

}

}

}

转载于:https://my.oschina.net/kangxi/blog/1822504

Java5线程并发库之同步集合相关推荐

  1. Java5线程并发库之LOCK(锁)CONDITION(条件)实现线程同步通信

    为什么80%的码农都做不了架构师?>>>    Lock(锁)&Condition(条件)实现线程同步通信 接下来介绍,java5线程并发库里面的锁.跟锁有关的类和接口主要是 ...

  2. Java5线程并发库之保障变量的原子性操作

    为什么80%的码农都做不了架构师?>>>    java.util.concurrent.atomic 首先我们看java.util.concurrent.atomic包,它主要是提 ...

  3. Java多线程与线程并发库高级应用笔记

    以下内容是学习张老师Java多线程与线程并发库高级应用时所做的笔记,很有用 网络编辑器直接复制Word文档排版有点乱,提供原始文件下载 先看源文件概貌 张孝祥_Java多线程与并发库高级应用 [视频介 ...

  4. 线程并发库和线程池的作用_线程和并发介绍

    线程并发库和线程池的作用 本文是我们名为Java Concurrency Essentials的学院课程的一部分. 在本课程中,您将深入探讨并发的魔力. 将向您介绍并发和并发代码的基础知识,并学习诸如 ...

  5. java基础巩固笔记(5)-多线程之线程并发库

    2019独角兽企业重金招聘Python工程师标准>>> java基础巩固笔记(5)-多线程之线程并发库 标签: java [TOC] 本文主要概述java.util.concurre ...

  6. 线程并发库和线程池的作用_并发–顺序线程和原始线程

    线程并发库和线程池的作用 不久前,我参与了一个项目,该项目的报告流程如下: 用户会要求举报 报告要求将被翻译成较小的部分 基于零件/节的类型的每个零件的报告将由报告生成器生成 组成报告的各个部分将重新 ...

  7. 使用Java线程并发库实现两个线程交替打印的线程题

    背景:是这样的今天在地铁上浏览了以下网页,看到网上一朋友问了一个多线程的问题.晚上闲着没事就决定把它实现出来. 题目: 1.开启两个线程,一个线程打印A-Z,两一个线程打印1-52的数据. 2.实现交 ...

  8. Java5的 线程并发库

    java.util.concurrent.atomic 一个小型工具包,支持单个变量上的无锁线程安全编程. 包含的类: 这些类的相关操作都是原子性的 java.util.concurrent 线程池 ...

  9. java线程并发库之--线程同步工具CountDownLatch用法

    CountDownLatch,一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待. 主要方法 public CountDownLatch(int count); pu ...

  10. java线程并发库之--线程同步工具Exchanger的使用

    Exchanger可以在两个线程之间交换数据,只能是2个线程,他不支持更多的线程之间互换数据.今天我们就通过实例来学习一下Exchanger的用法. Exchanger的简单实例 Exchanger是 ...

最新文章

  1. 10.27 noip模拟试题
  2. [转]IaaS、PaaS、SaaS、CaaS、MaaS五者的区别
  3. 目标跟踪_POI算法
  4. 我的第一个python web开发框架(23)——代码版本控制管理与接口文档
  5. linux中级之ansible配置(roles)
  6. 云南数据中心机房资源介绍、IDC资源分布,机房数据中心建设解决方案
  7. 阿里巴巴矢量图标引入方法
  8. 基于arduino制作激光电子竖琴
  9. NeoKylin中标麒麟系统用户和组的管理命令
  10. 传统蓝牙base on pincode配对以及安全简单配对(Secure Simple Pairing)流程介绍
  11. TIPTOP 智能仓库扫描系统解决方案
  12. 如何更改虚拟光驱与物理光驱的盘符
  13. 搭建K8S dashboard
  14. RocketMQ保姆级教程
  15. 漏极开路 推挽输出方式和弱上拉都什么意思
  16. css伪类渐变,CSS3文字 渐变内发光投影效果_css
  17. zotero+坚果云实现同步
  18. 学习贴:恶意软件类型及本质
  19. 《炬丰科技-半导体工艺》旋转超声雾化液中新型处理GaAs表面湿法清
  20. Win10安装nessus8.10系列

热门文章

  1. 从Ibatis过渡到Mybatis-比较Mybaits较与Ibatis有哪些方面的改进
  2. linux---动静态库编译及使用
  3. linux下目录的基本命令
  4. 防火墙配置十大任务之五,有NAT的两个接口的配置
  5. 应用程序正常初始化(0xc015002)失败解决方法
  6. 黑客获取数据信息的目的和进攻手段及应对之策
  7. datetime对应的jdbc mysql_Java连接MySQL数据库
  8. 为什么手工drop_caches之后cache值并未减少?
  9. SQlite数据库的C编程接口(四) 绑定参数(Bound Parameters) ——《Using SQlite》读书笔记
  10. JM8.6之get_block() 1/4亮度像素内插详述