java.util.concurrent工具包的简称就是JUC,这是一个处理线程的工具包 。AQS 被认为是 J.U.C 的核⼼
目录
CountDownLatch类 计数器
Semaphore类 信号量
CyclicBarrier类 循环屏障
J.U.C - 其它组件
FutureTask
BlockingQueue 阻塞队列
常用API
BlockingQueue队列下的生产者和消费者
CountDownLatch类 计数器
如果某个线程的执行需要等待其它线程执行完,CountDownLatch能够阻塞当前线程,当其它线程倒数完成后,再执行,相当于大号的join()
创建时要定义倒计时的数字:
new CountDownLatch(int 倒计时数) 实际上就是要等待的线程数
主要API:
- await() 阻塞当前线程
- countDown() 倒计时1次 -1
- getCount() 获得倒计时数
**
* 使用CountDownLatch
*/
public class CountDownLatchDemo {
public static void main(String[] args) {
//定义CountDownLatch对象
CountDownLatch latch = new CountDownLatch(3);
Thread thread = new Thread(() ->{
System.out.println(Thread.currentThread().getName()+"准备好了!"+latch.getCount());
try {
//阻塞当前线程,直到有3个线程调用countdown方法,将计数器减到0才会继续执行后面的内容
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"冲刺了,到达终点!");
});
thread.setName("闪电");
thread.start();
for (int i = 0; i < 3; i++) {
new Thread(() -> {
//倒计时一次 将次数-1,
latch.countDown();
System.out.println(Thread.currentThread().getName()+"倒计时完成!"+latch.getCount());
}).start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Semaphore类 信号量
Semaphore 类似于操作系统中的信号量,可以控制对互斥资源的访问线程数
信号量Semaphore 就是一个计数器,表示当前可用资源的个数。可以控制线程的启动数量,从而控制并发量
它允许n个任务同时访问某个资源,可以将信号量看做是在向外分发使用资源的许可证,只有成功获取许可证,才能使用资源。
可以把它简单的理解成我们停车场入口立着的那个显示屏,每有一辆车进入停车场显示屏就会显示剩余车位减1,每有一辆车从停车场出去,显示屏上显示的剩余车辆就会加1,当显示屏上的剩余车位为0时,停车场入口的栏杆就不会再打开,车辆就无法进入停车场了,直到有一辆车从停车场出去为止。
创建时设置信号量:
new Semaphone(int 可用资源) //创建信号量 参数1 信号量值 参数2 是否公平锁(公平锁,先等待获得锁几率更大,非公平锁,都一样)
核心操作:PV:P-申请资源操作 V-释放资源操作
Semaphore的PV加减操作都是原子性的,在多线程场景下可以直接使用
常用API:
- acquire() 申请一个信号量 P操作,每次申请一个资源,如果可用资源为空,则进入阻塞状态,直到有其他线程释放资源
- acquire(int n) 每次申请n个资源
- release() 释放信号量 V操作,每次释放一个资源
- release(int n) 每次释放n个资源
- availablePermits() 获取当前可用信号量数
//以下代码模拟了对某个服务的并发请求,每次只能有 3 个客户端同时访问,请求总数100
public class SemaphoreExample {
public static void main(String[] args) {
final int clientCount = 3;
final int totalRequestCount = 10;
Semaphore semaphore = new Semaphore(clientCount);
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < totalRequestCount; i++) {
executorService.execute(()->{
try {
System.out.print(semaphore.availablePermits() + " ");
if(semaphore.availablePermits()==0){
System.out.println("资源不足,请等待。。。");
}
semaphore.acquire();
Thread.sleep(new Random().nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
});
}
executorService.shutdown();
}
}
CyclicBarrier类 循环屏障
⽤来控制多个线程互相等待,只有当多个线程都到达时,这些线程才会继续执⾏。
和 CountdownLatch 相似,都是通过维护计数器来实现的。线程执⾏ await() ⽅法之后计数器会减 1, 并进⾏等待,直到计数器为 0,所有调⽤ await() ⽅法⽽在等待的线程才能继续执⾏
CyclicBarrier 和 CountdownLatch 的⼀个区别是,CyclicBarrier 的计数器通过调⽤ reset() ⽅法可以循 环使⽤,所以它才叫做循环屏障
CountdownLatch中子线程并不会因为调用countDown()而阻塞,会继续进行该做的工作,只是通知计数器-1,只需要在所有进程都进行到某一节点后才会执行被阻塞的进程.如果我们想要多个线程在同一时间进行就要用到CyclicBarrier了 ,调用await()会阻塞当前线程,并通知计数器-1,归0后会同一时间执行之前阻塞的线程
CyclicBarrier 有两个构造函数,其中 parties 指示计数器的初始值,barrierAction 在所有线程都到达屏障的时候会执⾏⼀次
public class CyclicBarrierExample {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(10);
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < totalThread; i++) {
executorService.execute(() -> {
System.out.print("before..");
try {
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
System.out.print("after..");
});
}
executorService.shutdown();
}
}
//输出结果
//before..before..before..before..before..before..before..before..before..before..after..after..after..after..after..after..after..after..after..after..
J.U.C - 其它组件
FutureTask
在介绍 Callable 时我们知道它可以有返回值,返回值通过 Future 进⾏封装。FutureTask 实现了 RunnableFuture 接⼝,该接⼝继承⾃ Runnable 和 Future 接⼝,这使得 FutureTask 既可以当做⼀ 个任务执⾏,也可以有返回值
作用:FutureTask 可⽤于异步获取执⾏结果或取消执⾏任务的场景。当⼀个计算任务需要执⾏很⻓时间,那么 就可以⽤ FutureTask 来封装这个任务,主线程在完成⾃⼰的任务之后再去获取结果。
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> futureTask = new FutureTask<Integer>(
new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int result = 0;
for (int i = 0; i < 100; i++) {
Thread.sleep(10);
result += i;
}
return result;
}
});
Thread computeThread = new Thread(futureTask);
computeThread.start();
Thread otherThread = new Thread(() -> {
System.out.println("other task is running...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
otherThread.start();
System.out.println(futureTask.get());//other task is running... 4950
}
BlockingQueue 阻塞队列
阻塞队列是一种集合,当数据达到临界值时自动让生产者线程等待,数据为空时自动让消费者线程等待,也能根据条件自动唤醒线程
特点:线程安全,无法扩容,采用了生产者消费者模式
阻塞添加:当队列已满时,往队列中添加元素会阻塞等待
阻塞删除:当队列为空时,往队列中删除或获取元素将被阻塞
常用实现类有:
- ArrayBlockingQueue 有界队列 基于数组结构的一个有界阻塞队列,先进先出,存取互相排斥(存取是同一把锁,操作的是同一数组对象)
- LinkedBlockingQueue 无界队列 基于链表结构的一个无界阻塞队列,先进先出,存取互不干扰(锁分离,存取不同的Node对象)可以指定容量
- DelayQueue 带延迟的阻塞队列 基于优先级队列实现的无界阻塞队列,为空时阻塞
- SynchronousQueue 直接提交 不存储元素的阻塞队列,是一个没有缓存的Blocking,不会为队列中元素维护存储空间,它只是多个线程之间 数据交换的媒介
- PriorityBlockingQueue 优先级队列
线程池用的是LinkedBlockingQueue 线程池的排队策略与BlockingQueue有关 (可查看线程池 newScheduledThreadPool
的创建方式)。
常用API
方法 | 抛出异常 | 不会抛出异常,有返回值 | 阻塞等待 | 超时等待 |
添加 | add() 到临界值会抛异常 | offer() | put() 到临界值自动阻塞 | offer(e,timeout,TimeUnit) |
移除 | remove() 为空抛出异常 | poll() | take() 队列为空自动阻塞 | poll(e,timeout,TimeUnit) |
获取队首元素 | element() | peek() |
说明:
抛出异常:add()队列到临界值抛的异常是IllegalStateException ;remove()队列为空是抛的异常是NoSuchException
有返回值:offer()插入结果返回布尔值,poll()移除结果返回移除的元素
阻塞:put()队列到临界值会一直阻塞,直到put成功或者出现中断退出;take()队列为空会一直阻塞,直到队列可用
超时等待:当队列满时,会阻塞生产者线程一段时间,超过时间后生产者线程会自动退出
BlockingQueue队列下的生产者和消费者
class MyResource {
// 默认开启,进行生产消费
// 这里用到了volatile是为了保持数据的可见性,也就是当TLAG修改时,要马上通知其它线程进行修改
private volatile boolean FLAG = true;
// 使用原子包装类,而不用number++
private AtomicInteger atomicInteger = new AtomicInteger();
// 这里不能为了满足条件,而实例化一个具体的SynchronousBlockingQueue
BlockingQueue<String> blockingQueue = null;
// 而应该采用依赖注入里面的,构造注入方法传入
public MyResource(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
// 查询出传入的class是什么
System.out.println(blockingQueue.getClass().getName());
}
public void myProducer() throws Exception{
String data = null;
boolean retValue;
// 多线程环境的判断,一定要使用while进行,防止出现虚假唤醒
// 当FLAG为true的时候,开始生产
while(FLAG) {
data = atomicInteger.incrementAndGet() + "";
// 2秒存入1个data
retValue = blockingQueue.offer(data, 2L, TimeUnit.SECONDS);
if(retValue) {
System.out.println(Thread.currentThread().getName() + "\t 插入队列:" + data + "成功" );
} else {
System.out.println(Thread.currentThread().getName() + "\t 插入队列:" + data + "失败" );
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "\t 停止生产,表示FLAG=false,生产介绍");
}
public void myConsumer() throws Exception{
String retValue;
// 多线程环境的判断,一定要使用while进行,防止出现虚假唤醒
// 当FLAG为true的时候,开始生产
while(FLAG) {
// 2秒移除1个data
retValue = blockingQueue.poll(2L, TimeUnit.SECONDS);
if(retValue != null && retValue != "") {
System.out.println(Thread.currentThread().getName() + "\t 消费队列:" + retValue + "成功" );
} else {
FLAG = false;
System.out.println(Thread.currentThread().getName() + "\t 消费失败,队列中已为空,退出" );
// 退出消费队列
return;
}
}
}
/**
* 停止生产的判断
*/
public void stop() {
this.FLAG = false;
}
}
public class BlockingQueueProducerConsumer {
public static void main(String[] args) {
// 传入具体的实现类, ArrayBlockingQueue
MyResource myResource = new MyResource(new ArrayBlockingQueue<String>(10));
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t 生产线程启动\n\n");
try {
myResource.myProducer();
System.out.println("\n");
} catch (Exception e) {
e.printStackTrace();
}
}, "producer").start();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t 消费线程启动");
try {
myResource.myConsumer();
} catch (Exception e) {
e.printStackTrace();
}
}, "consumer").start();
// 5秒后,停止生产和消费
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("\n\n5秒中后,生产和消费线程停止,线程结束");
myResource.stop();
}
}