文章目录
1.线程池
说到线程的连接释放, 与数据库进行连接时,就是需要先创建连接,使用完后,进行销毁;这样频繁地创建连接以及销毁连接是比较耗费时间的; 后来学了数据库连接池, 比如说德鲁伊数据库连接池,进行相关配置后,指定数据库连接池的链接数量,最小连接数,最大连接数等参数; 数据库连接时从连接池取到即可,用完后不用销毁,放回连接池即可;
线程池也就是这样;在并发量过大的情况下,使用线程池无疑是一种最好的选择;
java.uitl.concurrent.ThreadPoolExecutor 类
是线程池的核心类
线程池构造函数中的7个参数
corePoolSize:表示创建的核心线程池数量
, 其实在创建线程池后核心线程池数量默认为0
, 发现要执行的任务后,才会去创建线程去执行,或者调用prestartAllCoreThreads()或者 prestartCoreThread()方法
,进行预创建.
maximumPoolSize:表示在线程池中最多能创建多少个线程
keepAliveTime:非核心线程池中的线程,在没有执行任务时的存活时间
.
unit:参数 keepAliveTime 的时间单位
,
在 TimeUnit 类中有这几种静态属性:
workQueue:作为阻塞队列,用来存储等待执行的任务
threadFactory:线程工厂,用来创建线程;
handler:设置拒绝处理任务时的策略.
线程池的执行过程
创建 ThreadPoolExecutor 线程池后,当向线程池提交任务时,常用的是 execute 方法。
execute 方法的执行图:
-
如果线程池中在
corePoolSize
中存活的核心线程数小于线程数时,线程池会创建一个核心线程去执行处理提交的任务。 -
但是如果线程池核心线程数
corePoolSize
已满了,那么来了一个新提交的任务,就会被放进任务队列workQueue
中排队等待执行。 -
当线程池里面存活的线程数已经等于 corePoolSize 了,且
任务队列workQueue 也满
,这时去判断线程数是否达到最大线程容纳数maximumPoolSize
,如果没到达,可以考虑创建一个非核心线程执行提交的任务。 -
如果当前的线程总数达到了最大线程容纳数
maximumPoolSize
,要是还来新的任务,就得采用拒绝策略处理。
线程的执行工作队列
-
ArrayBlockingQueue
:数组实现的有界阻塞队列
,按 FIFO的排序量。 -
LinkedBlockingQueue
:基于链表结构的阻塞队列
,按 FIFO排序任务,
它的容量可以进行设置,不设置的话,默认是无边界的阻塞队列
,
最大长度为Integer.MAX_VALUE
,吞吐量通常要高于ArrayBlockingQuene
;
execute 与 submit 的区别
其实区别就是submit
执行时具有返回值
execute 适用于不需要返回值的场景,submit 方法适用于需要返回值
的场景。
可调用get方法获取返回值结果
四个类型拒绝策略
参数RejectedExecutionHandler
用于指定线程池的拒绝策略。
-
AbortPolicy 策略:直接抛出异常,阻止系统正常工作。
-
DiscardOleddestPolicy 策略:丢弃最老的一个请求(即将被执行的任务),并尝试再次提交当前任务。
-
DiscardPolicy 策略:丢弃无法处理的任务,不予任何处理.
-
CallerRunsPolicy 策略:只要线程池还没有关闭,就会在调用者线程中运行当前的任务(
如果任务被拒绝了,则由提交任务的线程(例如:main)直接执行此任务
)。
关闭线程池方法 shutdownNow 和 shutdown
- shutdownNow() 方法 :立即关闭线程池,正在执行的任务执行
interrupt()方法
,停止执行,还没开始执行的任务被全部取消,返回还没开始的任务列表。 - shutdown( ) 方法 : 正常关闭线程池,注意它会等待已经提交或执行的任务执行完成; 且线程池不再接受新的任务
案例
/*
执行的任务
*/
public class MyTask implements Runnable {
private int taskNum;
public MyTask(int num) {
this.taskNum = num;
}
@Override
public void run() {
try {
Thread.currentThread().sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":任务 "+taskNum+"执行完毕");
}
}
测试执行
public class Test {
public static void main(String[] args) {
//创建线程池, 核心线程池容量为 3;最大线程数为 8;
ThreadPoolExecutor executor = new ThreadPoolExecutor(
3, 8, 200,
TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(2),
Executors.defaultThreadFactory(),
//采用丢弃最老请求策略;
new ThreadPoolExecutor.DiscardOldestPolicy());
for (int i = 1; i <= 10; i++) {
MyTask myTask = new MyTask(i);
executor.execute(myTask);//添加任务到线程池
}
executor.shutdown();
}
}
执行结果之1
pool-1-thread-1:任务 1执行完毕
pool-1-thread-2:任务 2执行完毕
pool-1-thread-3:任务 3执行完毕
pool-1-thread-4:任务 6执行完毕
pool-1-thread-5:任务 7执行完毕
pool-1-thread-6:任务 8执行完毕
pool-1-thread-7:任务 9执行完毕
pool-1-thread-8:任务 10执行完毕
pool-1-thread-2:任务 5执行完毕
pool-1-thread-1:任务 4执行完毕
2.ThreadLocal(线程变量)
关于线程封闭
对象封闭在一个线程里,即使这个对象不是线程安全的,也不会出现并发安全问题。
例如 栈封闭:就是用栈(stack)来保证线程安全
public void testThread() {
StringBuilder s = new StringBuilder();
s.append("Hello");
}
- 线程封闭的指导思想是封闭,而不是共享。
- ThreadLocal 是用来解决变量共享的并发安全问题
初探执行原理
ThreadLocal 为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
达到线程隔离的效果;互相执行各自的操作,互不影响;
其中维护了一个ThreadLocalMap
集合;
ThreadLocalMap
作为ThreadLocal
的内部类
其中使用了set() 方法,get() 方法其实就是调用了ThreadLocalMap
中的方法;
比如说这个set方法;
-
调用set方法时, 先获取正在执行的线程对象, 为当前线程创建
ThreadLocalMap对象
-
ThreadLocalMap的键
就是ThreadLocal对象 , 值就是输入的值;
若当前线程中,没有Map;就去创建map,注意这时的key其实还是当前的ThreadLocal对象
然后看看get方法;
先根据自己线程去找ThreadLocal对象
维护的的ThreadLocalMap
集合
ThreadLocal带来的内存泄漏问题
注意:要是线程还不结束;那么这些key 为 null 的 Entry 的强引用 value
就会一直存在一条强引用链:
Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
那么就会产生内存泄漏
现在分析一下TreadLocalMap
中 的Key使用强引用 / 弱引用的情况;
-
当 ThreadLocalMap 的 key 为 强 引 用的ThreadLocal 时
, 由于ThreadLocalMap 还 持 有 ThreadLocal 的 强 引 用 , 若不使用 手 动 删 除 ,ThreadLocal 不会被回收,还是会导致 Entry 内存泄漏。 -
当 ThreadLocalMap 的 key 为 弱 引 用 的ThreadLocal 时
, 由 于ThreadLocalMap 持 有 ThreadLocal 的 弱 引 用 ,即 使 没 有 手 动 删 除 ,ThreadLocal 也会被回收
。当 key 为 null 后,在下一次ThreadLocalMap 调用set(),get(),remove()方法的时候
就会清除 value 值。
建议 每次使用完 ThreadLocal 都调用它的 remove()方法清除数据 ,防止出现内存泄漏
案例
public class ThreadLocalDemo {
//创建一个ThreadLocal对象,用来为每个线程会复制保存一份变量,实现线程封闭
private static ThreadLocal<Integer> localNum = new ThreadLocal<Integer>() {
//初始化 ThreadLocal中的默认值
@Override
protected Integer initialValue() {
return 0;
}
};
public static void main(String[] args) {
new Thread() {
@Override
public void run() {
//设置副本的数值;
localNum.set(1);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
localNum.set(localNum.get() + 10);
System.out.println(Thread.currentThread().getName() + ":" + localNum.get());//11
//当线程中不再使用线程变量时,将变量值清除
localNum.remove();
}
}.start();
new Thread() {
@Override
public void run() {
//设置副本的数值;
localNum.set(3);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
localNum.set(localNum.get() + 20);
System.out.println(Thread.currentThread().getName() + ":" + localNum.get());//23
//当线程中不再使用线程变量时,将变量值清除
localNum.remove();
}
}.start();
//主线程的操作数,用的是默认的0;
System.out.println(Thread.currentThread().getName() + ":" + localNum.get());//0
}
}