提升--12---并发容器历史(代码片段)

高高for循环 高高for循环     2023-01-07     553

关键词:


容器

java容器分2大类

  • 第一大类:Collection叫集合
  • 第二大类:Map

Collection集合

第一大类Collection叫集合。集合的意思是不管你这个容器是什么结构你可以把一个元素一个元素的往里面扔;

Map

第二大类Map:Map是一对一对的往里扔,kv结构。其实Map来说可以看出是Collection一个特殊的变种,你可以把一对对象看成一个entry对象,所以这也是一个整个对象。容器就是装一个一个对象的,这么一些个集合。严格来讲数组也属于容器。

从数据结构角度分:

从数据结构角度来讲在物理上的这种存储的数据结构其实只有两种,

  1. 一种是连续存储的数组Array
  2. 另一种就是非连续存储的一个指向另外一个的链表。在逻辑结构那就非常非常多了。

Collection 分三大类

  • list
  • set
  • Queue

Queue队列

队列就是一对一队的,往这个队列里取数据的时候它和这个List、Set都不一样。大家知道List取的时候如果是Array的话还可以取到其中一个的。Set主要和其他的区别就是中间是唯一的,不会有重复元素,这个它最主要的区别。Queue实现了一个什么逻辑呢,实际上就是一个队列,

队列什么概念,有进有出,那么在这个基础之上它实现了很多的多线程的访问方法(比如说put阻塞式的放、take阻塞式的取),这个是在其他的List、Set里面都是没有的。队列最主要的原因是为了实现任务的装载的这种取和装这里面最重要的就是是叫做阻塞队列,它的实现的初衷就是为了线程池、高并发做准备的。原来的这些是容器是普通的为了装东西做准备的。

list,set和队列 Queue的区别

  1. 队列 Queue实现了很多的多线程的访问方法(比如说put阻塞式的放、take阻塞式的取),这个是在其他的List、Set里面都是没有的。
  2. 初衷不同:队列 它的实现的初衷就是为了线程池、高并发做准备的。原来的这些list,set容器是普通的为了装东西做准备的

JDK容器发展历史

  1. java1.0-----Vector、Hashtable(synchronized)
  2. list、set 、HashMap(没有锁)
  3. Collections工具类------SynchronizedList、synchronizedMap
  4. 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];
    jdk并发容器(代码片段)

JDK提供的并发容器总结JDK提供的这些容器大部分在 java.util.concurrent 包中。ConcurrentHashMap: 线程安全的HashMapCopyOnWriteArrayList: 线程安全的List,在读多写少的场合性能非常好,远远好于Vector.ConcurrentLinkedQueue: 高效的... 查看详情

提升--03---并发编程之---可见性(代码片段)

并发编程三大特性可见性先看一个小案例:importjava.io.IOException;publicclassT01_HelloVolatileprivatestaticbooleanrunning=true;privatestaticvoidm()System.out.println("mstart");while(running)System.out.prin 查看详情

提升--04---并发编程之---有序性(代码片段)

并发编程之有序性经典案例1:importjava.util.concurrent.CountDownLatch;publicclassT01_Disorderprivatestaticintx=0,y=0;privatestaticinta=0,b=0;publicstaticvoidmain(String[]args)throwsInterruptedExcep 查看详情

java常见并发容器总结(代码片段)

Java常见并发容器总结ConcurrentHashMapCopyOnWriteArrayListCopyOnWriteArrayList简介CopyOnWriteArrayList是如何做到的?CopyOnWriteArrayList读取和写入源码简单分析CopyOnWriteArrayList读取操作的实现CopyOnWriteArrayList写入操作的实现Concurrent 查看详情

day839.并发容器-java并发编程实战(代码片段)

并发容器Hi,我是阿昌,今天学习记录的是关于并发容器。Java1.5之前提供的同步容器虽然也能保证线程安全,但是性能很差。Java1.5版本之后提供的并发容器在性能方面则做了很多优化,并且容器的类型也更加丰富... 查看详情

day839.并发容器-java并发编程实战(代码片段)

