文章目录
- 前言
- 一、CountDownLatch倒计时锁
- 二、如何控制线程并发数?
- 三、浅聊ThreadLocal
- 1.ThreadLocal定义
- 2.ThreadLocal源码解读
- 3.关于ThreadLocal的一个案例
- 总结
前言
本篇介绍多线程中的应用场景,比如倒计时锁CountDownLatch、信号量Semaphore、以及ThreadLocal的理解。
一、CountDownLatch倒计时锁
CountDownLatch是JUC包下的一个类,它提供了两个方法一个是countDown(),和await(),通过配合使用可以实现让其他线程执行完再执行当前线程,我们需要创建一个CountDownLatch对象,传入一个计量参数,每次执行countDown方法,计数就会减一,await()会阻塞当前线程继续往下执行,当计数变为0时,当前线程才会继续执行。
代码案例:
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
// 初始化一个倒计时锁,参数为3
CountDownLatch latch = new CountDownLatch(3);
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "-begin...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// count--
latch.countDown();
System.out.println(Thread.currentThread().getName()+"-end..." + latch.getCount());
}, "t1").start();
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "-begin...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// count--
latch.countDown();
System.out.println(Thread.currentThread().getName()+"-end.." + latch.getCount());
}, "t2").start();
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "-begin...");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// count--
latch.countDown();
System.out.println(Thread.currentThread().getName()+"-end..." + latch.getCount());
}, "t3").start();
String name = Thread.currentThread().getName();
System.out.println(name + "-waiting...");
//等待其它线程执行完
latch.await();
System.out.println(name + "-wait end...");
}
}
以上代码中创建了CountDownLatch对象,定义了计量参数为3,同时创建了三个线程,中间分别沉睡了1000ms、3000ms、1500ms,每个线程执行完都会进行countDown操作,等三个线程都执行完计量参数就变成0了,此时主线程的代码才会继续执行。
程序运行结果:
二、如何控制线程并发数?
多线程中,如何控制接口的并发访问的数量呢,JUC包下提供了一个类,Semaphore信号量,我们需要创建一个Semaphore对象,传入一个允许并发访问的数量,同时线程运行时可以使用acquare()方法让计数减一,当计数减到0,其他线程就会进入等待,直到运行的线程调用release(),计数就会+1,其他线程才可以继续执行。
代码如下:
public class SemaphoreCase {
// static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
// 1.创建信号量semaphoreCase对象
Semaphore semaphore = new Semaphore(3);
// 2.10个线程同时运行
for (int i = 0; i < 10; i++){
new Thread(()->{
// 获取许可,计数减一
try {
semaphore.acquire();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
try{
System.out.println("running...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("ending...");
}
finally {
// 释放许可 计数+1
semaphore.release();
}
}).start();
}
}
}
三、浅聊ThreadLocal
ThreadLocal是多线程中对于解决线程安全的一个操作类,它会为每个线程都分配一个独立的线程副本从而解决了变量并发访问冲突的问题。而ThreadLocal同时实现了线程内的资源共享。ThreadLocal本质来说就是一个线程内部存储类,从而让多个线程只操作自己内部的值,从而实现线程数据隔离。
1.ThreadLocal定义
- ThreadLocal可以实现资源对象的线程隔离,让每个线程各用各的资源对象,避免争用引发的线程安全问题
- ThreadLocal同时实现了线程内的资源共享
2.ThreadLocal源码解读
set方法:
首先获取当前访问的线程,通过当前线程获取ThreadLocalMap对象,它是ThreadLocal的一个静态内部类,它里面有一个Entry[]数组,用于存储元素信息,如果首次创建就会走else创建map就会走ThreadLocalMap的构造方法逻辑,先取ThreadLocal作为key取它的hash值,然后使用&运算符对数据长度取模得到数组下标,将元素值存储到数组中。
若不是首次创建就会走map.set方法
如果上次存储了元素,由于两次的ThreadLocal的hash值是一样的,所以本次存储的时候,会进行值替换操作然后return;
get方法:
get方法就比较简单,也是通过ThreadLocal对象获取它的hash值对数组取模定位下标,然后返回这个下标位置上的值。若没有set操作过,则返回setIntialValue()这个是一个null值。
remove方法:
remove方法也是同理,找出下标删除数组中的元素即可。
3.关于ThreadLocal的一个案例
public class ThreadLocalCase {
static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
// 在主线程存储一个字符串数据
threadLocal.set("test01");
// 开启一个线程
new Thread(()->{
try{
threadLocal.set("test02");
// 在子线程中能否获取到存储的test01呢?
System.out.println(Thread.currentThread().getName() + "-get: " + threadLocal.get());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}finally {
// 调用remove方法清楚,避免OOM
threadLocal.remove();
}
}, "t1").start();
// 主线程中获取存储的test01?
System.out.println(Thread.currentThread().getName() + "-get: " + threadLocal.get());
Thread.sleep(1000);
// 调用remove方法清楚,避免OOM
threadLocal.remove();
}
}
以上代码中,在主线程中set一个字符串,同时定义了一个子线程,在子线程中也set一个字符串,两个值是不影响的,主线程只能获取它自己set的值,而子线程也是获取它set的值,运行情况:
总结
以上是对多线程的一些运用场景进行了分析,以及从源码解读了ThreadLocal为啥能实现资源对象的线程隔离,它为每个线程都分配了一个独立的线程副本,不管是get还是set操作,都会先获取当前访问的线程,从而实现线程隔离。