首先我们通常说的并发包就是java.util.concurrent包及其子包。集中了Java并发的各种基础工具类。
一、这个并发包在哪
上面的包就是传说中的并发包。
为什么这个并发包就比较流弊呢?
原因主要有以下几点。
- 提供了几个比synchronized更加高级的各种同步结构。例如:CountDownLatch、CyclicBarrier、Semaphore等。可以实现更加丰富的多线程的操作。比如利用Semaphore作为资源控制器(其实可以简单理解为信号量),限制同时工作的线程的数量。
- 各种线程安全的容器。例如:无序的ConcurrentHashMap、有序的ConcurrentSkipListMap和线程安全的动态数组CopyOnWriteArrayList等。
- 各种并发队列的实现。比较典型的有ArrayBlockingQueue、SynchorousQueue或者优先级队列PriorityBlockingQueue等。
- 强大的Executor框架,可以创建各种不同类型的线程池,调度任务的运行,绝大部分情况下,用户不需要自己从头实现线程池和调度任务。
二、讲几个比较流弊用的较多的
万变不离其宗,抛砖引玉,如果这几个基础的你了解的比较好,以后再深入拓展就比较简单了。
空谈误国,实干兴邦,谢谢看嘛。
1、Semaphore
简单说,他就是一个计数器,其基本逻辑基于acquire/release申请或者获得资源。
- Semaphore:Java版本的信号量的实现。用这个你就能实现对资源的管理。例如你一辆车做五个人,那么就赋值5。你只有一台打印机可以用,那就是1。
- 1
package com.newframe.controllers.api;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import java.util.concurrent.Semaphore;
/**
* 第一步
* 创建一个实现了Runnable接口的信号量工作类
* 并重写它的run方法,让他做我想要做的事情
*/
public class SemaphoreWorker implements Runnable{
private String name;
private Semaphore semaphore;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//创建一个构造函数
public SemaphoreWorker(Semaphore semaphore) {
this.semaphore = semaphore;
}
@Override
public void run() {
try {
log("准备申请一个资源");
semaphore.acquire();//申请资源
log("申请到了资源");
log("执行");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//释放资源
semaphore.release();
log("释放资源成功");
}
}
private void log(String msg){
if (StringUtils.isEmpty(msg)){
name = Thread.currentThread().getName();
}
System.out.println(name + " " + msg);
}
}
- 2
package com.newframe.controllers.api;
import java.util.concurrent.Semaphore;
/**
* 第二步
* 创建一个Semaphore的测试类
*/
public class TestSemaphore {
public static void main(String[] args) {
System.out.println("我要开始执行了");
//创建一个Semaphore对象,允许3个资源
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 10; i++) {
SemaphoreWorker worker = new SemaphoreWorker(semaphore);
//给线程一个名字
worker.setName("线程"+i);
Thread thread = new Thread(worker);
thread.start();
}
}
}
在上面可以很清晰的看到Semaphore对资源信号量的控制。
如我上面的信号量给的3,就是最多只能有3个线程能同时申请到资源。其余线程需要等待申请到的线程释放资源后才能获得资源。
看输出结果:
2、CountDownLatch和CyclicBarrier
他们的行为有一定的相似度。经常会用来要你理解他们有什么区别。
- CountDownLatch 控制的这个数量是不可以重置的,两个线程不能够同时抢占CountDownLatch控制的资源数量。在前面的线程执行完后,后面的线程才可以执行。
- CycliBarrier这个就很有意思了。这个比如说你有5个士力架,你上学,你吃完了,你一定要把5个都吃完,你立马可以获得新的5个士力架。你怕不怕。
3、CountDownLatch
下面我们来举个例子
- 1
package com.newframe.controllers.api;
import java.util.concurrent.CountDownLatch;
/**
* 创建第一个吃巧克力的家伙,叫他小明吧
* 实现Runnable接口,并重写其方法
*/
public class FirstBatchWorker implements Runnable{
private CountDownLatch latch;
public FirstBatchWorker(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
//执行countDown()操作
latch.countDown();
System.out.println("小明吃了一块巧克力");
}
}
- 2
package com.newframe.controllers.api;
import java.util.concurrent.CountDownLatch;
/**
* 创建第二个吃巧克力的家伙,叫他小李吧
* 实现Runnable接口,并重写其方法
*/
public class SecondBatchWorker implements Runnable{
private CountDownLatch latch;
public SecondBatchWorker(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
try {
latch.await();
System.out.println("小李吃了一块巧克力");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 3
package com.newframe.controllers.api;
import java.util.concurrent.CountDownLatch;
/**
* 来测试一下LatchSample
*/
public class TestCountDownLatch {
public static void main(String[] args) {
//一共5个巧克力
//同时有5个线程可以获取到资源
CountDownLatch latch = new CountDownLatch(5);
//下面的线程是按照顺序执行的。
//五个小明线程
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(new FirstBatchWorker(latch));
thread.start();
}
//这个线程一定要等到上个线程全部执行完成了才可以执行
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(new SecondBatchWorker(latch));
thread.start();
}
}
}
执行结果
小明吃了一块巧克力
小明吃了一块巧克力
小明吃了一块巧克力
小明吃了一块巧克力
小明吃了一块巧克力
小明吃了一块巧克力
小明吃了一块巧克力
小明吃了一块巧克力
小明吃了一块巧克力
小明吃了一块巧克力
小李吃了一块巧克力
小李吃了一块巧克力
小李吃了一块巧克力
小李吃了一块巧克力
小李吃了一块巧克力
CountDownLatch用于线程间等待操作结束是非常普遍简单的用法。
4、CyclicBarrier
如果说CountDownLatch
是用于线程间等待操作结束的协调,那么CyclicBarrier
其实反映的是线程间并行运行时的协调。
下面来看一个例子。
- 1、
package com.newframe.controllers.api;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* 创建一个CyclicBarrier类,实现Runable接口
*/
public class CyclicBarrierWorker implements Runnable{
private CyclicBarrier cyclicBarrier;
public CyclicBarrierWorker(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
try {
for (int i = 0; i < 3; i++) {
System.out.println("执行");
cyclicBarrier.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
- 2、
package com.newframe.controllers.api;
import java.util.concurrent.CyclicBarrier;
/**
* 测试一下CyclicBarrier
*/
public class TestCyclicBarrier {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(5, new Runnable() {
@Override
public void run() {
System.out.println("操作再次......执行");
}
});
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(new CyclicBarrierWorker(barrier));
thread.start();
}
}
}
- 执行结果
执行
执行
执行
执行
执行
操作再次......执行
执行
执行
执行
执行
执行
操作再次......执行
执行
执行
执行
执行
执行
操作再次......执行
三、Java并发库的小知识
Java并发库还提供了Phaser,功能与CountDownLatch很接近。
Java并发库还提供了线程安全的Map、List和Set等容器。
ConcurrentHashMap
:侧重通过键值对,放入或者获取的速度,对顺序无所谓。ConcurrentSkipListMap
:通过键值对操作数据,侧重数据的顺序操作。如果对大量数据进行频繁的修改,ConcurrentSkipListMap
也是有优势的。
好啦,今天就到这里啦。好记性比不上烂笔头,还是的亲自实践一下的。