提升--12---并发容器历史
Posted 高高for 循环
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了提升--12---并发容器历史相关的知识,希望对你有一定的参考价值。
容器
java容器分2大类
- 第一大类:Collection叫集合
- 第二大类:Map
Collection集合
第一大类Collection叫集合。集合的意思是不管你这个容器是什么结构你可以把一个元素一个元素的往里面扔;
Map
第二大类Map:Map是一对一对的往里扔,kv结构。其实Map来说可以看出是Collection一个特殊的变种,你可以把一对对象看成一个entry对象,所以这也是一个整个对象。容器就是装一个一个对象的,这么一些个集合。严格来讲数组也属于容器。
从数据结构角度分:
从数据结构角度来讲在物理上的这种存储的数据结构其实只有两种,
- 一种是连续存储的数组Array
- 另一种就是非连续存储的一个指向另外一个的链表。在逻辑结构那就非常非常多了。
Collection 分三大类
- list
- set
- Queue
Queue队列
队列就是一对一队的,往这个队列里取数据的时候它和这个List、Set都不一样。大家知道List取的时候如果是Array的话还可以取到其中一个的。Set主要和其他的区别就是中间是唯一的,不会有重复元素,这个它最主要的区别。Queue实现了一个什么逻辑呢,实际上就是一个队列,
队列什么概念,有进有出,那么在这个基础之上它实现了很多的多线程的访问方法(比如说put阻塞式的放、take阻塞式的取),这个是在其他的List、Set里面都是没有的。队列最主要的原因是为了实现任务的装载的这种取和装这里面最重要的就是是叫做阻塞队列,它的实现的初衷就是为了线程池、高并发做准备的。原来的这些是容器是普通的为了装东西做准备的。
list,set和队列 Queue的区别
- 队列 Queue实现了很多的多线程的访问方法(比如说put阻塞式的放、take阻塞式的取),这个是在其他的List、Set里面都是没有的。
- 初衷不同:队列 它的实现的初衷就是为了线程池、高并发做准备的。原来的这些list,set容器是普通的为了装东西做准备的。
JDK容器发展历史
- java1.0-----Vector、Hashtable(synchronized)
- list、set 、HashMap(没有锁)
- Collections工具类------SynchronizedList、synchronizedMap
- Queue、ConcurrentHashMap(高并发)
1. Vector、Hashtable
Vector 和 Hashtable 自带锁,基本不用,大家记住这个结论。
- 最开始java1.0容器里只有两个,第一个叫Vector可以单独的往里扔,还有一个是Hashtable是可以一对一对往里扔的。Vector相对于实现了List接口,Hashtable实现了Map接口。但是这个两个容器在1.0设计的时候稍微有点问题,这两个容器设计成了所有方法默认都是加synchronized的,这是它最早设计不太合理的地方。多数的时候我们多数的程序只有一个线程在工作,所以在这种情况下你完全是没有必要加synchronized,因此最开始的时候设计的性能比较差,
2. list、set 、HashMap
- 在Hashtable之后又添加了HashMap,HashMap就是完全的没有加锁,一个是二话没说就加锁,一个是完全没有加锁。那这两个除了这个加锁区别之外 还有其他的一些源码上的区别,所以Sun在那个时候就在这个HashMap的基础之上又添加了一个,说你用的这个新的HashMap比原来Hashtable好用,但是HashMap没有那些锁的东西,
3. SynchronizedList、synchronizedMap
Map<> map = Collections.synchronizedMap(new HashMap<>());
- 那么怎么才可以让这个HashMap既可以用于这些不需要锁的环境,有可以用于需要锁的环境呢? 所以它又添加了一个方法叫做Collections相当于这个容器的工具类,这个工具类里有一个方法叫synchronizedMap,这个方法会把它变成加锁的版本。所以,HashMap有两个版本。
4. ConcurrentHashMap
map容器-----效率对比
那个效率高那个效率低不要想当然,务必要写程序来测试(压测)
准备数据:100万个UUID对、100个线程
- 容器装的都是Key,Value对,一对一对的。这个Key是UUID,Value也是UUID等于new一个Hashtable出来,这里面的UUID到底有多少个呢,我定义了两个常量,这两个常量在一个单独的类里,这个类叫Constants:100万个UUID对内容要装到容器里,会有100个线程,将来我们访问的时候会有100个线程来模拟。
public class Constants {
public static final int COUNT = 1000000;
public static final int THREAD_COUNT = 100;
}
程序设计:
- 看程序,我先new出100万个Key和100万个Value来,然后把这些东西装到数组里面去,for循环(int i = 0; i < count;i++)。为什么先把这些UUID对准备好而不是我们装的时候现场生成?原因是我们写这个测试用例的时候前后用的是一样的,你往Hashtable里头装的时候也得是这100万对,同样的内容,但你要是每次都生成是不一样的内容,在这种情况下你测试就会有一些干扰因素,所以我们先准备好在往里扔。
- 后面,写了一个线程类,叫MyThread,从Thread继承,start,gap是每个线程负责往里面装多少。线程开始往里面扔。在看后面主程序代码,记录起始时间,new出来一个线程数组,这个线程数据总共有100个线程,给它做初始化。由于你需要指定这个start值,所以这个MyThread启动的时候i乘以count(总而言之就是这个起始的值不一样,第一个线程是从0开始,第二个是从100000个开始)没用到也没关系,用到了就把他记录下来是一个好的习惯。然后让每一个线程启动,等待每一个线程结束,最后计算这个线程时间。
- 现在我有一个Hashtable,里面装的是一对一对的内容,现在我们起了100个线程,这个100个线程去Key,Value取数据,一个线程取1万个数据,一共100万个数据,100个线程,每个线程取1万个数据往里插,整个程序模拟的是这么一个情形
TestHashtable
package c_023_02_FromHashtableToCHM;
import java.util.Hashtable;
import java.util.UUID;
public class T01_TestHashtable {
static Hashtable<UUID, UUID> m = new Hashtable<>();
static int count = Constants.COUNT;
static UUID[] keys = new UUID[count];
static UUID[] values = new UUID[count];
static final int THREAD_COUNT = Constants.THREAD_COUNT;
static {
for (int i = 0; i < count; i++) {
keys[i] = UUID.randomUUID();
values[i] = UUID.randomUUID();
}
}
static class MyThread extends Thread {
int start;
int gap = count/THREAD_COUNT;
public MyThread(int start) {
this.start = start;
}
@Override
public void run() {
for(int i=start; i<start+gap; i++) {
m.put(keys[i], values[i]);
}
}
}
public static void main(String[] args) {
long start = System.currentTimeMillis();
Thread[] threads = new Thread[THREAD_COUNT];
for(int i=0; i<threads.length; i++) {
threads[i] =
new MyThread(i * (count/THREAD_COUNT));
}
for(Thread t : threads) {
t.start();
}
for(Thread t : threads) {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
long end = System.currentTimeMillis();
System.out.println("===Hashtable.put() 所花费时间===: "+ (end - start));
System.out.println("===m.size()===: "+m.size());
//-----------------------------------
start = System.currentTimeMillis();
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(()->{
for (int j = 0; j < 10000000; j++) {
m.get(keys[10]);
}
});
}
for(Thread t : threads) {
t.start();
}
for(Thread t : threads) {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
end = System.currentTimeMillis();
System.out.println("===Hashtable.get() 所花费时间===: "+(end - start));
}
}
HashMap
- 我们来看这HashMap,同学们想一下这个HashMap往里头插会不会有问题。因为HashMap没有锁啊,线程不安全,这个就没有意义了,这只是为了程序的完整性留在这,他虽然速度比较快,但是数据会出问题,还各种各样的报异常。主要是因为它内部会把这个变成TreeNode,我们先不去细究它,总而言之HashMap这个东西你往里扔的时候,由于它内部没有锁,所以你多线程访问的时候会出问题,这个你往里插的时候就没有实际意义了。
package c_023_02_FromHashtableToCHM;
import java.util.HashMap;
import java.util.UUID;
public class T02_TestHashMap {
static HashMap<UUID, UUID> m = new HashMap<>();
static int count = Constants.COUNT;
static UUID[] keys = new UUID[count];
static UUID[] values = new UUID[count];
static final int THREAD_COUNT = Constants.THREAD_COUNT;
static {
for (int i = 0; i < count; i++) {
keys[i] = UUID.randomUUID();
values[i] = UUID.randomUUID();
}
}
static class MyThread extends Thread {
int start;
int gap = count/THREAD_COUNT;
public MyThread(int start) {
this.start = start;
}
@Override
public void run() {
for(int i=start; i<start+gap; i++) {
m.put(keys[i], values[i]);
}
}
}
public static void main(String[] args) {
long start = System.currentTimeMillis();
Thread[] threads = new Thread[THREAD_COUNT];
for(int i=0; i<threads.length; i++) {
threads[i] =
new MyThread(i * (count/THREAD_COUNT));
}
for(Thread t : threads) {
t.start();
}
for(Thread t : threads) {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
long end = System.currentTimeMillis();
System.out.println(end - start);
System.out.println(m.size());
}
}
SynchronizedHashMap
- 我们在看第三个,用的是SynchronizedMap这个方法,给HashMap我们手动加锁,它的源码自己做了一个Object,然后每次都是SynchronizedObject,严格来讲他和那个Hashtable效率上区别不大。
package c_023_02_FromHashtableToCHM;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class T03_TestSynchronizedHashMap {
static Map<UUID, UUID> m = Collections.synchronizedMap(new HashMap<UUID, UUID>());
static int count = Constants.COUNT;
static UUID[] keys = new UUID[count];
static UUID[] values = new UUID[count];
static final int THREAD_COUNT = Constants.THREAD_COUNT;
static {
for (int i = 0; i < count; i++) {
keys[i] = UUID.randomUUID();
values[i] = UUID.randomUUID();
}
}
static class MyThread extends Thread {
int start;
int gap = count/THREAD_COUNT;
public MyThread(int start) {
this.start = start;
}
@Override
public void run() {
for(int i=start; i<start+gap; i++) {
m.put(keys[i], values[i]);
}
}
}
public static void main(String[] args) {
long start = System.currentTimeMillis();
Thread[] threads = new Thread[THREAD_COUNT];
for(int i=0; i<threads.length; i++) {
threads[i] =
new MyThread(i * (count/THREAD_COUNT));
}
for(Thread t : threads) {
t.start();
}
for(Thread t : threads) {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
long end = System.currentTimeMillis();
System.out.println("===SynchronizedHashMap.put() 所花费时间===: "+ (end - start));
System.out.println("===m.size()===: "+m.size());
//-----------------------------------
start = System.currentTimeMillis();
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(()->{
for (int j = 0; j < 10000000; j++) {
m.get(keys[10]);
}
});
}
for(Thread t : threads) {
t.start();
}
for(Thread t : threads) {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
end = System.currentTimeMillis();
System.out.println("===SynchronizedHashMap.get() 所花费时间===: "+(end - start));
}
}
看结果,SynchronizedHashMap严格来讲他和那个Hashtable效率上区别不大。
ConcurrentHashMap
- 这个第四个ConcurrentHashMap是多线程里面真正用的,以后我们多线程用的基本就是它,用Map的时候。并发的。
- 这个ConcurrentHashMap提高效率主要提高在读上面,由于它往里插的时候内部又做了各种各样的判断,本来是链表的,到8之后又变成了红黑树,然后里面又做了各种各样的cas的判断,所以他往里插的数据是要更低一些的。
- HashMap和Hashtable虽然说读的效率会稍微低一些,但是它往里插的时候检查的东西特别的少,就加个锁然后往里一插。所以,关于效率,还是看你实际当中的需求。用几个简单的小程序来给大家列举了这几个不同的区别。
package c_023_02_FromHashtableToCHM;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class T04_TestConcurrentHashMap {
static Map<UUID, UUID> m = new ConcurrentHashMap<>();
static int count = Constants.COUNT;
static UUID[] keys = new UUID[count];
static UUID[] values = new UUID[count];
以上是关于提升--12---并发容器历史的主要内容,如果未能解决你的问题,请参考以下文章
golang goroutine例子[golang并发代码片段]