一、为什么会出现同步容器

  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中的同步容器

  1)Vector,Stack,HashTable

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

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

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

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

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

三、同步容器的缺陷  

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

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

进行同样多的插入操作,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异常,关于这个异常将会在后续文章中讲述。

Java同步容器的更多相关文章

  1. JAVA同步容器和并发容器

    同步容器类 同步容器类的创建 在早期的JDK中,有两种现成的实现,Vector和Hashtable,可以直接new对象获取: 在JDK1.2中,引入了同步封装类,可以由Collections.sync ...

  2. Java 同步容器和并发容器

      同步容器(在并发下进行迭代的读和写时并不是线程安全的)   Vector.Stack.HashTable   Collections类的静态工厂方法创建的类(如Collections.synchr ...

  3. Java同步容器总结

    <0>StringBuffer适用于多线程场景,StringBuilder适用于字符串拼接[堆栈封闭] `Vector`实现`List`接口,底层和`ArrayList`类似,但是`Vec ...

  4. 转:java多线程--同步容器

    java同步容器 在Java的集合容器框架中,主要有四大类别:List.Set.Queue.Map.List.Set.Queue接口分别继承了Collection接口,Map本身是一个接口.注意Col ...

  5. Java并发编程:同步容器

    Java并发编程:同步容器 为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器.并发容器.阻塞队列.Synchronizer(比如CountDownLatch). ...

  6. 【JAVA并发编程实战】3、同步容器

    同步容器包括Vector和Hashtable,还有一些由Collections.synchronizedXxx等工厂方法创建的 1.同步容器类的问题 同步容器类都是线程安全的,但是有些时候还是要客户端 ...

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

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

  8. Java并发——同步容器与并发容器

    同步容器类 早期版本的JDK提供的同步容器类为Vector和Hashtable,JDK1.2 提供了Collections.synchronizedXxx等工程方法,将普通的容器继续包装.对每个共有方 ...

  9. 【转】Java并发编程:同步容器

    为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器.并发容器.阻塞队列.Synchronizer(比如CountDownLatch).今天我们就来讨论下同步容器. ...

随机推荐

  1. 初识shell编程

    1.shell编程之为什么学.怎么学 为什么学shell编程 Linux系统批量管理 提升工作效率,减少重复工作 学好shell编程所需要的基础知识 熟悉使用vim编辑器 熟悉SSH终端 熟练掌握Li ...

  2. .NET 开源项目 Polly 介绍

    今天介绍一个 .NET 开源库:Polly,它是支持 .NET Core 的,目前在 GitHub 的 Star 数量已经接近 5 千,它是一个强大且实用的 .NET 库. Polly 介绍 官方对 ...

  3. Oracle伪列(ROWNUM)的使用

    先看一个题:查询emp表的信息,显示前5行数据,这时候我们就需要使用伪列(rownum)的概念. rownum在数据表并不是一个真实的列,其实每一行应该都有一个行号,这个伪列就是用来记录这个行号的,这 ...

  4. Github经理和员工开发

    Git简介 Git是目前世界上最先进的分布式版本控制系统 git的两大特点: 版本控制:可以解决多人同时开发的代码问题,也可以解决找回历史代码的问题 分布式:Git是分布式版本控制系统,同一个Git仓 ...

  5. D3.js 入门学习(一)

    一.安装D3.js 1.网络连接 <script src="https://d3js.org/d3.v4.min.js"></script> 2.命令行安装 ...

  6. css实现多行文本溢出显示省略号(…)

    WebKit浏览器或移动端的页面在WebKit浏览器或移动端(绝大部分是WebKit内核的浏览器)的页面实现比较简单,可以直接使用WebKit的CSS扩展属性(WebKit是私有属性)-webkit- ...

  7. ABP中模块初始化过程(二)

    在上一篇介绍在StartUp类中的ConfigureService()中的AddAbp方法后我们再来重点说一说在Configure()方法中的UserAbp()方法,还是和前面的一样我们来通过代码来进 ...

  8. c提高第三次作业

    1. char buf[] = "abcdef"; //下面有啥区别? const char *p = buf; //p指向的内存不能变 char const *p = buf; ...

  9. Thymeleaf的超链接与AJAX的跳转问题

    //th:href :超链接<a th:href="@{/list}"></a>//可以在其他页面跳转yt <form id="msform ...

  10. 基于stm32智能车的设计(ucosiii)---北京之行

    实物演示视频:https://v.youku.com/v_show/id_XMzc3MDE3NjMyNA==.html?x&sharefrom=android&sharekey=172 ...