Java 多线程 —— 常用并发容器

引言

本博客基于常用的并发容器,简单概括其基本特性和简单使用,并不涉及较深层次的原理分析和全面的场景用法。

适合对不了解并发容器的同学,工作中遇到类似的场景,能够对文中提到的并发容器留有简单印象就好。

一、ConcurrentHashMap

下面的程序中,切换任意Map的实现方式,如TreeMap、HashTable、ConcurrentHashMap等,运行程序,观察执行结果:

public class T01_ConcurrentMap {public static void main(String[] args) {
//		Map<String, String> map = new TreeMap<>();
//		Map<String, String> map = new Hashtable<>();Map<String, String> map = new ConcurrentHashMap<>();
//		Map<String, String> map = new ConcurrentSkipListMap<>();
//		Map<String, String> map = new HashMap<>();Random rdm = new Random();Thread[] ths = new Thread[100];CountDownLatch latch = new CountDownLatch(ths.length);long start = System.currentTimeMillis();for (int i = 0; i < ths.length; i++) {ths[i] = new Thread(() -> {for (int j = 0; j < 10000; j++)map.put("a" + rdm.nextInt(100000), "a" + rdm.nextInt(100000));latch.countDown();});}Arrays.asList(ths).forEach(t -> t.start());try {latch.await();} catch (InterruptedException e) {e.printStackTrace();}long end = System.currentTimeMillis();System.out.println(end - start);}
}

ConcurrentHashMap和HashTable,前者在执行效率方面要比后者高,因为HashTable在put每个对象的时候,都需要锁定整个Map对象;而ConcurrentHashMap的实现方式是通过“分段锁”,即将锁的作用范围细化,在put的时候,只锁定对应的一段,从而提高效率。

ConcurrentSkipListMap跳表,不仅支持并发,而且是有序的,因为提供了排序功能,在性能方面可能不及Concurrent-HashMap。

二、CopyOnWriteList写时复制容器

切换下面程序中List的实现方式(ArrayList、Vector、CopyOnWriteArrayList)观察执行结果。

public class T02_CopyOnWriteList {public static void main(String[] args) {// List<String> lists = new ArrayList<>();// 会产生并发问题// List<String> lists = new Vector<>();List<String> lists = new CopyOnWriteArrayList<>();Random rdm = new Random();Thread[] ths = new Thread[100];for (int i = 0; i < ths.length; i++) {Runnable task = () -> {for (int j = 0; j < 1000; j++) {lists.add("a" + rdm.nextInt(10000));}};ths[i] = new Thread(task);}runAndComputeTime(ths);System.out.println(lists.size());}private static void runAndComputeTime(Thread[] ths) {long t1 = System.currentTimeMillis();Arrays.asList(ths).forEach(t -> t.start());Arrays.asList(ths).forEach(t -> {try {t.join();} catch (Exception e) {e.printStackTrace();}});long t2 = System.currentTimeMillis();System.out.println(t2 - t1);}
}

CopyOnWriteList写的效率非常低,读的效率非常高。这是因为在向写时复制容器中添加一个元素的时候会将整个容器复制一份,再对复制后的容器进行插入;而读的时候不需要加锁。因此,这种容器一般用于极少修改,而读取频繁的应用场景。

三、Collections.synchronizedXx

Collections是一个容器工具类,通过类似synchronizedList(List<T> list)、synchronizedMap(Map<K,V> m)等命名形式的静态方法,可以返回一个加了锁的对应容器。实际上,通过这种方式获得的同步容器仅仅是将普通的非线程安全的容器的方法进行synchronized封装:

使用方法非常简单,即传入的非同步容器,返回一个同步容器

List<String> strs = new ArrayList<>();
List<String> syncStrs = Collections.synchronizedList(strs);

 四、ConcurrentLinkedQueue

线程安全的并发队列,就可以使用ConcurrentLinkedQueue,是“尾进头出”的并发队列;另外还有一个ConcurrentLinkedDeque 是并发的双端队列,在实际开发中用处也非常大。

执行下面的程序,观察输出结果,重点理解个别方法的含义:

