1. 目录
- ThreadLocal
- CountDownLatch
- CyclicBarrier
- Semaphore
- Exchanger
- AtomicInteger
2. ThreadLocal
2.1 介绍:
即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。这
个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值
2.2 应用场景
同一个线程下共享同一个值,可以获取、修改和删除这个值,是线程安全的操作
2.3 常用方法
//获取ThreadLocal在当前线程中保存的变量
public T get()
//设置ThreadLocal在当前线程中保存的变量
public void set(T value)
//删除ThreadLocal在当前线程中保存的变量
public void remove()
2.4 实例
在web工程中运用AOP统计controller层的方法调用时间
@Aspect
@Component
public class TimeAspect {
ThreadLocal<Long> timeThreadLocal = new ThreadLocal<Long>();
@Pointcut("execution(public * com..*.controller.*Controller.*(..))")
public void webLog() {
}
/**
* @Before 在方法执行之前执行
*/
@Before("webLog()")
public void before(JoinPoint joinPoint) {
//设置方法调用前时间
timeThreadLocal.set(System.currentTimeMillis());
}
/**
* @After在方法执行之后执行
*/
@AfterReturning(value = "webLog()", returning = "returnData")
public void doAfterReturning(JoinPoint joinPoint, Object returnData) {
//获取本次请求日志实体
long beginTime = timeThreadLocal.get();
System.out.println(System.currentTimeMillis() - beginTime);
}
}
如果使用静态变量存储timeThreadLocal,会被别的进程给修改,如果用普通变量存储,因为spring默认是单例的,也会被修改。
3. CountDownLatch
3.1 介绍
可以看成是一个计数器,其内部维护着一个count计数,只不过对这个计数器的操作都是原子操作,同时只能有一个线程去操作这个计数器,CountDownLatch通过构造函数传入一个初始计数值,调用者可以通过调用CounDownLatch对象的cutDown()方法,来使计数减1;如果调用对象上的await()方法,那么调用者就会一直阻塞在这里,直到别人通过cutDown方法,将计数减到0,才可以继续执行
3.2 应用场景
计数器功能,不能复位
3.3 常用方法
//总数减一
public void countDown()
//总数减count
public CountDownLatch(int count)
//线程阻塞,一直到总数为0才解除阻塞
public void await()
//等待某个时间后继续执行
public boolean await(long timeout, TimeUnit unit)
方法 | 说明 |
---|---|
countDown() | 总数减一 |
await() | 线程阻塞,一直到总数为0才解除阻塞 |
3.4 实例
说明:主程序启动时,启动了另外5个线程进行初始化数据,主线程必须等到所有的线程完成初始化后才能提示启动成功
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println("开始初始化执行完毕 ");
//5个线程需要初始化
CountDownLatch countDownLatch = new CountDownLatch(5);
//用于睡眠的秒数递增
AtomicInteger atomicInteger = new AtomicInteger(0);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
try {
int sleepTime = atomicInteger.incrementAndGet();
TimeUnit.SECONDS.sleep(sleepTime);
System.out.println(String.format("睡眠%d秒的线程初始化完成", sleepTime));
} catch (InterruptedException e) {
e.printStackTrace();
}
countDownLatch.countDown(); //递减
}).start();
}
countDownLatch.await(); //阻塞
System.out.println("初始化执行完毕 ");
}
}
4. CyclicBarrier
4.1 介绍
字面意思回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。我们暂且把这个状态就叫做barrier,当调用await()方法之后,线程就处于barrier了
4.2 应用场景
等待一组线程统一到某一个状态后,再全部同时执行
4.3 常用方法
构造方法:
//参数parties指让多少个线程或者任务等待至barrier状态;
//参数barrierAction为当这些线程都达到barrier状态时会执行的内容
public CyclicBarrier(int parties, Runnable barrierAction) {
}
public CyclicBarrier(int parties) {
}
普通方法:
//用来挂起当前线程,直至所有线程都到达barrier状态再同时执行后续任务
public int await()
//超时时间,如果该时间后还没有线程达到barrier状态就直接让到达barrier状态的线程执行后续任务
public int await(long timeout, TimeUnit unit)
4.4 实例
**说明:**当4个线程都完成数据写入的时候,再一起执行后续操作。
public class CyclicBarrierTest {
public static void main(String[] args) {
int N = 4;
CyclicBarrier barrier = new CyclicBarrier(N);
for(int i=0;i<N;i++){
new Writer(barrier).start();
}
}
static class Writer extends Thread{
private CyclicBarrier cyclicBarrier;
public Writer(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
System.out.println("线程"+Thread.currentThread().getName()+"正在写入数据...");
try {
Thread.sleep(5000); //以睡眠来模拟写入数据操作
System.out.println("线程"+Thread.currentThread().getName()+"写入数据完毕,等待其他线程写入完毕");
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
}catch(BrokenBarrierException e){
e.printStackTrace();
}
System.out.println("所有线程写入完毕,继续处理其他任务...");
}
}
}
5. Semaphore
5.1 介绍
翻译成字面意思为 信号量,Semaphore可以控同时访问的线程个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。
5.2 应用场景
并发线程数阈值的控制
5.3 常用方法
构造方法:
//参数permits表示许可数目,即同时可以允许多少线程进行访问
public Semaphore(int permits)
//参数fair表示是否是公平的,即等待时间越久的越先获取许可
public Semaphore(int permits, boolean fair)
普通方法:
//获取一个许可
public void acquire() throws InterruptedException { }
//获取permits个许可
public void acquire(int permits) throws InterruptedException { }
//释放一个许可
public void release() { }
//释放permits个许可
public void release(int permits) { }
5.4 实例
说明:假设有10个人请求数据库连接,但是数据库只有5个连接资源,只能等有连接释放后等待的请求才能连接数据库
public static void main(String[] args) {
int N = 8; //请求数
Semaphore semaphore = new Semaphore(5); //数据库连接数目
for(int i=0;i<N;i++)
new Connect(i,semaphore).start();
}
static class Connect extends Thread{
private int num;
private Semaphore semaphore;
public Connect(int num, Semaphore semaphore){
this.num = num;
this.semaphore = semaphore;
}
@Override
public void run() {
try {
semaphore.acquire();
System.out.println("线程"+this.num+"占用一个数据库连接");
Thread.sleep(2000);
System.out.println("线程"+this.num+"释放出连接");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
6. Exchanger
6.1 介绍
是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。它提供一个同步点,在这个同步点,两个线程可以交换彼此的数据。这两个线程通过exchange方法交换数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方
6.2 应用场景
两个线程交互数据
6.3 常用方法
//交互数据,当数据没接收到时,线程阻塞
public V exchange(V x)
//避免阻塞,一定时间未收到数据继续往下执行
public V exchange(V x, long timeout, TimeUnit unit)
6.4 实例
说明:
我们需要将纸制银行流水通过人工的方式录入成电子银行流水,为了避免错误,采用AB岗两人进行录入,录入到Excel之后,系统需要加载这两个Excel,并对两个Excel数据进行校对,看看是否录入一致
public class ExchangerTest {
private static final Exchanger<String> exgr = new Exchanger<String>();
public static void main(String[] args) {
new Thread(()->{
try {
String A = "银行流水A";// A录入银行流水数据
System.out.println("A收到的数据是:" + exgr.exchange(A));
} catch (InterruptedException e) {
}
}).start();
new Thread(()->{
try {
String B = "银行流水B";// B录入银行流水数据
String A = exgr.exchange(B);
System.out.println("A和B数据是否一致:" + A.equals(B) + ",A录入的是:"
+ A + ",B录入是:" + B);
} catch (InterruptedException e) {
}
}).start();
}
}
7. AtomicInteger
7.1 介绍
当程序更新一个变量时,如果多线程同时更新这个变量,可能得到期望之外的值,通常我们会使用synchronized来解决这个问题,synchronized会保证多线程不会同时更新变量i。
而Java从JDK 1.5开始提供了java.util.concurrent.atomic包(以下简称Atomic包),这个包中的原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式。
因为变量的类型有很多种,所以在Atomic包里一共提供了13个类,属于4种类型的原子更新方式,分别是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性(字段)。Atomic包里的类基本都是使用Unsafe实现的包装类
7.2 应用场景
线程安全的更新变量
7.3 常用方法
//获取当前值
public final int get()
//设置当前值
public final void set(int newValue)
//设置并获取当前值
public final int getAndSet(int newValue)
//将原先为expect的值设置为update
public final boolean compareAndSet(int expect, int update)
//原先数据+1后返回+1后的值
public final int incrementAndGet()
//返回当前值后再+delta,返回结果还是未+delta的值
public final int getAndAdd(int delta)
7.4 实例
public static void main(String[] args) {
//初始值为1
AtomicInteger ai = new AtomicInteger(1);
System.out.println(String.format("当前值为:%d",ai.get()));
System.out.println(String.format("当前值+1后的值为:%d",ai.incrementAndGet()));
//先返回后+5
System.out.println(String.format("+5前的的值为:%d",ai.getAndAdd(5)));
System.out.println(String.format("+5后的值为:%d",ai.get()));
//当前值由7设置为10,当前值如何实际值不匹配,设置失败
ai.compareAndSet(7,10);
System.out.println(String.format("当前值为:%d",ai.get()));
}