【Java 基础 18】Java 集合

Java中的集合主要继承Collection和Map两个根接口,Collection又派生了List,Queue,Set三个子接口,因此Java集合主要分为四类:List,Queue,Set,Map

List

Queue

Set

线程安全非线程安全线程安全非线程安全线程安全非线程安全
CopyOnWriteArrayListArrayListArrayBlockingQueueArrayDequeCopyOnWriteArraySetHashSet
CollesynchronizedListLinkedListPriorityBlockingQueuePriorityQueue TreeSet
Vector DelayQueue   
Stack LinkedBlockingDeque 

Map

  LinkedBlockingQueue 线程安全非线程安全
  LinkedTransferQueue HashtableHashmap
  SynchronousQueue Collections.synchronizedMapTreeMap
  DelayedWorkQueue ConcurrentHashMap 

 

 

 

 

 

 

 

 

 

 

 


Hashmap和ConcurrentHashMap

HashMap描述一一映射的关系,是非线程安全的数据结构,如果想在多线程下安全的操作 map,主要有以下解决方法:

(1)使用HashTable线程安全类,HashTable实现线程安全,是将所有访问和操作数据的方法被synchronized修饰(加同步锁),对整张哈希表加锁(注意:HashTable单个方法是线程安全的,但是当多个线程同时进行put和remove时,可能出现不线程安全);

(2)使用Collections.synchronizedMap方法,对方法进行加同步锁(本质和HashTable类似);

(3)ConcurrentHashMap把整个整张哈希表分为默认N个Segment,每个Segment提供相同的线程安全,效率提升N倍(默认N=16)

线程安全就是同一个对象对多线程访问时,采用加锁机制,对该对象的某个关键数据进行保护,其他线程不能进行访问直到该线程访问操作完,其他线程才可进入访问数据,不会出现不同的线程在同一时刻读取该对象的数据不一致或者数据污染的问题;

非线程安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据;】

 

为什么connurrentHashMap的效率高于hashtable

 Hashtable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,只要有一个线程在修改操作hashtable的对象,其他线程只能阻塞等待锁被释放,并发编程中性能差,ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术(每个Segment由自己的ReentrantLock保护);

 

ConcurrentHashMap什么时候不线程安全

案例

        ConcurrentHashMap<String, Integer> concurrentHashMap = new ConcurrentHashMap<>();
        concurrentHashMap.put("key", 0);

        ExecutorService executorService = Executors.newFixedThreadPool(1000);
        for (int i = 0; i < 1000; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    
                    /** 这块代码造成concurrentHashMap线程不安全:
                     * 
                     * int value = concurrentHashMap.get("key");
                     * 首先,concurrentHashMap的get方法是线程安全的,一个线程在获取的时候,其他线程阻塞,
                     * 而且concurrentHashMap通过HashEntry存储的value是volatile的,保证其原子性
                     * 
                     * concurrentHashMap.put("key", ++value);
                     * concurrentHashMap的get方法是线程安全的,一个线程在修改数据的时候,其他线程阻塞,
                     * 
                     * 那问题出在哪呢
                     */
                    int value = concurrentHashMap.get("key");
                    concurrentHashMap.put("key", ++value);
                    
                }
            });
        }
        executorService.shutdown();

        Thread.sleep(3000);
        System.out.println(concurrentHashMap.get("key"));



//运行三次打印结果:
"C:\Program Files\Java\jdk-11.0.3\bin\java.exe" "-javaagent:E:\IntelliJIDEA\IntelliJ IDEA Community Edition 2019.3.1\lib\idea_rt.jar=64471:E:\IntelliJIDEA\IntelliJ IDEA Community Edition 2019.3.1\bin" -Dfile.encoding=UTF-8 -classpath E:\Projects\project_java\LeetCodeAns\out\production\LeetCodeAns javaKown.JAVA数据结构
998
995
999

Process finished with exit code 0

ConcurrentHashMap保证单个读或写操作的线程安全, 但是不能保证读写复合操作的原子性,++value包括三步:

1. 获取value值;

2. value+1;

3. 将新值赋给value;

可能存在线程已经读到value值然后等待写锁,等它拿到写锁去更新时,value已经被前一个获取到写锁的线程修改,因此出现value的线程不安全。

解决方法

1. value使用原子类AtomicInteger(见【Java 基础 12】Atomic )来定义,保证读写复合操作原子性;

2. 外部程序中单个线程的关键代码块加同步锁,但是这样做的话,跟多线程访问修改HashTable差不多,单个线程锁着整个哈希表,效率降低,在实际开发过程中要仔细思考灵活应用

        ExecutorService executorService = Executors.newFixedThreadPool(1000);
        Object lock = new Object();
        for (int i = 0; i < 1000; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    
                    // 关键代码区加同步锁
                    synchronized (lock) {
                        int value = concurrentHashMap.get("key");
                        concurrentHashMap.put("key", ++value);
                    }
                }
            });
        }
        executorService.shutdown();


//运行三次打印结果
"C:\Program Files\Java\jdk-11.0.3\bin\java.exe" "-javaagent:E:\IntelliJIDEA\IntelliJ IDEA Community Edition 2019.3.1\lib\idea_rt.jar=65355:E:\IntelliJIDEA\IntelliJ IDEA Community Edition 2019.3.1\bin" -Dfile.encoding=UTF-8 -classpath E:\Projects\project_java\LeetCodeAns\out\production\LeetCodeAns javaKown.JAVA数据结构
1000
1000
1000

Process finished with exit code 0

 

HashMap,HashTable,ConcurrentHashMap如何扩容

1. HashMap底层由数组+链表实现,可以存储null键和null值, 参数initialCapacity(默认16),loadFactor(默认75%),扩容策略:newsize = oldsize*2;