    public static void main(String[] args) {Queue<String> strs = new ConcurrentLinkedQueue<>();Deque<String> names = new ConcurrentLinkedDeque<>();for (int i = 0; i < 10; i++) {strs.offer("S" + i); // addnames.offer("N" + i);}System.out.println("output#1 : " + strs);System.out.println("output#2 : " + strs.size());System.out.println("output#3 : " + strs.poll()); // FIFO,容器中的删除System.out.println("output#4 : " + strs.size());System.out.println("output#5 : " + strs.peek()); // 取出,容器中的不删System.out.println("output#6 : " + strs.size());System.out.println("output#7 : " + names);System.out.println("output#8 : " + names.pollLast()); // LIFOSystem.out.println("output#9 : " + names);System.out.println("output#10 : " + names.peekFirst()); // FIFOSystem.out.println("output#11 : " + names);}

执行结果:

 五、LinkedBlockingQueue

使用LinkedBlockingQueue非常适合解决生产者-消费者模式的问题。

public class T05_LinkedBlockingQueue {static BlockingQueue<String> strs = new LinkedBlockingQueue<>();static Random rdm = new Random();public static void main(String[] args) {new Thread(() -> {for (int i = 0; i < 100; i++) {try {strs.put("a" + i);TimeUnit.MILLISECONDS.sleep(rdm.nextInt(1000));} catch (Exception e) {e.printStackTrace();}}}, "p1").start();for (int i = 0; i < 5; i++) {new Thread(() -> {for (;;) {try {System.out.println(Thread.currentThread().getName()+ " take " + strs.take());// 如果空了,就会等待} catch (Exception e) {e.printStackTrace();}}}, "c" + i).start();}}
}

Queue在高并发的情况下,通常可以使用两种队列:ConcurrentLinkedQueue 和 BlockingQueue(接口)。

BlockingQueue是阻塞式队列,它是一个接口,可以根据有界或无界实际需要考虑使用它的相关实现类,如ArrayBlockingQueue或上面代码中用到的LinkedBlockingQueue。

六、ArrayBlockingQueue

ArrayBlockingQueue是一个有界队列,在构造时可以指定元素个数,队列会自行维护元素的个数:

public class T06_ArrayBlockingQueue {static BlockingQueue<String> strs = new ArrayBlockingQueue<>(10);static Random rdm = new Random();public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 10; i++) {strs.put("a" + i);}//         strs.put("aaa");// 线程阻塞,知道有空闲位置
//         strs.add("aaa"); // output:java.lang.IllegalStateException: Queue full
//        strs.offer("aaa"); // output:[a0, a1, a2, a3, a4, a5, a6, a7, a8, a9]strs.offer("aaa", 3, TimeUnit.SECONDS);// 阻塞,但可以指定超时时间System.out.println(strs);}
}

注意观察此容器为我们提供的不同场景下可以选择的添加元素的方式,执行结果见代码注释部分。

七、DelayQueue

DelayQueue也是一种无界队列,容器中的每个元素会记录一个倒计时,等待时间结束后才可以被消费者取出。可以用于执行定时任务。

public class T07_DelayQueue {static BlockingQueue<MyTask> tasks = new java.util.concurrent.DelayQueue<>();static class MyTask implements Delayed {long runningTime;MyTask(long rt) {runningTime = rt;}@Overridepublic int compareTo(Delayed o) {if (this.getDelay(TimeUnit.MILLISECONDS) < o.getDelay(TimeUnit.MILLISECONDS)) {return -1;} else if (this.getDelay(TimeUnit.MILLISECONDS) > o.getDelay(TimeUnit.MILLISECONDS)) {return 1;} else {return 0;}}@Overridepublic long getDelay(TimeUnit unit) {return unit.convert(runningTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);}@Overridepublic String toString() {return String.valueOf(runningTime);}}public static void main(String[] args) throws InterruptedException {long now = System.currentTimeMillis();MyTask t1 = new MyTask(now + 1000);MyTask t2 = new MyTask(now + 2000);MyTask t3 = new MyTask(now + 1500);MyTask t4 = new MyTask(now + 2500);MyTask t5 = new MyTask(now + 500);tasks.put(t1);tasks.put(t2);tasks.put(t3);tasks.put(t4);tasks.put(t5);System.out.println(tasks);for (int i = 0; i < 5; i++) {System.out.println(tasks.take());}}
}

八、TransferQueue

TransferQueue提供了一种特殊的方法叫做transfer(), 这个方法在有消费者等待消费的时候,不会将元素放入队列中,而是直接传递给消费者。因此这种方法非常适用于高并发的情况下。 但是,当没有消费者的时候,那么transfer就会阻塞,而其他类似的方法如put(),add()都不会阻塞。在实时消息处理中用到的比较多, 例如Netty。

public class T08_TransferQueue {public static void main(String[] args) throws InterruptedException {LinkedTransferQueue<String> strs = new LinkedTransferQueue<>();new Thread(() -> {try {System.out.println(strs.take());} catch (Exception e) {e.printStackTrace();}}).start();strs.transfer("aaa");new Thread(() -> {try {System.out.println(strs.take());} catch (Exception e) {e.printStackTrace();}}).start();}
}

九、SynchronusQueue

同步队列 SynchronousQueue是一种特殊的TransferQueue。 它的容量为0,是没有容量的队列,在消费者正在等待的时候,必须使用put(实际上内部使用的是transfer),放入的任何元素 都必须直接交给消费者,而不能放入容器中。

public class T09_SynchronousQueue { // 容量为0public static void main(String[] args) throws InterruptedException {BlockingQueue<String> strs = new SynchronousQueue<>();new Thread(() -> {try {System.out.println(strs.take());} catch (Exception e) {e.printStackTrace();}}).start();// strs.put("aaa"); // 阻塞等待消费者消费strs.add("aaa");System.out.println(strs.size());}}

总结

综上,是对一些常用容器的介绍和案例展示,重点要理解它们的应用区别和使用场景,最起码要有个大概印象,在遇到类似问题的时候考虑使用它们。

ConcurrentHashMap

CopyOnWriteList

Collections.synchronizedXx

ConcurrentLinkedQueue

BlockingQueue

    LinkedBQ

    ArrayBQ

    TransferQueue

    SynchronusQueue

DelayQueue执行定时任务

鸣谢

《马士兵老师高并发编程系列--第三部分》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/558887.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode算法入门- Longest Palindromic Substring-day5

LeetCode算法入门- Longest Palindromic Substring-day5 Longest Palindromic Substring Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000. Example 1: Input: “babad” Output: “bab” Note: “a…

Tomcat运行三种模式:http-bio|http-nio|http-apr介绍

转自《tomcat运行三种模式:http-bio|http-nio|http-apr介绍》 Tomcat是一个小型的轻量级应用服务器&#xff0c;也是JavaEE开发人员最常用的服务器之一。不过&#xff0c;许多开发人员不知道的是&#xff0c;Tomcat Connector(Tomcat连接器)有bio、nio、apr三种运行模式&#…

LeetCode算法入门- Reverse Integer-day6

LeetCode算法入门- Reverse Integer-day6 Given a 32-bit signed integer, reverse digits of an integer. Example 1: Input: 123 Output: 321 Example 2: Input: -123 Output: -321 Example 3: Input: 120 Output: 21 class Solution {public int reverse(int x) {long…

Java工具方法——属性拷贝方法:BeanUtils.copyProperties(Object, Object)

介绍 org.springframework.beans.BeanUtils.copyProperties(Object, Object)是spring 框架的对象工具类&#xff1a;BeanUtils下的一个拷贝对象属性的方法。 官方注释 把给定的源对象属性值拷贝到目标对象中。 注意&#xff1a;源对象类与目标对象类不一定非要完全匹配&…

LeetCode算法入门- String to Integer (atoi)-day7

LeetCode算法入门- String to Integer (atoi)-day7 String to Integer (atoi): Implement atoi which converts a string to an integer. The function first discards as many whitespace characters as necessary until the first non-whitespace character is found. The…

LeetCode算法入门- Roman to Integer Integer to Roman -day8

LeetCode算法入门- Roman to Integer -day8 Roman to Integer&#xff1a; 题目描述&#xff1a; Roman numerals are represented by seven different symbols: I, V, X, L, C, D and M. Symbol    Value I        1 V        5 X        10 L…

Git初学札记(九)————EGit检出远程分支

引言 现在有这样一个使用场景&#xff1a;团队中的其他开发者提交了一个新的特性分支&#xff08;如feature_1&#xff09;&#xff0c;要求我们一同开发&#xff0c;并将自己修改的代码也全部提交到这个分支上去。那么如何将这个分支检出&#xff0c;并将本地检出的分支与这个…

LeetCode算法入门- 3Sum -day9

LeetCode算法入门- 3Sum -day9 题目描述&#xff1a; Given an array nums of n integers, are there elements a, b, c in nums such that a b c 0? Find all unique triplets in the array which gives the sum of zero. Note: The solution set must not contain dup…

XML学习(一)————XML简介

引言 作为数据传输界鼎鼎大名的扛把子&#xff0c;XML被应用于各个方面&#xff0c;但随着弱结构化标记语言如JSON、YAML等的出现&#xff0c;人们慢慢的脱离了XML的统治&#xff0c;但在互联网早期的发展当中XML是不可或缺的一部分&#xff0c;比如各种微信开发中的数据传输&…

Java核心篇之Java锁--day2

Java核心篇之Java锁–day2 乐观锁&#xff1a;乐观锁是一种乐观思想&#xff0c;即认为读多写少&#xff0c;每次去取数据的时候都认为其他人不会修改&#xff0c;所以不会上锁&#xff1b;但是在更新的时候会判断一下在此期间别人有没有去修改它&#xff0c;如果有人修改的话…

XML学习(二)————属性还是标签?

引言 xml中并没有规则要求我们什么时候使用属性&#xff0c;什么时候使用标签。 属性和标签都可以存储数据&#xff0c;但是在XML的使用中&#xff0c;我们需要探讨一下对属性和标签的选择问题。 约定规则 XML 应该避免使用属性来存储数据&#xff0c;这与HTML的推荐规则不…

LeetCode算法入门- 3Sum Closest -day10

LeetCode算法入门- 3Sum Closest -day10 Given an array nums of n integers and an integer target, find three integers in nums such that the sum is closest to target. Return the sum of the three integers. You may assume that each input would have exactly one …

Spring Boot————Web应用启动时自动执行ApplicationListener用法

原文&#xff1a;《web服务启动spring自动执行ApplicationListener的用法》 引言 我们知道&#xff0c;一般来说一个项目启动时需要加载或者执行一些特殊的任务来初始化系统&#xff0c;通常的做法就是用servlet去初始化&#xff0c;但是servlet在使用Spring bean时不能直接注…

LeetCode算法入门- 4Sum -day11

LeetCode算法入门- 4Sum -day11 Given an array nums of n integers and an integer target, are there elements a, b, c, and d in nums such that a b c d target? Find all unique quadruplets in the array which gives the sum of target. Note: The solution se…

Spring Boot————ApplicationListener实现逃课事件监听

引言 上一篇文章转了一篇关于ApplicationListener用于在Web项目启动时做一些初始化的用法。 但是&#xff0c;在实际生产过程中&#xff0c;当一个事件产生&#xff0c;又是如何被onApplicationEvent()方法监听到&#xff0c;并执行一系列动作呢&#xff1f;简单搜索了一下&a…

Java核心篇之Redis--day4

Java核心篇之Redis–day4 Redis有哪些数据结构&#xff1f; 字符串String、字典Hash、列表List、集合Set、有序集合SortedSet。 1.String&#xff1a;字符串&#xff0c;常用命令&#xff1a;get&#xff0c;set&#xff0c;decr&#xff0c;incr&#xff0c;mget&#xff08;查…

软件版本GA、RC、beta等含义

原文《软件版本GA、RC、beta等含义》 GA General Availability&#xff0c;正式发布的版本&#xff0c;官方开始推荐广泛使用&#xff0c;国外有的用GA来表示release版本。 RELEASE 正式发布版&#xff0c;官方推荐使用的版本&#xff0c;有的用GA来表示。比如spring。 Sta…

Java核心篇之泛型--day5

Java核心篇之泛型–day5 泛型是JDK5时引入的一个新特性&#xff0c;泛型提供了编译时类型安全检查的机制&#xff0c;该机制允许程序猿在编译时检测到非法的类型输入。 泛型的本质是参数化类型&#xff0c;也就是说操作的类型被指定为一个参数。 假定我们有一个需求&#xff…

Spring Boot————AOP入门案例及切面优先级设置

看了这篇文章&#xff0c;如果你还是不会用AOP来写程序&#xff0c;请你打我&#xff01;&#xff01; .||| 引言 Spring AOP是一个对AOP原理的一种实现方式&#xff0c;另外还有其他的AOP实现如AspectJ等。 AOP意为面向切面编程&#xff0c;是通过预编译方式和运行期动态代…

Java核心篇之HashMap--day6

Java核心篇之HashMap–day6 HashMap是一种键值对的数据结构&#xff0c;以数组与链表的形式&#xff08;key&#xff1a;value&#xff09;实现&#xff0c;查询性能和添加性能很好。他是通过将key进行hashcode&#xff08;&#xff09;映射函数来找到表中对应的位置。 HashM…