Java并发基础构建模块简介
在实际并发编程中,可以利用synchronized来同步线程对于共享对象的访问,用户需要显示的定义synchronized代码块或者方法。为了加快开发,可以使用Java平台一些并发基础模块来开发。
注:关于容器类中的常见并发容器和同步容器的类图,详见另一篇文章《Java集合框架图》
一 同步容器类
同步容器类主要包括Vector和Hashtable,都是通过Collections.synchronizedXxx工厂方法创建的。这些类都是线程安全的。一些修改删除添加方法都利用了synchronized来进行同步。将这些容器类的状态封装,然后对于其所有的public方法都进行同步,这样就保证了每次只能允许一个线程访问这些容器类。对于每个public方法都保证了原子性操作。
当对于同步容器类进行多个synchronized方法组成的复合操作,为了保证正确性,必须要确保这些复合操作为一个原子操作。
对于这些同步容器里进行迭代处理中,需要注意多个线程访问的问题。当利用for来迭代处理,容器的大小可能会在迭代中修改,则就会抛出ArrayIndexOutOfBoundsException异常。而在利用迭代器foreach运用interator过程中,可能会抛出ConcurrentModificationException。所以在对于这种同步容器类迭代过程中一定要进行加锁处理。
在对同步容器类调用toString,hashCode,equals,containsAll,removeAll,retainAll以及容器作为参数传递都会对容器进行迭代操作。
二 并发容器类
由于同步容器类,只允许同时一个外部线程访问该集合,则降低了它的并发能力。就引出了并发容器类。并发容器类主要就是针对多线程访问设计的。
常见的有ConcurrentHashMap以代替同步且基于散列的Map。CopyOnWriteArrayList用于以迭代为主要操作来代替List。Queue和BlockingQueue,其中BlockingQueue尤其在生产者与消费者模式中作为缓冲得到了很大的运用。
ConcurrentHashMap,这是一种并发性容器类,这里并没有针对每一个方法使用同一个锁进行同步,而是在内部用一种分段锁来实现并发性操作。可以允许同时多个读线程操作,允许同时多个写线程操作,多个读线程与写线程同时操作。迭代过程不需要加锁,但是在迭代过程可能容量大小会发生变化。这种最重要的是用于针对get,put,containsKey和remove等操作频繁的多线程中。
它增加了几个复合型的原子性操作,从而可以直接使用不用加锁。
CopyOnWriteArrayList,这是一种并发性容器类,迭代过程不需要加锁。它是“写入时复制”的容器,当在每次修改时候,都会复制底层数组,创建并发布一个新的容器副本。这种最重要用于需要频繁的进行迭代操作,且迭代操作远远大于修改操作的时候,多个线程可以同时对这个容器进行迭代操作,而不会彼此干扰且与修改容器的线程不相干。
Queue,是一种用来保存临时的数据,包括ConcurrentLinkedQueue,Queue上的操作不会阻塞,如果队列为空,会立即返回。
BlockingQueue,它主要用于并发操作方面。这是一种基于阻塞的队列,当从队列中获取元素时候,如果队列为空则等待,当向队列插入元素时候,如果队列满了则等待。它利用put和take来获取对象,这种操作都是在内部加锁机制实现的。常见的几个插入与移除对象操作:
put,将object放入队列中,如果无空间,则一直等待到有空间,会阻塞调用该方法的线程。
offer,如果可以放入object,则返回true,否则false。一会阻塞调用该方法的线程。
poll,获取首位对象,若立即得不到,可以等待一定时间后再返回值或者null。
take,获取首位对象,若队列为空,则一直等待有元素添加,会阻塞调用该方法的线程。
常见的子类包括ArrayBlockingQueue和LinkedBlockingQueue,分别用于替代LinkedList和ArrayList,提高并发性能。SynchronousQueue,这是一种同步移除与添加的队列,每个插入操作必须等待另一个线程的对应移除操作。同步队列没有任何内部容量,甚至连一个队列的容量都没有。
这种最重要用于常见的生产者与消费者模式中,作为缓冲用。在我的一篇《Java多线程设计模式(2)生产者与消费者模式》利用自定义数组大小来实现生产者与消费者模式,在ArrayBlockingQueue中其内部大体的实现机制就是和那一样的。
利用ArrayBlockingQueue可以很容易实现生产者与消费者模式,不用进行额外的同步处理,因为它内部都已经实现了同步处理,并且进行了并发性能的提高。代码示例如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
package whut.producer;import java.util.Random;import java.util.concurrent.ArrayBlockingQueue;import java.util.concurrent.BlockingQueue;//利用BlockingQueue实现消费者与生产者class Producer implements Runnable private final BlockingQueue public Producer(BlockingQueue queue } public void run() try { int i=0; while (i<10) { queue.put(i); i++; } } catch (InterruptedException } } private Object { Randomnew Random(); int res=rd.nextInt(10); return res; }}class Consumer extends Thread private final BlockingQueue public Consumer(String super(name); queue } public void run() try { while (true) consume(queue.take()); } } catch (InterruptedException } } private void consume(Object System.out.println(Thread.currentThread().getName()+"+x); }}class BlockingQueueDemo public static void main(String[] BlockingQueue<Integer>new ArrayBlockingQueue<Integer>(10); Producernew Producer(q); Consumernew Consumer("Apple",q); Consumernew Consumer("Hawk",q); new Thread(p).start(); c1.start(); c2.start(); }} |
三 同步工具类
常见使用的同步工具类有信号量(Semaphore),栅栏(Barrier),闭锁(Latch).
闭锁(Latch),可以延迟线程进度直到闭锁到达最终状态。闭锁主要用来确保某些活动直到其他活动都执行完毕后才能继续执行,这里执行的形如递减操作,初始化等待活动的数目,当递减到0,则该活动才得以继续执行。
CountDownLatch是一种灵活的闭锁实现,有个状态为计数器,利用构造器设置。通过countDown方法递减计数器,表示一个活动已经执行完毕,而await方法等待计数器为0,如果不为0则阻塞等待,或者线程被中断,或者等待超时。这两个方法必须成对使用。
CountDownLatch的简单实例:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
package whut.concurrentmodel;import java.util.concurrent.CountDownLatch;//利用闭锁来实现,闭锁可以用于线程之间的协作,//即一个线程必须等待其余所有活动完后执行public class CountDownLatchClient public void timeTasks(int nThreads, final Runnable throws InterruptedException // final CountDownLatchnew CountDownLatch(1); // final CountDownLatchnew CountDownLatch(nThreads); for (int i0; Threadnew Thread() public void run() try { // startGate.await(); try { task.run(); } finally { System.out.println(Thread.currentThread().getName() + "); // endGate.countDown(); } } catch (InterruptedException } } }; t.start(); } // startGate.countDown(); // endGate.await(); System.out.println("All); } // public static void main(String[] // Runnablenew Runnable() public void run() int i0; while (i100) i++; } } }; CountDownLatchClientnew CountDownLatchClient(); try { cdl.timeTasks(10, } catch (InterruptedException } System.out.println("do); }} |
栅栏(Barrier),可以阻塞一组线程直到某个事件发生。它和闭锁一样。闭锁用于等待其他活动,栅栏用于等待其他线程。这里执行的形如递增操作,初始化等待线程的数目,当递增到目标线程数目时候,则该线程才得以继续执行。
CyclicBarrier,是一种常用的栅栏类,可以使得一定数量的参与者反复在栅栏处汇集。通常用于并行迭代计算中。这种将一个复杂的大问题,分解成多个子问题,为每一个子问题创建线程来处理。当线程到达栅栏位置时候将调用await,这个将一直阻塞,直到所有的线程都到达了栅栏位置。在使用它的时候,可以传递一个Runnable,用于当成功通过栅栏后执行的操作或任务。一般每个线程执行的时候先利用barrier.hasCoverged判断,然后执行任务,最后利用barrier.await(),来阻塞所有都到达栅栏位置。
一般使用代码如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
package whut.concurrentmodel;import java.util.concurrent.BrokenBarrierException;import java.util.concurrent.CyclicBarrier;//栅栏实例public class BarrierClient public static void main(String[] // BarrierClientnew BarrierClient(); //获取可以同时并行处理的数目 int count=Runtime.getRuntime().availableProcessors(); CyclicBarriernew CyclicBarrier(count); for(int i=0;i<count;i++) { Workernew Worker(barrier); new Thread(work).start(); } } private class Worker implements Runnable { private final CyclicBarrier public Worker(CyclicBarrier { this.bar=bar; } public void run() { //dosome //........... try{ bar.await(); }catch(InterruptedException { }catch(BrokenBarrierException { } } }} |
栅栏与闭锁,可以使得任务线程同时开始同时结束,利用栅栏也可以实现与闭锁一样的效果。在并发测试中很有用
信号量(Semaphore),主要是用来控制同时访问某个特定资源的操作数目,或者执行某个指定操作的数目。可以用来实现资源池,对容器施加边界。Semaphore管理者一组虚拟许可,许可的数目由构造器指定。执行操作必须先获取许可,使用完毕后释放许可。如果没有许可,则acquire一直阻塞直到有许可。release方法将返回一个许可信号量。当初始值为1,则可以用作互斥体。
Semaphore一般用于实现资源池以及设置任何容器为有界阻塞容器。注意这种方式与另一篇《Java多线程设计模式(3)读写锁模式》的比较
代码示例如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
package whut.concurrentmodel;import java.util.Collections;import java.util.HashSet;import java.util.Set;import java.util.concurrent.Semaphore;//利用Semaphore来实现集合的边界处理public class SemaphoreTest<T> private final Set<T> private final Semaphore public SemaphoreTest(int bound) { this.set=Collections.synchronizedSet(new HashSet<T>());//同步处理 //设置Semaphore的大小,用于设置set的边界,控制同时多少个访问 sem=new Semaphore(bound); } //add操作成功则会返回true,否则返回false public boolean add(Tthrows InterruptedException { sem.acquire();//获取信号量 boolean wasAdded=false; try{ wasAdded=set.add(o);//同步访问这些方法 return wasAdded; }finally{ if(!wasAdded) sem.release();//释放信号量,如果没有添加成功 } } public boolean remove(Object { boolean wasRemoved=set.remove(o);//成功移除返回true if(wasRemoved) sem.release();//释放信号量 return wasRemoved; }} |
FutureTask,这是一种可以获取长时间执行任务的快照,可以执行任何返回该对象,从而继续做其他工作,当需要获取任务的执行结果的时候,再利用Future.get获取任务的处理结果。如果任务已经完成,则get会立即返回,否则则会阻塞直到任务进入完成状态,然后返回结果或者抛出异常。FutureTask将计算结果从执行计算的线程传递到了获取这个结果的线程。
FutureTask可以包装Callable和Runnable作为其构造器参数,同时FutureTask是实现了Runnable接口,故可以作为Executor.execute的参数传递。
使用FutureTask的方式有两种,一种是将其作为Thread的构造器参数或者execute()的参数在新的线程中执行。一种是直接运行其run方法,在主线程中串行运行。
利用FutureTask其实就是和另一篇《Java多线程设计模式(5)Future模式》的机制一样的,只不过在内部已经封装好了。
代码示例:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
package whut.future;import java.util.Random;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;//利用FutureTask来实现future设计模式public class FutureTaskDemo public static void main(String[] // MyCallalenew MyCallale(); FutureTask<String>new FutureTask<String>(mc); new Thread(myfuture).start(); System.out.println("operate); try { System.out.println("data1=" + } catch (InterruptedException } catch (ExecutionException } }}class MyCallale implements Callable<String> @Override public Stringthrows Exception // int i0; Randomnew Random(); StringBuildernew StringBuilder(); int res0; while (i100000000) i++; res } sb.append(res); return sb.toString(); }} |
四 阻塞对象池的几种方式
在实现阻塞对象池中,可以自定义同步,有数组方式和LinkedList,由于这些都不是线程安全的,所以需要显示的进行synchronized同步处理。但是都是串行化访问的,不利于并行处理。
还可以利用基础构建模块,利用BlockingQueue和Semaphore来实现,这些内部都实现了加锁机制,更便于并发与同步的效率。
Java并发基础构建模块简介的更多相关文章
- Java 并发基础
Java 并发基础 标签 : Java基础 线程简述 线程是进程的执行部分,用来完成一定的任务; 线程拥有自己的堆栈,程序计数器和自己的局部变量,但不拥有系统资源, 他与其他线程共享父进程的共享资源及 ...
- java并发基础(二)
<java并发编程实战>终于读完4-7章了,感触很深,但是有些东西还没有吃透,先把已经理解的整理一下.java并发基础(一)是对前3章的总结.这里总结一下第4.5章的东西. 一.java监 ...
- java并发基础(五)--- 线程池的使用
第8章介绍的是线程池的使用,直接进入正题. 一.线程饥饿死锁和饱和策略 1.线程饥饿死锁 在线程池中,如果任务依赖其他任务,那么可能产生死锁.举个极端的例子,在单线程的Executor中,如果一个任务 ...
- Java并发基础概念
Java并发基础概念 线程和进程 线程和进程都能实现并发,在java编程领域,线程是实现并发的主要方式 每个进程都有独立的运行环境,内存空间.进程的通信需要通过,pipline或者socket 线程共 ...
- java并发基础及原理
java并发基础知识导图 一 java线程用法 1.1 线程使用方式 1.1.1 继承Thread类 继承Thread类的方式,无返回值,且由于java不支持多继承,继承Thread类后,无法再继 ...
- 【搞定 Java 并发面试】面试最常问的 Java 并发基础常见面试题总结!
本文为 SnailClimb 的原创,目前已经收录自我开源的 JavaGuide 中(61.5 k Star![Java学习+面试指南] 一份涵盖大部分Java程序员所需要掌握的核心知识.欢迎 Sta ...
- java并发编程实战学习(3)--基础构建模块
转自:java并发编程实战 5.3阻塞队列和生产者-消费者模式 BlockingQueue阻塞队列提供可阻塞的put和take方法,以及支持定时的offer和poll方法.如果队列已经满了,那么put ...
- 【Java并发.5】基础构建模块
本章会介绍一些最有用的并发构建模块,有丶东西(最后一小节,纯干货). 5.1 同步容器类 同步容器类包括 Vector 和 Hashtable ,这些类实现线程安全的方式是:将它们的状态封装起来,并对 ...
- 【Java并发基础】管程简介
前言 在Java 1.5之前,Java语言提供的唯一并发语言就是管程,Java 1.5之后提供的SDK并发包也是以管程为基础的.除了Java之外,C/C++.C#等高级语言也都是支持管程的. 那么什么 ...
- Java并发基础框架AbstractQueuedSynchronizer初探(ReentrantLock的实现分析)
AbstractQueuedSynchronizer是实现Java并发类库的一个基础框架,Java中的各种锁(RenentrantLock, ReentrantReadWriteLock)以及同步工具 ...
随机推荐
- python configparser 创建ini文件,动态读取与修改配置文件,以及保存与读取字符串与QColor类型的配置
# 动态配置所需 from import ConfigParser # 获取系统语系所需 import locale # QColor 类型的传参所需 from PyQt6.QtGui import ...
- Seata 核心源码详解
参考文章: 分布式事务实战方案汇总 https://www.cnblogs.com/yizhiamumu/p/16625677.html 分布式事务原理及解决方案案例https://www.cnblo ...
- 使用 nuxi init 创建全新 Nuxt 项目
title: 使用 nuxi init 创建全新 Nuxt 项目 date: 2024/9/6 updated: 2024/9/6 author: cmdragon excerpt: 摘要:本文介绍了 ...
- vuejs怎样封装一个插件(以封装vue-toast为例扩展)
插件介绍 插件通常会为 Vue 添加全局功能.插件的范围没有限制--一般有下面几种: 1.添加全局方法或者属性,如: vue-custom-element 2.添加全局资源:指令/过滤器/过渡等,如 ...
- Nuxt Kit 的使用指南:从加载到构建
title: Nuxt Kit 的使用指南:从加载到构建 date: 2024/9/12 updated: 2024/9/12 author: cmdragon excerpt: 摘要:本文详细介绍了 ...
- 搭建ipv6并发代理池
声明 本文章中所有内容仅供学习交流,抓包内容.敏感网址.数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请联系我立即删除! 学习目标 ounter(l ...
- MySQL服务无法启动 服务没有报告任何错误
安装MYSQL后 启动服务 出现错误 在启动MySQL服务时 出现该报错 解决方法: 将原本在MySQL根目录下的my.ini文件移动到bin目录下(my.ini文件参考:这里) 删除根目录下的 ...
- IDEA如何自动导入依赖的jar包
前言 我们在使用IDEA开发时,会引入第三方的jar包,这些第三方的jar包使我们可以快速的使用别人开发好的功能,而不用重复造轮子了. 这大大提高了我们的开发效率. 但是,有时候我们一下子需要导入太多 ...
- AE cc 2017 和 2018 中英文切换的方法
AE cc 2017中文切换英文的方法 找到AE的安装文件目录下的"Support Files"文件夹,路径为 C:\Program Files\Adobe\Adobe After ...
- SpringBoot 实现文件上传
参考:Java springboot进阶教程 文件上传功能实现 后端代码编写 常见错误分析与解决 在 Service 业务层接口中增加一个上传文件的方法 因为文件并不是上传至数据库中,所以不需要编写 ...