在开发过程中我们经常遇到高并发之后使用到多线程,线程池等相关内容,但是我们经常遇到在多个线程里面操作同一个变量出现了高并发数据问题,这种情况其实可以通过本地线程进行避免.
public static void main(String[] args) {
// 创建一个线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor
(10,20,60,TimeUnit.SECONDS,new SynchronousQueue<>());
// 创建一个本地变量
ThreadLocal<String> threadLocal = new ThreadLocal<>();
// 本地变量塞入值
threadLocal.set("张三");
// 使用线程池运行在里面获取本地线程的内容
executor.submit(new Runnable() {
@Override
public void run() {
System.out.println("当前是线程池"+threadLocal.get());
}
});
// new出来一个线程获取本地线程的内容
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("当前是new出来的线程"
+threadLocal.get());
}
}).start();
System.out.println("当前是主线程"+Thread.currentThread()
.getName()+threadLocal.get());
}
}
// 运行结果:
当前是线程池null
当前是主线程main张三
当前是new出来的线程null
- 不难发现本地线程存入数据就真的只是当前所在线程可以获取到其他线程都是获取不到的
本地线程存的值是放在哪里的 怎么区分不同本地线程里面的数据,我们通过查看源码来探索一下
// 我们创建一个本地线程 往里面set值的时候下面的代码就是源码,首先获取到当前线程
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
//之后调用了getMap方法,我们进去看一下,通过当前线程获取到当前本地线程里面的threadLocals属性
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// 继续往里面跟进,默认值是null
// 注意 这里的ThreadLocal.ThreadLocalMap是保存在当前线程对象里面的
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
回到上面的代码返回的ThreadLocalMap返回值是null然后进入else里面的creatMap
可以看到这里创建的ThreadLocalMap里面的key值就是当前new出来的本地线程,
值就是我们set进去的对象
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
通过翻看源码我们发现,不同线程之间会通过获取 当前线程对象 然后获取
其本地线程属性之后本地线程属性里面保存
key是当前本地线程对象,也就是存在Thread对象里面的属性值:ThreadLocal.ThreadLocalMap
值就是之前set进去的值
InheritableThreadLocal和ThreadLocal区别最主要的是在父线程的值获取上面
ThreadLocal是获取不到父线程里面的设置的值而InheritableThreadLocal可以获取到 我们通过代码看一下
public class TestThreadLocal {
public static void main(String[] args) {
InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
ThreadLocal<String> threadLocal = new ThreadLocal<String>();
inheritableThreadLocal.set("555");
threadLocal.set("123");
doString(inheritableThreadLocal,threadLocal);
}
public static void doString(InheritableThreadLocal<String> inheritableThreadLocal,ThreadLocal<String> threadLocal){
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+":inheritableThreadLocal的值是:"+inheritableThreadLocal.get());
System.out.println(Thread.currentThread().getName()+":threadLocal的值是:"+threadLocal.get());
// 在创建一个子线程查看是否能获取到
new Thread(() -> System.out.println(Thread.currentThread().getName()+"inheritableThreadLocal的值是的值是:"+inheritableThreadLocal.get())).start();
}).start();
}
}
结果:
Thread-0:inheritableThreadLocal的值是:555
Thread-0:threadLocal的值是:null
Thread-1inheritableThreadLocal的值是的值是:555
可以看到InheritableThreadLoacl就算嵌套线程也是可以获取到父线程
里面设置的值的,但是ThreadLocal就不行了
线程池shutdown是否还可以在继续使用
// 创建线程池设置相关参数
ThreadPoolExecutor executor = new ThreadPoolExecutor(10,20,
60,TimeUnit.SECONDS,new SynchronousQueue<>());
executor.submit(() -> System.out.println("我是你爹"));
// 线程池关闭
executor.shutdown();
// 再次使用线程池
executor.submit(() -> System.out.println("哈哈哈"));
运行结果:
我是你爹
报错.......
线程池相关参数
- corePoolSize:线程池最小线程数量,也就是初始化数量,如果设置了allowCoreThreadTimeOut那么如果线程池里面的线程数量等于核心数量且处于空闲状态那么可以回收,反之不可回收
- maximumPoolSize:线程池最大线程数量
- keepAliveTime:线程池里面的线程处于空闲状态多长时间进行回收
- unit:上述时间的单位
- workQueue:当线程有任务进来的时候会先进入到队列里面,一般有下面几种队列:
1.ArrayBlockingQueue:当任务进来之后会先使用初始化的核心线程数去执行任务,当核心线程都被使用且任务就会进入队列,队列缓存的任务数达到了定义的数量就会创建新的线程去执行任务,但是这里的可以创建新线程的数量就是,最大线程数-核心线程数通过下面的代码看一下
public class ThreadPoolTest {
// 定义缓存数量为20的队列
static ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue(20);
// 定义一个核心线程数是2 最大线程数是5的线程池
static ThreadPoolExecutor executor =
new ThreadPoolExecutor(2,5,60, TimeUnit.SECONDS,arrayBlockingQueue);
public static void main(String[] args) {
for (int i = 0; i < 26; i++) {
System.out.println("当前队列长度是"+arrayBlockingQueue.size());
executor.execute(() -> {
System.out.println(Thread.currentThread().getName() + "执行了");
try {
// 这里让线程睡60秒用来测试一共可以有多少个线程进来 判断缓存队列
Thread.currentThread().sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
}
运行结果: 可以从运行结果来看刚开始线程1和2执行了队列长度都是0后面队列差浓度慢慢变长,
到20后又开始有新的3个线程去执行任务,是因为最大线程数5-核心线程数数2 = 3
当前队列长度是0
当前队列长度是0
当前队列长度是0
当前队列长度是1
pool-1-thread-1执行了
pool-1-thread-2执行了
当前队列长度是2
当前队列长度是3
当前队列长度是4
当前队列长度是5
当前队列长度是6
当前队列长度是7
当前队列长度是8
当前队列长度是9
当前队列长度是10
当前队列长度是11
当前队列长度是12
当前队列长度是13
当前队列长度是14
当前队列长度是15
当前队列长度是16
当前队列长度是17
当前队列长度是18
当前队列长度是19
当前队列长度是20
当前队列长度是20
当前队列长度是20
当前队列长度是20
pool-1-thread-3执行了
pool-1-thread-4执行了
pool-1-thread-5执行了
2.LinkedBlockingQuene:按照笔者的理解这是一个长度为Integer.MAX长度的队列可以指定长度,只要有任务就塞进去,之后线程池里面只要有空闲的线程就来这里面拿任务执行,如果核心线程全部有任务在执行那么就会进入任务队列如果任务队列满了就会启动最大线程去执行.
3.SynchronousQuene:这个队列和ArrayBlockingQueue最大的区别就是数组阻塞队列是可以缓存任务的,就是刚才所说的线程里面的任务队列,而SynchronousQuene每个线程没有任务队列,如果有任务进来就立马去线程池拿线程执行任务,没线程就创建,当池里面的数量大于最大线程数量就拒绝执行
拒绝策略:
// 创建一个线程任务
public class MyThread implements Runnable{
private String threadName;
public MyThread(String name){
this.threadName = name;
}
@Override
public void run() {
try {
System.out.println("当前线程"+threadName+"正在运行");
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- DiscardOldestPolicy:如果核心线程最大线程和队列全部都是满了的状态就会弹出队列第一个任务把新的任务放到任务最后.
public class ThreadPoolTest {
public static void main(String[] args) {
// 定义一个不缓存任务的SynchronousQueue 如果拒绝就弹出第一个任务的线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(1,2,30,
TimeUnit.SECONDS,new LinkedBlockingQueue<>(2),new ThreadPoolExecutor.DiscardOldestPolicy());
for (int i = 1; i < 7; i++) {
System.out.println("添加任务:"+i);
executor.execute(new MyThread("线程"+i));
}
Iterator<Runnable> iterator = executor.getQueue().iterator();
while(iterator.hasNext()){
MyThread next =(MyThread) iterator.next();
System.out.println("当前队列还有任务"+next.threadName);
}
}
}
结果:
添加任务:1
添加任务:2
添加任务:3
添加任务:4
添加任务:5
当前线程线程1正在运行
添加任务:6
当前线程线程4正在运行
当前队列还有任务线程5
当前队列还有任务线程6
当前线程线程5正在运行
当前线程线程6正在运行
解释:
首先代码运行添加6个任务到线程池里面没问题
之后因为我们创建的是一个核心线程两个最大线程和2个长度的队列
所以第一个任务进来就直接执行了,第二个和第三个会进入队列
第四个进来就会去开启最大线程数了所以就会直接拿到线程运行
这时候第五个任务进来由于核心线程、队列、最大线程都是满载状态
就会触发拒绝策略弹出队列里面最早的任务也就是任务二加入任务五
任务六进来也是一样弹走最早的任务三加入任务六.
2.AbortPolicy核心线程队列和最大线程都满载就直接抛出异常拒绝任务
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(1,2,30,
TimeUnit.SECONDS,new LinkedBlockingQueue<>(2),new ThreadPoolExecutor.AbortPolicy());
try{
for (int i = 1; i < 7; i++) {
System.out.println("添加任务:"+i);
executor.execute(new MyThread("线程"+i));
}
}catch (Exception e){
e.printStackTrace();
}
Iterator<Runnable> iterator = executor.getQueue().iterator();
while(iterator.hasNext()){
MyThread next =(MyThread) iterator.next();
System.out.println("当前队列还有任务"+next.threadName);
}
}
结果:
添加任务:1
添加任务:2
添加任务:3
添加任务:4
当前线程线程1正在运行
添加任务:5
当前线程线程4正在运行
java.util.concurrent.RejectedExecutionException:
Task com.qmpay.threadPool.ThreadPoolTest$MyThread@65b54208
rejected from java.util.concurrent.ThreadPoolExecutor@1be6f5c3
[Running, pool size = 2, active threads = 2, queued tasks = 2, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
at com.qmpay.threadPool.ThreadPoolTest.main(ThreadPoolTest.java:17)
当前队列还有任务线程2
当前队列还有任务线程3
当前线程线程2正在运行
当前线程线程3正在运行
解释:任务进来先进核心线程,之后进队列,最后是创建到最大的线程(最大线程数量-核心线程) = 1 + 2 + (2-1) = 4
也就是任务一(核心线程执行)、二和三(进队列)、四(创建出来的新线程执行) 任务五和六抛错拒绝执行
3.DiscardPolicy跟AbortPolicy区别就是不会抛错
4.CallerRunsPolicy如果遇到需要拒绝的任务就抛给主线程执行
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(1,2,30,
TimeUnit.SECONDS,new LinkedBlockingQueue<>(2),new ThreadPoolExecutor.CallerRunsPolicy());
try{
for (int i = 1; i < 7; i++) {
System.out.println("添加任务:"+i);
executor.execute(new MyThread("线程"+i));
}
}catch (Exception e){
e.printStackTrace();
}
Iterator<Runnable> iterator = executor.getQueue().iterator();
while(iterator.hasNext()){
MyThread next =(MyThread) iterator.next();
System.out.println("当前队列还有任务"+next.threadName);
}
}
结果:
添加任务:1
添加任务:2
添加任务:3
添加任务:4
添加任务:5
当前线程线程1正在运行
当前线程线程5正在运行
当前线程线程4正在运行
添加任务:6
当前线程线程2正在运行
当前线程线程3正在运行
当前队列还有任务线程6
当前线程线程6正在运行
解释:
任务:一、二、三、四不用解释跟之前一样,任务五进入拒绝策略里面抛给主线程执行
所以任务一 四 五会直接运行,但是这个时候任务五是主线程运行的会发生主线程阻塞
等到主线程运行结束才会继续原代码的执行上述添加任务6才会运行