2. HashTable底层由数组+链表实现,不可以存储null键和null值,参数initialCapacity(默认11),loadFactor(默认75%),扩容策略:newsize = oldsize*2+1;

3. ConcurrentHashMap底层由分段的数组+链表实现,参数initialCapacity(默认16),loadFactor(默认75%),concurrencyLevel(维护Segment数组大小,默认16,或者自定义参数最接近2的n次方的数),扩容策略:newsize = oldsize*2;

注意,JDK8之后,HashMap底层由数组+链表+红黑树实现


阻塞队列和非阻塞队列

阻塞队列,实现了阻塞接口BlockingQueue(BlockingQueue接口规定队列当生产线程向队列添加元素但队列已满时,生产线程被阻塞直到队列有剩余空间;当消费线程从队列移除元素但队列为空时,消费线程被阻塞直到队列有元素),

位于 java.util.concurrent 并发包中,包括:

ArrayBlockingQueue<>(capacity) :基于数组实现的有界队列;

DelayQueue :基于优先级堆实现,支持时延的无界调度队列;

DelayedWorkQueue in ScheduledThreadPoolExecutor : 基于优先级堆实现,支持时延实现周期性的无界调度队列;

LinkedBlockingDueue<>(省略/capacity) :基于链表实现的可选无界双端队列;

LinkedBlockingQueue<>(省略/capacity) :基于链表实现的可选无界队列;

LinkedTransferQueue:基于链表实现可选无界队列(采用预占模式,当队列为空时,提交一个null进队列,消费线程发现null进行等待,省略了链表的判空步骤);

PriorityBlockingQueue :基于优先级堆实现的无界队列;

SynchronousQueue<>() :没有缓冲的阻塞队列(1进1出);

 

非阻塞队列,没有实现BlockingQueue接口的

ArrayDeque: 基于数组实现的双端队列(位于 java.util包中);

PriorityQueue:基于优先级堆实现的,维护一个有序列表的无界队列(位于 java.util包中);

ConcurrentLinkedDeque:基于链表实现的,线程安全的双端队列(位于 java.util.concurrent包中);

ConcurrentLinkedQueue:基于链表实现的,线程安全的队列(位于 java.util.concurrent包中);

 

Java队列中线程安全的有

ArrayBlockingQueue<>(capacity,boolean) :并发控制使用经典的两条件法(使用1个ReentrantLock结合2个Condition)

DelayQueue:并发控制通过一个final ReebtrantLock非公平锁;

DelayedWorkQueue in ScheduledThreadPoolExecutor : 

LinkedBlockingDeque:并发控制使用经典的两条件法(使用2个ReentrantLock结合2个Condition)

LinkedBlockingQueue:并发控制使用经典的两条件法(使用1个ReentrantLock结合2个Condition)

LinkedTransferQueue:是ConcurrentLinkedQueue,SynchronousQueue ,LinkedBlockingQueue的超类;

PriorityBlockingQueue :并发控制使用经典的条件法(使用1个ReentrantLock结合1个Condition)

SynchronousQueue:内部没有实现锁,但是使用LockSupport控制线程,实现"wait-free"算法(即CAS算法, CompareAndSwap,比较的交换,保证原子操作)控制基础链表的head和tail;

(阻塞队列都是线程安全的,使用一个锁或者两个锁控制入队和出队, 它们都位于java.util.concurrent 并发包)

ConcurrentLinkedQueue: 采用非阻塞的方式实现线程安全队列,它采用CAS算法来实现;

ConcurrentLinkedDeque: 采用非阻塞的方式实现线程安全队列,它采用CAS算法来实现;

(非阻塞队列实现线程安全,基于循环CAS实现, 它们也都位于java.util.concurrent 并发包)

 

Java队列中非线程安全的有

ArrayDeque: 非线程安全队列,无同步策略,不支持多线程安全访问(位于 java.util包中);

PriorityQueue:非线程安全队列,无同步策略,不支持多线程安全访问(位于 java.util包中);

并发编程中如果要用ArrayDeque和PriorityQueue可以替换成线程安全的ArrayBlockingQueue和PriorityBlockingQueue

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java集合Java编程中非常重要的一部分,它提供了一种方便的方式来处理一组对象。Java集合框架包括List、Set、Map等接口和实现它们的类。下面是Java集合基础知识的介绍: 1. List接口:List是一个有序的集合,它可以包含重复的元素。List接口的常用实现类有ArrayList和LinkedList。其中,ArrayList是一个动态数组,它可以自动扩容以容纳更多的元素;而LinkedList是一个双向链表,它可以快速地在列表中插入或删除元素。 2. Set接口:Set是一个不允许重复元素的集合。Set接口的常用实现类有HashSet和TreeSet。其中,HashSet是一个基于哈希表的实现,它可以快速地查找元素;而TreeSet是一个基于红黑树的实现,它可以对元素进行排序。 3. Map接口:Map是一个键值对的集合,它允许使用键来查找值。Map接口的常用实现类有HashMap和TreeMap。其中,HashMap是一个基于哈希表的实现,它可以快速地查找键值对;而TreeMap是一个基于红黑树的实现,它可以对键进行排序。 下面是一个示例代码,演示了如何使用ArrayList集合存储学生的成绩,并遍历这个集合: ```java // 创建一个ArrayList集合,向这个集合中存入学生的成绩 ArrayList<Integer> al = new ArrayList<Integer>(); al.add(78); al.add(67); // 对集合遍历 // 方式1 for (Object obj : al) { System.out.println(obj);} // 方式2 for (Integer i : al) { System.out.println(i); } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值