1 容器集简单介绍

java.util包下面的容器集主要有两种,一种是Collection接口下面的List和Set,一种是Map,

大致结构如下:

  • Collection

    • List

      • LinkedList
      • ArrayList
      • Vector
        • Stack
    • Set
      • HashSet
      • TreeSet
      • LinkedSet
  • Map

    • Hashtable
    • HashMap
    • WeakHashMap

2 同步容器

同步容器也叫线程安全容器,是通过syncrhoized关键字对线程不安全的操作进行加锁来保证线程安全的

其中同步容器主要包括:

1.Vector、Stack、HashTable

2.Collections 工具类中提供的同步集合类

Collections类是一个工具类,相当于Arrays类对于Array的支持,Collections类中提供了大量对集合或者容器进行排序、查找的方法。它还提供了几个静态方法来创建同步容器类:

3 并发容器

java.util.concurrent提供了多种线程安全容器,大多数是使用系统底层技术实现的线程安全,也叫并发容器,类似native。Java8中使用CAS。

4 案例讲解

这里主要介绍一些常见的同步容器和并发容器,通过案例输出结果对比进行介绍

我大致分为了三类Map/Set,List,Queue来进行讲解,但一个Map/Set,只介绍了Map,因为在java的设计中,Set就是Map,说白了就是只有Key没有Value的Map,好了,现在开始进入正题

4.1 Map/Set

代码中new了三个Map,HashTable,ConcurrentHashMap,ConcurrentSkipListMap比较每个map的运行效率,起100个线程向map中存放10000条随机数,并通过门闩CountDownLatch控制运行状态,输出运行时间

/**
* 并发容器 - ConcurrentMap
*/
package com.bernardlowe.concurrent.t06; import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.CountDownLatch; public class Test_01_ConcurrentMap { public static void main(String[] args) {
final Map<String, String> map = new Hashtable<>();
// final Map<String, String> map = new ConcurrentHashMap<>();
// final Map<String, String> map = new ConcurrentSkipListMap<>();
final Random r = new Random();
Thread[] array = new Thread[100];
final CountDownLatch latch = new CountDownLatch(array.length); long begin = System.currentTimeMillis();
for(int i = 0; i < array.length; i++){
array[i] = new Thread(new Runnable() {
@Override
public void run() {
for(int j = 0; j < 10000; j++){
map.put("key"+r.nextInt(100000000), "value"+r.nextInt(100000));
}
latch.countDown();
}
});
}
for(Thread t : array){
t.start();
}
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("执行时间为 : " + (end-begin) + "毫秒!");
} }

Hashtable结果:

ConcurrentHashMap结果:

ConcurrentSkipListMap结果:

ConcurrentHashMap的底层是哈希实现的同步Map(Set)

ConcurrentSkipListMap内部是SkipList(跳表)结构实现的非阻塞读/写/删除 的 Map,它的value是有序存储的, 而且其内部是由纵横链表组成,在JDK1.8中,ConcurrentHashMap的性能和存储空间要优于ConcurrentSkipListMap

为了让测试数据结果对比更加直观,我这里故意将生成的随机数调的比较大。这里需要注意一下,在测试的时候,如果机器性能比较好,可能结果会出现误差,因为System.currentTimeMillis(),这个方法调用了个native方法,获取的时间精度会依赖于操作系统的实现机制,具体为什么,可以看看这篇文章http://blog.sina.com.cn/s/blog_6b8bd9d80101fe8t.html。但我按照文档的办法将System.currentTimeMillis()改为System.nanoTime(),发现并没有解决这个问题,可能是因为并没有达到纳秒级别吧。

4.2 List

下面代码与4.1的代码类似,也是new了三个List,ArrayList,Vector,CopyOnWriteArrayList,起100个线程向map中存放1000条随机数,并通过门闩CountDownLatch控制运行状态,输出运行时间和最后list的的长度。由于ArrayList是线程不安全,在多线程执行的时候,需要try{}catch{},否则会因为数组越界而报错,因为ArrayList底层是一个长度动态扩展的数组

/**
* 并发容器 - CopyOnWriteList
*/
package com.bernardlowe.concurrent.t06; import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Vector;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch; public class Test_02_CopyOnWriteList { public static void main(String[] args) {
final List<String> list = new ArrayList<String>(); // 线程不安全
// final List<String> list = new Vector<>(); // 线程安全
// final List<String> list = new CopyOnWriteArrayList<>(); // 线程安全
final Random r = new Random();
Thread[] array = new Thread[100];
final CountDownLatch latch = new CountDownLatch(array.length); long begin = System.currentTimeMillis();
for(int i = 0; i < array.length; i++){
array[i] = new Thread(new Runnable() {
@Override
public void run() {
for(int j = 0; j < 1000; j++){
try {
list.add("value" + r.nextInt(100000));
} catch (Exception e) { }
}
latch.countDown();
}
});
}
for(Thread t : array){
t.start();
}
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("执行时间为 : " + (end-begin) + "毫秒!");
System.out.println("List.size() : " + list.size());
} }

