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,一经查实,立即删除!

相关文章

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;源对象类与目标对象类不一定非要完全匹配&…

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

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

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;是通过预编译方式和运行期动态代…

Spring Boot————Spring Data JPA简介

引言 JPA是Java 持久化API的缩写&#xff0c;是一套Java数据持久化的规范&#xff0c; Spring Data Spring Data项目的目的是为了简化构建基于Spring 框架应用的数据访问技术&#xff0c;包括对关系型数据库的访问支持。另外也包含非关系型数据库、Map-Reduce框架、云数据服…

Spring Boot————Spring Boot启动流程分析

一、引言 Spring Boot 的启动虽然仅仅是执行了一个main方法&#xff0c;但实际上&#xff0c;运行流程还是比较复杂的&#xff0c;其中包含几个非常重要的事件回调机制。在实际生产开发中&#xff0c;有时候也会利用这些启动流程中的回调机制&#xff0c;做一些项目初始化的工…

Spring Boot————应用启动时的监听机制测试

引言 本文承接前面的《Spring Boot————Spring Boot启动流程分析》&#xff0c;主要测试一下ApplicationContextInitializer、SpringApplicationRunListener、ApplicationRunner、CommandLineRunner这四个接口实现之下的组件是何时在Spring Boot项目启动时创建并执行相关方…

2018年度总结

2018年&#xff0c;已经成为过去式&#xff0c;这360多天依旧过的很快&#xff0c;快到当我手扶键盘回想这一年发生的点点滴滴时&#xff0c;都没有任何感慨。可能我天生是个无感之人&#xff0c;或许&#xff0c;这一年的时光&#xff0c;无数的事故、故事已经让我变得不那么感…

Spring Boot————默认缓存应用及原理

引言 应用程序的数据除了可以放在配置文件中、数据库中以外&#xff0c;还会有相当一部分存储在计算机的内存中&#xff0c;这部分数据访问速度要快于数据库的访问&#xff0c;因此通常在做提升数据访问速度时&#xff0c;会将需要提升访问速度的数据放入到内存中&#xff0c;…

LeetCode算法入门- Multiply Strings -day18

LeetCode算法入门- Multiply Strings -day18 题目介绍 Given two non-negative integers num1 and num2 represented as strings, return the product of num1 and num2, also represented as a string. Example 1: Input: num1 “2”, num2 “3” Output: “6” Exampl…

Linux——VMware虚拟机安装CentOS步骤

一、下载CentOS.iso镜像 最地道的下载方式就是通过官网&#xff0c;大多数的网上连接会直接抛出网易、华为的镜像连接&#xff0c;实际上这些连接都可以在官网找到&#xff1a; 官网地址&#xff08;可直接百度搜索CentOS&#xff09;&#xff1a;https://www.centos.org/ 1…

Spring Boot——Redis安装配置与应用整合

引言 Spring Boot默认以ConcurrentHashMap作为缓存容器&#xff0c;但默认的缓存容器在简单的场景使用还是可以的&#xff0c;而作为NoSQL的代表&#xff0c;Redis可以做内存数据库、消息中间件都是不错的&#xff0c;而且有RedisDesktopManager作为可视化管理工具&#xff0c…

利用Aria2高速下载网盘文件

利用Aria2高速下载网盘文件 方法步骤&#xff1a; 下载文件 解压arial2&#xff0c;运行aria2启动.VBS添加插件&#xff0c;解压BaiduExporter-master.zip在Google浏览器扩展程序中chrome://extensions加载已经解压的扩展程序 选择BaiduExporter进行添加即可&#xff0c;打开…

MySQL——JSON_REPLACE()函数修改JSON属性值

引言 由于对mysql的函数并不了解&#xff0c;之前遇到了一个场景&#xff1a; mysql表中有一个字段res_content 是一个由longtext类型&#xff08;可以理解为一个更长的varchar&#xff09;保存的巨大的JSON对象&#xff0c;但是&#xff0c;由于录入的疏忽&#xff0c;导致这…

Spring Boot整合Redis——自定义RedisSerializer

引言 spring boot简单引入redis依赖&#xff0c;并使用RedisTemplate进行对象存储时&#xff0c;需要使存储对象实现Serializable接口&#xff0c;这样才能够成功将对象进行序列化。 RedisTemplate默认使用的序列化机制是JdkSerializationRedisSerializer&#xff0c;但实际开…