一. 同步容器出现的原因

  在Java的集合容器框架中,主要有四大类别:List、Set、Queue、Map。

  List、Set、Queue接口分别继承了Collection接口,Map本身是一个接口。

  注意Collection和Map是一个顶层接口,而List、Set、Queue则继承了Collection接口,分别代表数组、集合和队列这三大类容器。

  像ArrayList、LinkedList都是实现了List接口,HashSet实现了Set接口,而Deque(双向队列,允许在队首、队尾进行入队和出队操作)继承了Queue接口,PriorityQueue实现了Queue接口。另外LinkedList(实际上是双向链表)实现了了Deque接口。

  像ArrayList、LinkedList、HashMap这些容器都是非线程安全的。

  如果有多个线程并发地访问这些容器时,就会出现问题。

  因此,在编写程序时,必须要求程序员手动地在任何访问到这些容器的地方进行同步处理,这样导致在使用这些容器的时候非常地不方便。

  所以,Java提供了同步容器供用户使用。

二. Java中的同步容器类

在Java中,同步容器主要包括2类:

  1)Vector、Stack、HashTable

  2)Collections类中提供的静态工厂方法创建的类

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

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

  HashTable实现了Map接口,它和HashMap很相似,但是HashTable进行了同步处理,而HashMap没有。

  Collections类是一个工具提供类,注意,它和Collection不同,Collection是一个顶层的接口。在Collections类中提供了大量的方法,比如对集合或者容器进行排序、查找等操作。最重要的是,在它里面提供了几个静态工厂方法来创建同步容器类,如下图所示:

三. 同步容器的缺陷

  从同步容器的具体实现源码可知,同步容器中的方法采用了synchronized进行了同步,那么很显然,这必然会影响到执行性能,另外,同步容器就一定是真正地完全线程安全吗?不一定,这个在下面会讲到。

  我们首先来看一下传统的非同步容器和同步容器的性能差异,我们以ArrayList和Vector为例:

1.性能问题

  我们先通过一个例子看一下Vector和ArrayList在插入数据时性能上的差异:

 public class Test {
public static void main(String[] args) throws InterruptedException {
ArrayList<Integer> list = new ArrayList<Integer>();
Vector<Integer> vector = new Vector<Integer>();
long start = System.currentTimeMillis();
for(int i=0;i<100000;i++)
list.add(i);
long end = System.currentTimeMillis();
System.out.println("ArrayList进行100000次插入操作耗时:"+(end-start)+"ms");
start = System.currentTimeMillis();
for(int i=0;i<100000;i++)
vector.add(i);
end = System.currentTimeMillis();
System.out.println("Vector进行100000次插入操作耗时:"+(end-start)+"ms");
}
}

这段代码上跑出来的结果是:

ArrayList进行100000次插入操作耗时:8ms
Vector进行100000次插入操作耗时:17ms

  进行同样多的插入操作,Vector的耗时是ArrayList的两倍。这只是其中的一方面性能问题上的反映。

  另外,由于Vector中的add方法和get方法都进行了同步,因此,在有多个线程进行访问时,如果多个线程都只是进行读取操作,那么每个时刻就只能有一个线程进行读取,其他线程便只能等待,这些线程必须竞争同一把锁。因此为了解决同步容器的性能问题,在Java 1.5中提供了并发容器,位于java.util.concurrent目录下。

2. 同步容器不一定安全

也有有人认为Vector中的方法都进行了同步处理,那么一定就是线程安全的,事实上这可不一定。看下面这段代码:

 public class Test {
static Vector<Integer> vector = new Vector<Integer>();
public static void main(String[] args) throws InterruptedException {
while(true) {
for(int i=0;i<10;i++)
vector.add(i);
Thread thread1 = new Thread(){
public void run() {
for(int i=0;i<vector.size();i++)
vector.remove(i);
};
};
Thread thread2 = new Thread(){
public void run() {
for(int i=0;i<vector.size();i++)
vector.get(i);
};
};
thread1.start();
thread2.start();
while(Thread.activeCount()>10) { }
}
}
}

运行结果为:

  正如大家所看到的,这段代码报错了:数组下标越界。

  也许有人会问:Vector是线程安全的,为什么还会报这个错?很简单,对于Vector,虽然能保证每一个时刻只能有一个线程访问它,但是不排除这种可能:

  当某个线程在某个时刻执行这句时:

 for(int i=0;i<vector.size();i++)
vector.get(i);

  假若此时vector的size方法返回的是10,i的值为9

  然后另外一个线程执行了这句:

 for(int i=0;i<vector.size();i++)
vector.remove(i);

  将下标为9的元素删除了。

  那么通过get方法访问下标为9的元素肯定就会出问题了。

  因此为了保证线程安全,必须在方法调用端做额外的同步措施,如下面所示:

 public class Test {
static Vector<Integer> vector = new Vector<Integer>();
public static void main(String[] args) throws InterruptedException {
while(true) {
for(int i=0;i<10;i++)
vector.add(i);
Thread thread1 = new Thread(){
public void run() {
synchronized (Test.class) { //进行额外的同步
for(int i=0;i<vector.size();i++)
vector.remove(i);
}
};
};
Thread thread2 = new Thread(){
public void run() {
synchronized (Test.class) {
for(int i=0;i<vector.size();i++)
vector.get(i);
}
};
};
thread1.start();
thread2.start();
while(Thread.activeCount()>10) { }
}
}
}

