多线程编程-之并发编程:同步容器

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

同时说说List,Set,Map之间的区别. 

自动扩展的数组: List

重复的数组:set

自动排序的组数 :TreeSet,TreeList,Tree**

而几乎所有的集合都是基于数组来实现的.

因为集合是对数组做的封装,所以,数组永远比任何一个集合要快

但任何一个集合,比数组提供的功能要多

一:数组声明了它容纳的元素的类型,而集合不声明。这是由于集合以object形式来存储它们的元素。

二:一个数组实例具有固定的大小,不能伸缩。集合则可根据需要动态改变大小。

三:数组是一种可读/可写数据结构---没有办法创建一个只读数组。然而可以使用集合提供的ReadOnly方法,以只读方式来使用集合。该方法将返回一个集合的只读版本.

一.为什么会出现同步容器?

 在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提供了同步容器供用户使用。va中的同步容器类

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

  1)Vector、Stack、HashTable 其他都是在这基础上拓展

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

3) 并发容器:ConcurrentHashMap, CopyOnWriteArrayList, ConcurrentLinkedQuerue, ConcurrentSkipListMap, ConcurrentSkipListSet

ConcurrentHashMap代替同步的Map(Collections.synchronized(new HashMap())),众所周知,HashMap是根据散列值分段存储的,同步Map在同步的时候锁住了所有的段,而ConcurrentHashMap加锁的时候根据散列值锁住了散列值锁对应的那段,因此提高了并发性能。ConcurrentHashMap也增加了对常用复合操作的支持,比如"若没有则添加":putIfAbsent(),替换:replace()。这2个操作都是原子操作。

  CopyOnWriteArrayList和CopyOnWriteArraySet分别代替List和Set,主要是在遍历操作为主的情况下来代替同步的List和同步的Set,这也就是上面所述的思路:迭代过程要保证不出错,除了加锁,另外一种方法就是"克隆"容器对象。

  ConcurrentLinkedQuerue是一个先进先出的队列。它是非阻塞队列。

   ConcurrentSkipListMap可以在高效并发中替代SoredMap(例如用Collections.synchronzedMap包装的TreeMap)。

  ConcurrentSkipListSet可以在高效并发中替代SoredSet(例如用Collections.synchronzedSet包装的TreeMap)。

  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 static void main(String [] args) throws InterruptedException{

		//10万次插入list集合
		new Thread(){
			@Override
			public void run() {
				List<Integer> list=new ArrayList<Integer>();
				long begin=System.currentTimeMillis();
				for(int i=0;i<1000000;i++){
					list.add(i);
				}
				long end=System.currentTimeMillis();
				System.out.println("List插入消耗时间:"+(end-begin)+"ms");
			}
		}.start();

		//10万次插入Vector线程同步集合
		 new Thread(){
			 @Override
			public void run() {
				 Vector<Integer> v=new Vector<Integer>();
					long begin1=System.currentTimeMillis();
					for(int i=0;i<1000000;i++){
						v.add(i);
					}
					long end1=System.currentTimeMillis();
					System.out.println("Vector插入消耗时间:"+(end1-begin1)+"ms");
			}
		 }.start();

	}art)+"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");
    }
}

  

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

  另外,由于Vector中的add方法和get方法都进行了同步,因此,在有多个线程进行访问时,如果多个线程都只是进行读取操作,那么每个时刻就只能有一个线程进行读取,其他线程便只能等待,这些线程必须竞争同一把锁。

  因此为了解决同步容器的性能问题,在Java 1.5中提供了并发容器,位于java.util.concurrent目录下,并发容器的相关知识将在下一篇文章中讲述。

2.同步容器是否是安全的吗?

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

public static void main(String[] args) throws InterruptedException {

            for(int i=0;i<10;i++)
                vector.add(i);

            //开启一个线程删除集合元素
            new Thread(){
                public void run() {

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

                };
            }.start();

            //开启另外一个线程读取集合
            new Thread(){
                public void run() {
	                    for(int i=0;i<vector.size();i++)
	                        vector.get(i);

                };
            }.start();

    }

以下是执行结果:

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

不是说好的,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异常,关于这个异常将会在后续文章中讲述。

  但是在并发容器中不会出现这个问题.所以文章只为记录学习过程,不作为任何技术分享.如有错误,请多多谅解.

时间: 2024-10-29 00:32:12

多线程编程-之并发编程:同步容器的相关文章

IO复用、多进程和多线程三种并发编程模型

I/O复用模型 I/O复用原理:让应用程序可以同时对多个I/O端口进行监控以判断其上的操作是否可以进行,达到时间复用的目的.在书上看到一个例子来解释I/O的原理,我觉得很形象,如果用监控来自10根不同地方的水管(I/O端口)是否有水流到达(即是否可读),那么需要10个人(即10个线程或10处代码)来做这件事.如果利用某种技术(比如摄像头)把这10根水管的状态情况统一传达到某一点,那么就只需要1个人在那个点进行监控就行了,而类似与select或epoll这样的多路I/O复用机制就好比是摄像头的功能