ArrayList结果:因为ArrayList是线程不安全的,所以在多线程环境中,可能会丢失数据

Vector结果:

CopyOnWriteArrayList结果:

CopyOnWriteArrayList是读写分离的,写时复制出一个新的数组,完成插入、修改或者移除操作后将新数组赋值给array,读取时直接读取最新的数组,所以在写操作时,效率非常低(虽然写比较慢,但它在删除数组头和尾还是很快的)

从上面三个结果可以看出,CopyOnWriteArrayList虽然保证了线程安全,但它的写操作效率太低了,但相比Vector,并发安全且性能比Vector好,Vector是增删改查方法都加了synchronized,保证同步,但是每个方法执行的时候都要去获得锁,性能就会大大下降,而CopyOnWriteArrayList 只是在增删改上加锁,但是读不加锁,在读方面的性能就好于Vector,CopyOnWriteArrayList支持读多写少的并发情况,所以CopyOnWriteArrayList是不会存在脏读问题的

4.3 Queue

这一节主要介绍一些并发队列的常用api

4.3.1 ConcurrentLinkedQueue

基础链表同步队列

peek() -> 查看queue中的首数据

poll() -> 获取queue中的首数据

/**
* 并发容器 - ConcurrentLinkedQueue
* 队列 - 链表实现的。
*/
package com.bernardlowe.concurrent.t06; import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue; public class Test_03_ConcurrentLinkedQueue { public static void main(String[] args) {
Queue<String> queue = new ConcurrentLinkedQueue<>(); //向队列中增加10个数据
for(int i = 0; i < 10; i++){
queue.offer("value" + i);
} System.out.println(queue);
System.out.println(queue.size()); // peek() -> 查看queue中的首数据,
System.out.println("首数据 " + queue.peek());
System.out.println("队列长度 "+ queue.size());
System.out.println("===================");
// poll() -> 获取queue中的首数据
System.out.println("首数据 " + queue.peek());
System.out.println("队列长度 "+ queue.size());
} }

结果:

4.3.2 阻塞队列LinkedBlockingQueue

阻塞队列,队列容量不足自动阻塞,队列容量为0自动阻塞。

put & take - 自动阻塞

put自动阻塞, 队列容量满后,自动阻塞

take自动阻塞方法, 队列容量为0后,自动阻塞