3. ConcurrentModificationException异常

  在对Vector等容器并发地进行迭代修改时,会报ConcurrentModificationException异常,但是在并发容器中不会出现这个问题。关于这个异常请参考http://www.cnblogs.com/dolphin0520/p/3933551.html

作者:海子

出处:http://www.cnblogs.com/dolphin0520/ 

本博客中未标明转载的文章归作者海子和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

Java并发(5):同步容器的更多相关文章

  1. java并发:同步容器&并发容器

    第一节 同步容器.并发容器 1.简述同步容器与并发容器 在Java并发编程中,经常听到同步容器.并发容器之说,那什么是同步容器与并发容器呢?同步容器可以简单地理解为通过synchronized来实现同 ...

  2. Java并发编程--同步容器

    BlockingQueue 阻塞队列 对于阻塞队列,如果BlockingQueue是空的,从BlockingQueue取东西的操作将会被阻断进入等待状态,直到BlockingQueue进了东西才会被唤 ...

  3. Java 并发 线程同步

    Java 并发 线程同步 @author ixenos 同步 1.异步线程本身包含了执行时需要的数据和方法,不需要外部提供的资源和方法,在执行时也不关心与其并发执行的其他线程的状态和行为 2.然而,大 ...

  4. java多线程总结-同步容器与并发容器的对比与介绍

    1 容器集简单介绍 java.util包下面的容器集主要有两种,一种是Collection接口下面的List和Set,一种是Map, 大致结构如下: Collection List LinkedLis ...

  5. Java并发编程:CopyOnWrite容器的实现

    Java并发编程:并发容器之CopyOnWriteArrayList(转载) 原文链接: http://ifeve.com/java-copy-on-write/ Copy-On-Write简称COW ...

  6. Java线程安全同步容器

    线程安全同步容器(使用 synchronized关键字) 1.ArrayList->Vector,Stack 2.HashMap->HashTable(key.value不能为null) ...

  7. Java并发队列与容器

    [前言:无论是大数据从业人员还是Java从业人员,掌握Java高并发和多线程是必备技能之一.本文主要阐述Java并发包下的阻塞队列和并发容器,其实研读过大数据相关技术如Spark.Storm等源码的, ...

  8. Java并发与同步

    Java中并发的形式无非是多线程和多进程两种形式.这两种形式都是能够利用多核来发挥计算能力的. 先说并发: 多进程意味着同一时候执行多个JVM.这个代价通常比多线程高,每一个JVM都有自己的堆栈.都要 ...

  9. Java并发框架——同步状态的管理

    整个AQS框架核心功能都是围绕着其32位整型属性state进行,一般可以说它表示锁的数量,对同步状态的控制可以实现不同的同步工具,例如闭锁.信号量.栅栏等等.为了保证可见性此变量被声明为volatil ...

  10. Java并发——线程同步Volatile与Synchronized详解

    0. 前言 转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52370068 面试时很可能遇到这样一个问题:使用volatile修饰in ...

随机推荐

  1. $.messager.show扩展:指定位置显示

    扩展了个$.messager.showBySite,根据舍得的位置显示$.messager.show.代码如下: /** * 指定位置显示$.messager.show * options $.mes ...

  2. EasyUI DataGrid 多级表头设置

    使用EasyUI做一个报表统计,需要合并表头为多级表头,核心代码如下: $('#dg').datagrid({ url:'datagrid_data.action', fit : true, fitC ...

  3. C++ 类的继承五(类继承中的static关键字)

    //类继承中的static关键字 #include<iostream> using namespace std; /* 派生类中的静态成员 基类定义的静态成员,将被所有派生类共享 根据静态 ...

  4. 设置grid高度

    $("#jqxSalaryCalculation").jqxGrid({ height: $("#jqxTree").height() - 73 });

  5. ASP.NET动态增加HTML元素的方法实例小结

    本文实例讲述了ASP.NET动态增加HTML元素的方法.分享给大家供大家参考,具体如下: 在使用asp.net进行web开发的时候页面中的<head></head>中的信息可以 ...

  6. 【转】MySQL常用命令总结

    http://blog.csdn.net/qq_33850438/article/details/52144686 MySQL常用的基本操作,以下都是MySQL 5.0下测试通过首先说明下,记住在每个 ...

  7. 利用Fiddler或Charles进行mock数据

    使用场景:服务器数据不符合测试条件时,我们可以通过在本地创建虚拟数据来打到测试用例所描述的条件. fiddler使用方法 1.首先在本地创建txt数据:将抓到的response中的json数据拷贝到记 ...

  8. 【BZOJ3714】[PA2014]Kuglarz 最小生成树

    [BZOJ3714][PA2014]Kuglarz Description 魔术师的桌子上有n个杯子排成一行,编号为1,2,…,n,其中某些杯子底下藏有一个小球,如果你准确地猜出是哪些杯子,你就可以获 ...

  9. Web移动端使用localStorage缓存Js和css文件 | 强制不要缓存

    1.尽量不把js放在onload事件中,而是放在由用户主动触发的事件 2.加时间戳,时间不同则会加载js而非使用缓存 强制不要缓存: <meta http-equiv=Cache-Control ...

  10. Windows(7)上那些好用的软件及优化技巧(原创)

    *本文为原创内容,转载请注明作者和出处:www.cnblogs.com/wang1024 软件篇 注:以下软件在百度直接搜索软件名均可找到官网,直接官网下载即可 大众的软件哪个好: 杀毒软件专题 基于 ...