【Java并发编程】并发编程大合集

转载自:http://blog.csdn.net/ns_code/article/details/17539599 为了方便各位网友学习以及方便自己复习之用,将Java并发编程系列内容系列内容按照由浅入深的学习顺序总结如下,点击相应的标题即可跳转到对应的文章    [Java并发编程]实现多线程的两种方法    [Java并发编程]线程的中断    [Java并发编程]正确挂起.恢复.终止线程    [Java并发编程]守护线程和线程阻塞    [Java并发编程]Volatile关键字(上)

多线程编程-之并发编程:阻塞队列

在前面几篇文章中,我们讨论了同步容器(Hashtable.Vector),也讨论了并发容器(ConcurrentHashMap.CopyOnWriteArrayList),这些工具都为我们编写多线程程序提供了很大的方便.今天我们来讨论另外一类容器:阻塞队列. 在前面我们接触的队列都是非阻塞队列,比如PriorityQueue.LinkedList(LinkedList是双向链表,它实现了Dequeue接口). 使用非阻塞队列的时候有一个很大问题就是:它不会对当前线程产生阻塞,那么在面对类似消费者

【多线程】Java并发编程:Lock(转载)

原文链接:http://www.cnblogs.com/dolphin0520/p/3923167.html Java并发编程:Lock 在上一篇文章中我们讲到了如何使用关键字synchronized来实现同步访问.本文我们继续来探讨这个问题,从Java 5之后,在java.util.concurrent.locks包下提供了另外一种方式来实现同步访问,那就是Lock. 也许有朋友会问,既然都可以通过synchronized来实现同步访问了,那么为什么还需要提供Lock?这个问题将在下面进行阐述

【多线程】Java并发编程:并发容器之CopyOnWriteArrayList(转载)

原文链接: http://ifeve.com/java-copy-on-write/ Copy-On-Write简称COW,是一种用于程序设计中的优化策略.其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容Copy出去形成一个新的内容然后再改,这是一种延时懒惰策略.从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器,它们是CopyOnWriteArrayList和CopyOnWriteArraySet.CopyOnW

Python3 网络编程和并发编程总结

目录 网络编程 开发架构 OSI七层模型 socket subprocess 粘包问题 socketserver TCP UDP 并发编程 多道技术 并发和并行 进程 僵尸进程和孤儿进程 守护进程 互斥锁 队列 IPC进程间通信 生产者与消费者模型 线程 GIL 多线程与多进程的选择 死锁 递归锁 信号量 线程队列 event事件 进程池与线程池 协程 gevent IO模型 网络编程 开发架构 B/S: browser/server C/S: client/server OSI七层模型 应用层

网络编程与并发编程总结

目录 软件开发架构: C/S架构: B/S架构: 一.网络编程: 1.互联网协议OSI七层协议 2.socket 3.手撸socket套接字模板 4.subprocess(了解) 5.粘包问题 6.struct解决粘包问题 7.上传大文件数据 8.socketserver(现阶段,了解) 二.并发编程 1.并发与并行: 2.进程调度: 3.进程的三个状态: 4.同步与异步: 5.阻塞与非阻塞 7.回收进程的两种条件: 9.互斥锁: 10.队列 11.线程 12.全局解释器锁 14.递归锁 15.

【并发编程】并发编程中你需要知道的基础概念

本博客系列是学习并发编程过程中的记录总结.由于文章比较多,写的时间也比较散,所以我整理了个目录贴(传送门),方便查阅. 并发编程系列博客传送门 多线程是Java编程中一块非常重要的内容,其中涉及到很多概念.这些概念我们平时经常挂在嘴上,但是真的要让你介绍下这些概念,你可能还真的讲不清楚.这篇博客就总结下多线程编程中经常用到的概念,理解这些概念能帮助我们更好地掌握多线程编程. 进程(Process)与线程(Thread) 进程和线程是最常提到的概念了.在linux中,线程与进程最大的区别就是是否共

【Java并发编程】并发编程大合集-值得收藏

http://blog.csdn.net/ns_code/article/details/17539599这个博主的关于java并发编程系列很不错,值得收藏. 为了方便各位网友学习以及方便自己复习之用,将Java并发编程系列内容系列内容按照由浅入深的学习顺序总结如下,点击相应的标题即可跳转到对应的文章    [Java并发编程]实现多线程的两种方法    [Java并发编程]线程的中断    [Java并发编程]正确挂起.恢复.终止线程    [Java并发编程]守护线程和线程阻塞    [Ja