/**
* 并发容器 - LinkedBlockingQueue
* 阻塞容器。
*/
package com.bernardlowe.concurrent.t06; import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit; public class Test_04_LinkedBlockingQueue { final BlockingQueue<String> queue = new LinkedBlockingQueue<>();
final Random r = new Random(); public static void main(String[] args) {
final Test_04_LinkedBlockingQueue t = new Test_04_LinkedBlockingQueue(); new Thread(new Runnable() {
@Override
public void run() {
while(true){
try {
t.queue.put("value"+t.r.nextInt(1000));
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "producer").start(); for(int i = 0; i < 3; i++){
new Thread(new Runnable() {
@Override
public void run() {
while(true){
try {
System.out.println(Thread.currentThread().getName() +
" - " + t.queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "consumer"+i).start();
}
} }

结果:

结果就是一个简单的生产者消费者

4.3.3 BlockingQueue

底层数组实现的有界队列,当容量不足的时候,有阻塞能力,根据调用API(add/put/offer)不同,有不同特性

这里主要介绍三个api方法add,put,offer

  • add方法在容量不足的时候,抛出异常。
  • put方法在容量不足的时候,阻塞等待。
  • offer方法

    单参数offer方法,不阻塞。容量不足的时候,返回false。当前新增数据操作放弃。

    三参数offer方法(offer(value,times,timeunit)),容量不足的时候,阻塞times时长(单位为timeunit),如果在阻塞时长内,有容量空闲,新增数据返回true。如果阻塞时长范围内,无容量空闲,放弃新增数据,返回false。

/**
* 并发容器 - ArrayBlockingQueue
* 有界容器。
*/
package com.bernardlowe.concurrent.t06; import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit; public class Test_05_ArrayBlockingQueue { final BlockingQueue<String> queue = new ArrayBlockingQueue<>(3); public static void main(String[] args) {
final Test_05_ArrayBlockingQueue t = new Test_05_ArrayBlockingQueue(); for(int i = 0; i < 5; i++){ // 1.add method
System.out.println("add method : " + t.queue.add("value"+i)); // 2.put method
// try {
// t.queue.put("put"+i);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println("put method : " + i); // 3.offer method
// System.out.println("offer method : " + t.queue.offer("value"+i));
// try {
// System.out.println("offer method : " +
// t.queue.offer("value"+i, 1, TimeUnit.SECONDS));
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
} System.out.println(t.queue);
} }

add方法结果:容量不足的时候,抛出异常

put方法结果:容量不足的时候,阻塞等待

单/多参数offer方法结果:

单参数offer:容量不足,直接返回结果,不阻塞

多参数offer:容量不足,阻塞

4.3.4 延时队列DelayQueue

延时队列。根据比较机制,实现自定义处理顺序的队列。常用于定时任务。

如:定时关机。

具体示例代码如下

/**
* 并发容器 - DelayQueue
*/
package com.bernardlowe.concurrent.t06; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit; public class Test_06_DelayQueue { static BlockingQueue<MyTask_06> queue = new DelayQueue<>(); public static void main(String[] args) throws InterruptedException {
long value = System.currentTimeMillis();
MyTask_06 task1 = new MyTask_06(value + 2000);
MyTask_06 task2 = new MyTask_06(value + 1000);
MyTask_06 task3 = new MyTask_06(value + 3000);
MyTask_06 task4 = new MyTask_06(value + 2500);
MyTask_06 task5 = new MyTask_06(value + 1500); queue.put(task1);
queue.put(task2);
queue.put(task3);
queue.put(task4);
queue.put(task5); System.out.println(queue);
System.out.println(value);
for(int i = 0; i < 5; i++){
System.out.println(queue.take());
}
} } class MyTask_06 implements Delayed { private long compareValue; public MyTask_06(long compareValue){
this.compareValue = compareValue;
} /**
* 比较大小。自动实现升序
* 建议和getDelay方法配合完成。
* 如果在DelayQueue是需要按时间完成的计划任务,必须配合getDelay方法完成。
*/
@Override
public int compareTo(Delayed o) {
return (int)(this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
} /**
* 获取计划时长的方法。
* 根据参数TimeUnit来决定,如何返回结果值。
*/
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(compareValue - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
} @Override
public String toString(){
return "Task compare value is : " + this.compareValue;
} }

结果:

4.3.5 转移队列LinkedTransferQueue

这里主要是两个方法的区别,add和transfer

  • add - 队列会保存数据,不做阻塞等待。
  • transfer - 是TransferQueue的特有方法。必须有消费者(take()方法的调用者)。
/**
* 并发容器 - LinkedTransferQueue
* 转移队列
*/
package com.bernardlowe.concurrent.t06; import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TransferQueue; public class Test_07_TransferQueue { TransferQueue<String> queue = new LinkedTransferQueue<>(); public static void main(String[] args) {
final Test_07_TransferQueue t = new Test_07_TransferQueue(); /*new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " thread begin " );
System.out.println(Thread.currentThread().getName() + " - " + t.queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "output thread").start(); try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
} try {
t.queue.transfer("test string");
} catch (InterruptedException e) {
e.printStackTrace();
}*/ new Thread(new Runnable() { @Override
public void run() {
try {
t.queue.transfer("test string");
// t.queue.add("test string");
System.out.println("add ok");
} catch (Exception e) {
e.printStackTrace();
}
}
}).start(); try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
} new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " thread begin " );
System.out.println(Thread.currentThread().getName() + " - " + t.queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "output thread").start(); } }

这里的transfer()和take()都是阻塞方法,take先请求接收数据或者transfer先发送数据,都会进行阻塞等待。

举个例子,transfer()就相当与手机打电话,当A给B打电话,B必须接收到电话信号接听才能进行通话,否则A会一直等待

add()就相当于A给B发短信,短信已经存到了运营商那边,等待B接收,不管发短信时B是否在线

4.3.6 SynchronousQueue

该队列一个容量为0的队列,是一个特殊的TransferQueue,它和TransferQueue很像,但这个队列必须要有消费线程才行

又两个方法add,put

add方法,无阻塞。若没有消费线程阻塞等待数据,则抛出异常。

put方法,有阻塞。若没有消费线程阻塞等待数据,则阻塞。

/**
* 并发容器 - SynchronousQueue
*/
package com.bernardlowe.concurrent.t06; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit; public class Test_08_SynchronusQueue { BlockingQueue<String> queue = new SynchronousQueue<>(); public static void main(String[] args) {
final Test_08_SynchronusQueue t = new Test_08_SynchronusQueue(); new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " thread begin " );
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " - " + t.queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "output thread").start(); /*try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
// t.queue.add("test add");
try {
t.queue.put("test put");
} catch (InterruptedException e) {
e.printStackTrace();
} System.out.println(Thread.currentThread().getName() + " queue size : " + t.queue.size());
} }

t.queue.add("test add");的注释打开,t.queue.put("test put");加上注释

add方法异常结果: 因为它是一个容量为0的队列

java多线程总结-同步容器与并发容器的对比与介绍的更多相关文章

  1. Java多线程之同步集合和并发集合

    Java多线程之同步集合和并发集合 不管是同步集合还是并发集合他们都支持线程安全,他们之间主要的区别体现在性能和可扩展性,还有他们如何实现的线程安全. 同步集合类 Hashtable Vector 同 ...

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

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

  3. Java并发—同步容器和并发容器

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

  4. Java多线程的同步控制记录

    Java多线程的同步控制记录 一.重入锁 重入锁完全可以代替 synchronized 关键字.在JDK 1.5 早期版本,重入锁的性能优于 synchronized.JDK 1.6 开始,对于 sy ...

  5. Java多线程编程(同步、死锁、生产消费者问题)

    Java多线程编程(同步.死锁.生产消费): 关于线程同步以及死锁问题: 线程同步概念:是指若干个线程对象并行进行资源的访问时实现的资源处理保护操作: 线程死锁概念:是指两个线程都在等待对方先完成,造 ...

  6. 【Java并发编程二】同步容器和并发容器

    一.同步容器 在Java中,同步容器包括两个部分,一个是vector和HashTable,查看vector.HashTable的实现代码,可以看到这些容器实现线程安全的方式就是将它们的状态封装起来,并 ...

  7. Java并发编程原理与实战三十三:同步容器与并发容器

    1.什么叫容器? ----->数组,对象,集合等等都是容器.   2.什么叫同步容器? ----->Vector,ArrayList,HashMap等等.   3.在多线程环境下,为什么不 ...

  8. java 普通容器,同步容器,并发容器,同步工具

    同步容器,如HashTable,提供独占访问. 并发容器,ConcurrentHashMap,有着更好的并发性能,但是不能独占访问. --putIfAbsent 同步工具: 闭锁:CountDownL ...

  9. Java并发(9)- 从同步容器到并发容器

    引言 容器是Java基础类库中使用频率最高的一部分,Java集合包中提供了大量的容器类来帮组我们简化开发,我前面的文章中对Java集合包中的关键容器进行过一个系列的分析,但这些集合类都是非线程安全的, ...

随机推荐

  1. ML:吴恩达 机器学习 课程笔记(Week3~4)

    Logistic Regression Regularization Neural Networks: Representation

  2. win7如何开启快速启动栏

    设置步骤如下: 1.右键任务栏空白区域,检查是否解除锁定任务栏,需解锁: 2.右键任务栏空白区域,点击工具栏---新建工具栏: 3.选择C:\Users\Administrator\AppData\R ...

  3. 深入windows的关机消息截获-从XP到Win7的变化(在XP中程序可以阻止关机,但是在Win7中程序无法阻止关机,可Block的时间从1秒调到了5秒) good

    之前写了一个软件用于实验室的打卡提醒,其中一个重要的功能是在关机之前提醒当天晚上是否已经打卡.之前我是在WM_ENDSESSION中弹出一个模态对话框来提醒,在XP中基本工作正常,在Win7中大多数时 ...

  4. Qt官方开发环境生成的exe发布方式--使用windeployqt

    Qt 官方开发环境使用的动态链接库方式,在发布生成的exe程序时,需要复制一大堆 dll,如果自己去复制dll,很可能丢三落四,导致exe在别的电脑里无法正常运行.因此 Qt 官方开发环境里自带了一个 ...

  5. 高性能高并发网站架构,教你搭建Redis5缓存集群

    一.Redis集群介绍 Redis真的是一个优秀的技术,它是一种key-value形式的NoSQL内存数据库,由ANSI C编写,遵守BSD协议.支持网络.可基于内存亦可持久化的日志型.Key-Val ...

  6. 对SpringMVC架构进行工程拆分遇到的问题总结

    经过一个月的开发,一个单一的SpringMVC教育类创业项目正式上线,随着时间的推移,业务流量逐渐增大,最近对这个单一的工程从零进行SOA分布式改造,改造包括dubbo改造,集成化部署.高可用集群,负 ...

  7. 用composer安装php代码(以安装phpmailer为例)

    1.安装composer.exe软件 2.下载composer.phar 3.创建composer.json文件 { "require": { "php": & ...

  8. 【React】react学习笔记03-React组件对象的三大属性-state

    今天晚上学习了React中state的使用,特做此记录,对于学习的方式,博主仍然推荐直接复制完整代码,对着注释观察现象!: 上文中,我列举了两种React自定义组件的声明,这里我拿方式二进行举例: / ...

  9. 【Shell学习笔记3》实践项目自动部署脚本】shell中获取返回值、获取当前sh文件路径

    原创部分: 1.获取返回值 #This is a shell to Deploy Project #!/bin/bashcheck_results=`ps -ef | grep "java& ...

  10. Appcan 日期控件

    某个页面的onclick事件 <div id="topSendDate" class="ub ub-f1" onclick="appcan.wi ...