并发容器Hi,我是阿昌,今天学习记录的是关于并发容器。Java1.5之前提供的同步容器虽然也能保证线程安全,但是性能很差。Java1.5版本之后提供的并发容器在性能方面则做了很多优化,并且容器的类型也更加丰富... 查看详情

多线程5-同步容器和并发容器(代码片段)

...dList、HashMap这些容器都是非线程安全的,如果有多个线程并发访问这些容器时,就会出现问题。因此,编写程序时,必须要求开发者手动在任何访问到这些容器的地 查看详情

java并发编程学习6-同步容器类和并发容器(代码片段)

本篇开始将要介绍Java平台类库下的一些最常用的并发基础构建模块,以及使用这些模块来构造并发应用程序时的一些常用模式。同步容器类同步容器类包括Vector和Hashtable,还有由Collections.synchronizedXxx等工厂方法创建的同... 查看详情

这些并发容器的坑,你要谨记!(代码片段)

...的版本中,提供的线程安全的容器,一般被称为并发容器。与同步容器一样,并发容器在总体上也可以分为四大类,分别为:List、Set、Map和Queue。本文分享自华为云社区《【高并发】要想学好并发编程,这... 查看详情

并发编程-线程安全策略之并发容器(j.u.c)中的集合类(代码片段)

J.U.C总览脑图 同步容器: 并发容器 概述同步容器是通过synchronized来实现同步的,所以性能较差。而且同步容器也并不是绝对线程安全的,在一些特殊情况下也会出现线程不安全的行为。那么有没有更好的方式代替... 查看详情

同步类容器并发修改的问题(代码片段)

...元素)、以及条件运算等。3、在这些复合操作下,进行并发的修改(add或remove)容器时,会抛出java.util.ConcurrentModificationException异常。在早期的迭代器设计的时候并没有考虑并发修改的 查看详情

提升--05---并发编程之---原子性---cas(代码片段)

并发编程之原子性从一个简单的小程序谈起:需求:new100个线程,每个线程对共有资源n,进行+1操作,每个线程执行10000次.按需求,最后共有资源n,应该是100*10000=1000000(1百万)代码1:importjava.util.concurrent.CountDownLatch;publicclassT00_00_... 查看详情

并发类容器-第一讲(代码片段)

一、基础知识夯实1.首先是我们需要懂得几点是在java中的集中运算直接看代码吧publicclassIntToBinarypublicstaticvoidmain(String[]args)throwsUnsupportedEncodingExceptionintdata=4;System.out.println("the4is"+Integer.toBinaryString(data));/ 查看详情

同步容器与并发容器(代码片段)

...器同步容器是指那些在容器内部已经同步化了,使我们在并发操作使用容器的时候不需要进行手动同步了。1.2同步容器的分类同步容器可以分为两大类:普通类和内部类普通类主要是Vector、Stack、HashTable普通类其实现的方式是通... 查看详情

copyonwritearraylist并发容器源码解析(代码片段)

CopyOnWriteArrayList并发List容器源码解析备注:下面的源码拷贝自JDK11类结构实现的接口Serializable:支持对象的序列化Cloneable:支持对象的复制RandomAccess:支持通过索引的随机访问List:支持List的所有操作核心数据结构由下面的源码... 查看详情

多线程之并发容器concurrenthashmap(代码片段)

...将结合Java内存模型,分析JDK源代码,探索ConcurrentHashMap高并发的具体实现机制。由于ConcurrentHashMap的源代码实现依赖于Java内存模型,所以阅读本文需要读者了解Java内存模型。同时,ConcurrentHashMap的源代码会涉及到散列算法和链表... 查看详情

高并发容器copyonwritearraylist原理解析(代码片段)

CopyOnWriteArrayList实现arraylist多线程数据安全的方式jdk提供的Collections.SynchronizedList()所有方法进行添加synchronized块publicvoidadd(intindex,Eelement)synchronized(mutex)list.add(index,element);使用reentrantLock自己对add时& 查看详情

java并发工具类java并发容器(代码片段)

前言Java并发包有很大一部分都是关于并发容器的。Java在5.0版本之前线程安全的容器称之为同步容器。同步容器实现线程安全的方式:是将每个公有方法都使用synchronized修饰,保证每次只有一个线程能访问容器的状态。但是这样... 查看详情