线程安全问题
- 一、对线程安全的理解(实际上是内存安全)
- 二、Thread类的继承、Runable接口的重写
- 三、守护线程
- 四、ThreadLocal原理和使用场景
- 五、sleep、wait、join、yield
- 六、线程池、解释线程池参数
一、对线程安全的理解(实际上是内存安全)
- 堆是共享内存,可以被所有线程访问
- 当多个线程访问一个对象时,如果不用进行额外的同步控制或其他的协调操作,调用这个对象的行为都可以获得正确的结果,我们就说这个对象是线程安全的。
- 堆是进程共有的空间,也是线程的空间,分为全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是用完了要还给操作系统,要不然就是内存泄露。
- 注意:局部堆和全局堆都是可以共享的
- 栈是线程安全的
- 栈是每个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈相互独立。因此,栈是线程安全的。操作系统在切换线程的时候会自动切换栈。
- 线程安全问题
在每个进程的内存空间中都会有一块特殊的公共区域。通常称为堆(内存)。进程内的所有线程都可以访问到该区域,这就是造成线程安全问题的潜在原因
二、Thread类的继承、Runable接口的重写
- Thread类实例(未同步)
class Store1{ //domain
private int seq;
public int get(){return seq;}
public void put(int value){seq = value}
}
class Producer extends Thread{ //生产者Producer线程
private Store1 store;
private int num;
public Producer(Store1 s,int num){ //构造方法
store = s;
this.num = num;
}
public void run(){ //线程体
for(int i = 0; i<10; i++){
store.put(i);
System.out.println("Producer #" + this.num + "put:" + i); //显示放数i
try{
sleep((int)(Math.random().100));
}catch(InterruptedException e){}
}
}
}
class Consumer extends Thread{ //消费者Consumer线程
private Store1 store;
private int num;
public Consumer(Store1 s,int num){ //构造方法
store = s;
this.num = num;
}
public void run(){ //线程体
int value = 0;
for(int i = 0; i<10; i++){
value = store.get();
System.out.println("Consumer #" + this.num + "got:" + value); //从store对象取值
try{
sleep((int)(Math.random().100));
}catch(InterruptedException e){}
}
}
}
public class MyProCon{
public static void main(String[] args){
Store1 s = new Store1();
Producer p1 = new Producer(s,1);
Consumer c1 = new Consumer(s,1);
p1.start();
c1.start();
}
}
- synchronized关键字修饰方法
- 标有synchronized的方法称为不同方法,当某线程访问某一资源时,被同步的方法不能同时使用这一资源。(实际上就是加了synchronized的方法,在执行时需要执行完整个方法)
- 注意:这些方法能实现同步必须是同一实例对象的方法才能实现同步,因为一个对象一把锁,通过3.4.实例来感受一下
- Thread类(同步的典型错误案例)
public class VolatileThread extends Thread {
public synchronized void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName());
}
}
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
VolatileThread vt = new VolatileThread();
//设置线程的名称,看在执行哪个对象的run()
vt.setName(i + "");
vt.start();
}
}
}
分析
在方法上加synchronized等同于synchronized(this),虽然看似给run()方法加上了锁,但是我们看main()中是如何去产生多个线程的,是分别new出了三个不同的线程对象。也就是说三个线程都拿到各自对象的锁,因此都能够执行run()中的代码。
- Thread类(实现同步)
class Store2{ //domain
private int seq;
private boolean available = false; //Producer与Consumer共用
public synchronized int get(){
while(available == false){
try{
wait();
}catch (InterruptedException e)
}
available = false;
notify();
return seq;
}
public synchronized void put(int value){
while(available == true){
try{
wait();
}catch (InterruptedException e)
}
seq = value;
available = true;
notify();
}
}
class Producer extends Thread{ //生产者Producer线程
private Store2 store;
private int num;
public Producer(Store1 s,int num){ //构造方法
store = s;
this.num = num;
}
public void run(){ //线程体
for(int i = 0; i<10; i++){
store.put(i);
System.out.println("Producer #" + this.num + "put:" + i); //显示放数i
try{
sleep((int)(Math.random().100));
}catch(InterruptedException e){}
}
}
}
class Consumer extends Thread{ //消费者Consumer线程
private Store2 store;
private int num;
public Consumer(Store1 s,int num){ //构造方法
store = s;
this.num = num;
}
public void run(){ //线程体
int value = 0;
for(int i = 0; i<10; i++){
value = store.get();
System.out.println("Consumer #" + this.num + "got:" + value); //从store对象取值
try{
sleep((int)(Math.random().100));
}catch(InterruptedException e){}
}
}
}
public class MyProCon{
public static void main(String[] args){
Store1 s = new Store1();
Producer p1 = new Producer(s,1);
Consumer c1 = new Consumer(s,1);
p1.start();
c1.start();
}
}
- Runnable接口(实现同步)
public class RunnableThread implements Runnable{
@Override
public void run() {
for(int i = 0;i < 100;i++){
System.out.println(Thread.currentThread().getName() + "----" +i);
}
}
}
public class RunnableThreadTest {
public static void main(String[] args) {
//1. 通过继承 Runnable 创建线程
Runnable r = new RunnableThread();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r,"线程2");
//开始执行
t1.start();
t2.start();
//2. 使用内部类创建线程
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0;i < 100;i++){
System.out.println(Thread.currentThread().getName() + "----" +i);
}
}
},"线程3");
t3.start();
//3. 使用匿名内部类创建线程
new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0;i < 100;i++){
System.out.println(Thread.currentThread().getName() + "----" +i);
}
}
},"线程4").start();
}
}
- Thread与Runnable的相关问题
- 两者使用场景:多线程编程主要就是为线程编写run()方法。如何选用这两种方法?其规则是:如果编写的类必须从其他类中导出,则选出第二种方法实现多线程。因为Java不支持多重继承,继承了其他类后不能再继承Thread类,只能利用Runnable接口。
- 两者线程同步的区别:正如上述两个例子,Runnable可以实现多个相同的程序代码的线程去共享同一个资源,而Thread并不是不可以,而是相比于Runnable来说,不太合适
- Runnable不可以直接run:多线程原理:相当于玩游戏机,只有一个游戏机(cpu),可是有很多人要玩,于是,start是排队!等CPU选中你就是轮到你,你就run(),当CPU的运行的时间片执行完,这个线程就继续排队,等待下一次的run()。调用start()后,线程会被放到等待队列,等待CPU调度,并不一定要马上开始执行,只是将这个线程置于可动行状态。然后通过JVM,线程Thread会调用run()方法,执行本线程的线程体。先调用start后调用run,这么麻烦,为了不直接调用run?就是为了实现多线程的优点,没这个start不行。
- Thread实现Runnable源码
public class Thread implements Runnable {
/** 这里只看一些 常见的参数 */
/** 线程名 */
private volatile char name[];
/** 优先级 */
private int priority;
/** 是否为守护线程 */
private boolean daemon;
/** 线程要执行的目标任务 */
private Runnable target;
/** 所属线程组 */
private ThreadGroup group;
/** 类加载器 */
private ClassLoader contextClassLoader;
/**
* ThreadLocal 能为线程设置线程私有变量 就是通过下面这个threadLocals变量完成的,
* ThreadLocal的get/set方法就是通过操作 各个线程的 threadLocals 变量实现的。
* 1、线程A持有一个 ThreadLocalMap 变量;
* 2、线程A调用一个类的 ThreadLocal变量 tlA 的 get/set方法;
* 3、tlA(ThreadLocal)的 get/set方法 获取当前线程A,调用 线程A 的 ThreadLocalMap变量 的get/put方法;
* 4、其它线程 调用 tlA(ThreadLocal)的 get/set方法 同理。
*/
ThreadLocal.ThreadLocalMap threadLocals;
ThreadLocal.ThreadLocalMap inheritableThreadLocals;
/** 线程栈的大小 */
private long stackSize;
/**
* Thread类定义了6个线程状态:New、Runnable、Blocked、Waiting、TimedWaiting、Terminated(终止)
* 实际上还会把 Runnable 再细分为 就绪(未抢到时间片) 和 运行中(抢到时间片)
*/
private volatile int threadStatus;
/** 最小优先级 */
public static final int MIN_PRIORITY = 1;
/** 中等优先级 */
public static final int NORM_PRIORITY = 5;
/** 最大优先级 */
public static final int MAX_PRIORITY = 10;
/**
* 内部枚举类,用来描述线程状态,状态值有:
* NEW: 新建,还未调用start()方法;
* RUNNABLE: 运行,在java多线程模型中,就绪和运行都是运行状态;
* BLOCKED: 阻塞;
* WAITING: 等待,需要其他的线程来唤醒;
* TIMED_WAITING:超时等待,可以在指定的时间内自动醒来,如 sleep()方法;
* TERMINATED: 终止,线程执行完毕。
*/
public static final class State extends Enum {
public static final State NEW;
public static final State RUNNABLE;
public static final State BLOCKED;
public static final State WAITING;
public static final State TIMED_WAITING;
public static final State TERMINATED;
private static final State VALUES[];
static {
NEW = new State("NEW", 0);
RUNNABLE = new State("RUNNABLE", 1);
BLOCKED = new State("BLOCKED", 2);
WAITING = new State("WAITING", 3);
TIMED_WAITING = new State("TIMED_WAITING", 4);
TERMINATED = new State("TERMINATED", 5);
VALUES = (new State[] { NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED });
}
private State(String s, int i) {
super(s, i);
}
}
/**
* 一系列 构造方法 ------------------------------------------------------
* 可以看出来,其中都调用了init()方法,这也是一个约定俗成的规矩, 即,如果要在 new 时进行一些初始化操作,
* 那么请将初始化操作单独写在 init()方法中,然后在构造函数中调用该 init()方法
*/
public Thread() {
daemon = false;
stillborn = false;
threadLocals = null;
inheritableThreadLocals = null;
threadStatus = 0;
blockerLock = new Object();
init(null, null, (new StringBuilder()).append("Thread-").append(nextThreadNum()).toString(), 0L);
}
public Thread(Runnable runnable) {
daemon = false;
stillborn = false;
threadLocals = null;
inheritableThreadLocals = null;
threadStatus = 0;
blockerLock = new Object();
init(null, runnable, (new StringBuilder()).append("Thread-").append(nextThreadNum()).toString(), 0L);
}
Thread(Runnable runnable, AccessControlContext accesscontrolcontext) {
daemon = false;
stillborn = false;
threadLocals = null;
inheritableThreadLocals = null;
threadStatus = 0;
blockerLock = new Object();
init(null, runnable, (new StringBuilder()).append("Thread-").append(nextThreadNum()).toString(), 0L,
accesscontrolcontext);
}
public Thread(ThreadGroup threadgroup, Runnable runnable) {
daemon = false;
stillborn = false;
threadLocals = null;
inheritableThreadLocals = null;
threadStatus = 0;
blockerLock = new Object();
init(threadgroup, runnable, (new StringBuilder()).append("Thread-").append(nextThreadNum()).toString(), 0L);
}
public Thread(String s) {
daemon = false;
stillborn = false;
threadLocals = null;
inheritableThreadLocals = null;
threadStatus = 0;
blockerLock = new Object();
init(null, null, s, 0L);
}
public Thread(ThreadGroup threadgroup, String s) {
daemon = false;
stillborn = false;
threadLocals = null;
inheritableThreadLocals = null;
threadStatus = 0;
blockerLock = new Object();
init(threadgroup, null, s, 0L);
}
public Thread(Runnable runnable, String s) {
daemon = false;
stillborn = false;
threadLocals = null;
inheritableThreadLocals = null;
threadStatus = 0;
blockerLock = new Object();
init(null, runnable, s, 0L);
}
public Thread(ThreadGroup threadgroup, Runnable runnable, String s) {
daemon = false;
stillborn = false;
threadLocals = null;
inheritableThreadLocals = null;
threadStatus = 0;
blockerLock = new Object();
init(threadgroup, runnable, s, 0L);
}
public Thread(ThreadGroup threadgroup, Runnable runnable, String s, long l) {
daemon = false;
stillborn = false;
threadLocals = null;
inheritableThreadLocals = null;
threadStatus = 0;
blockerLock = new Object();
init(threadgroup, runnable, s, l);
}
private void init(ThreadGroup threadgroup, Runnable runnable, String s, long l) {
init(threadgroup, runnable, s, l, null);
}
/**
* 初始化线程
*/
private void init(ThreadGroup threadgroup, Runnable runnable, String name, long l,
AccessControlContext accesscontrolcontext) {
// 参数校验,线程name不能为null
if (name == null)
throw new NullPointerException("name cannot be null");
this.name = name.toCharArray();
// 当前线程就是该线程的父线程
Thread parent = currentThread();
SecurityManager securitymanager = System.getSecurityManager();
if (threadgroup == null) {
if (securitymanager != null)
threadgroup = securitymanager.getThreadGroup();
if (threadgroup == null)
threadgroup = parent.getThreadGroup();
}
threadgroup.checkAccess();
if (securitymanager != null && isCCLOverridden(getClass()))
securitymanager.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
threadgroup.addUnstarted();
// 守护线程、优先级等设置为父线程的对应属性
group = threadgroup;
daemon = parent.isDaemon();
priority = parent.getPriority();
if (securitymanager == null || isCCLOverridden(parent.getClass()))
contextClassLoader = parent.getContextClassLoader();
else
contextClassLoader = parent.contextClassLoader;
inheritedAccessControlContext = accesscontrolcontext == null ? AccessController.getContext()
: accesscontrolcontext;
target = runnable;
setPriority(priority);
if (parent.inheritableThreadLocals != null)
// 创建线程共享变量副本
inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
stackSize = l;
// 分配线程id
tid = nextThreadID();
}
public synchronized void start() {
//假若当前线程初始化还未做好,不能start,0->NEW状态
if (threadStatus != 0)
throw new IllegalThreadStateException();
//通知group该线程即将启动,group的未启动线程数量减1
group.add(this);
boolean started = false;
try {
// 调用native的start0()方法 启动线程,启动后执行run()方法
start0();
started = true;
} finally {
try {
//启动不成功,group设置当前线程启动失败
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
private native void start0();
public void run() {
if (target != null)
target.run();
}
/**
* 请求终止线程。interrupt不会真正停止一个线程,它仅仅是给这个线程发了一个信号,
* 告诉它要结束了,具体要中断还是继续运行,将由被通知的线程自己处理
*/
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0();
b.interrupt(this);
return;
}
}
interrupt0();
}
private native void interrupt0();
/**
* 线程main 调用了线程A的join方法,则 线程main 会被阻塞,直到线程A执行完毕
*/
public final void join() throws InterruptedException {
join(0);
}
/**
* 实际上是利用 wait/notify机制 来实现的
*/
public final synchronized void join(long millis) throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
// millis 为 0,所以走这个分支
if (millis == 0) {
// 当前线程是否还在运行,还在运行 则main线程 进入等待状态,直到 A线程运行完毕,将其唤醒
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
/**
* 线程睡眠指定的时间,释放CPU资源,但不释放锁
*/
public static native void sleep(long millis) throws InterruptedException;
/**
* 线程是否还在运行
*/
public final native boolean isAlive();
}
三、守护线程
- 守护线程的概念
- 守护线程为所有非守护线程提供服务的线程,任何一个守护线程都是整个JVM中所有非守护线程的保姆
- 守护线程类似于整个进程的一个默默无闻的小喽喽,它的生死无关重要,它却依赖整个进程而运行,哪天其他线程结束了,没有要执行的了,程序就结束了,理都没理守护线程,就把它中断
- 注意:由于守护线程的终止是自身无法控制的,因此千万不要把IO、File等重要操作逻辑分配给它,因为它不靠谱
- 守护线程的作用(举例)
- GC垃圾回收线程:就是一个经典的守护线程,当我们的程序中不再有任何运行的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是JVM上仅剩的线程时,垃圾回收线程会自动离开。它始终再低级别的状态中运行,用于实时监控和管理系统中的可回收资源
- 守护线程设置
- 设置守护线程:thread.setDaemon(true)
- 设置条件:thread.setDaemon(true)必须在thread.start()之前设置,否则会抛出一个illegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
- 注意:在Daemon线程中产生的新线程也是Daemon线程
四、ThreadLocal原理和使用场景
- ThreadLocal背景
- 每个线程都会有属于自己的本地内存,在堆中的变量在被线程使用的时候会被复制一个副本线程的本地内存中,当线程修改了共享变量之后就会通过JMM管理控制写会到主内存中。
- 很明显,在多线程的场景下,当有多个线程对共享变量进行修改的时候,就会出现线程安全问题,即数据不一致问题。常用的解决方法是对访问共享变量的代码加锁(synchronized或者Lock)。但是这种方式对性能的耗费比较大。在JDK1.2中引入了ThreadLocal类,来修饰共享变量,使每个线程都单独拥有一份共享变量,这样就可以做到线程之间对于共享变量的隔离问题。
- ThreadLocal与Synchronized的区别
- ThreadLocal的使用
- 一般都会将ThreadLocal声明成一个静态字段
- set(T value):设置线程本地变量的内容。
- get():获取线程本地变量的内容。
- remove():移除线程本地变量。
- 注意:在线程执行完毕时一定要调用remove,避免在线程被重新放入线程池中时,本地变量的旧状态仍然被保存。
public class Test {
static ThreadLocal<User> threadLocal = new ThreadLocal<>();
public void m1(User user) {threadLocal.set(user);}
public void m2() {
User user = threadLocal.get(); //获取user变量的副本
/*
使用
*/
threadLocal.remove(); // 使用完清除
}
}
- 举例:未使用ThreadLocal和使用ThreadLocal
public class MyDemo01{
private String content;
private String getContent(){ return content;}
private void setContent(String content){this.content = content;}
public static void main(String[] args){
MyDemo01 demo = new MyDemo01();
for(int i = 0; i<5; i++){
Thread thread = new Thread(new Runnable(){
@Override
public void run(){
//每一个线程存一个变量,过一会儿再来取出这个变量
demo.setContent(Thread.currentThread().getName() + "的数据");
System.out.println("------------------");
System.out.println(Thread.currentThread().getName() + "--->" + demo.getContent());
}
});
}
}
}
public class MyDemo01{
ThreadLocal<String> t1 = new ThreadLocal<>();
private String content;
private String getContent(){
return t1.get();
}
private void setContent(String content){
t1.set(content);
}
public static void main(String[] args){
MyDemo01 demo = new MyDemo01();
for(int i = 0; i<5; i++){
Thread thread = new Thread(new Runnable(){
@Override
public void run(){
//每一个线程存一个变量,过一会儿再来取出这个变量
demo.setContent(Thread.currentThread().getName() + "的数据");
System.out.println("------------------");
System.out.println(Thread.currentThread().getName() + "--->" + demo.getContent());
}
});
}
}
}
- 多个ThreadLocal怎么实现
- 使用ThreadLocal作为key:由于每一个ThreadLocal对象都可以由threadLocalHashCode属性唯一区分或者说每一个ThreadLocal对象都可以由这个对象的名字唯一区分(下面的例子),所以可以用不同的ThreadLocal作为key,区分不同的value,方便存取。
public class Son implements Cloneable{
public static void main(String[] args){
Thread t = new Thread(new Runnable(){
public void run(){
ThreadLocal<Son> threadLocal1 = new ThreadLocal<>();
threadLocal1.set(new Son());
System.out.println(threadLocal1.get());
ThreadLocal<Son> threadLocal2 = new ThreadLocal<>();
threadLocal2.set(new Son());
System.out.println(threadLocal2.get());
}});
t.start();
}
}
- Thread、ThreadLocal、ThreadLocalMap、Entry的原理
- 一个线程内部都有一个ThreadLocalMap
- 一个ThreadLocalMap里面存储多个Entry
- 一个Entry存储一个键值对,即线程本地对象ThreadLocal(key)和线程的变量副本(value)
- Thread内部的Map是由ThreadLocal维护,如下:
- ThreadLocal为什么是弱引用
- 首先了解什么是内存泄漏:不再会被使用的对象或者变量占用的呢村不能被回收,就是内存泄漏
下图:实现是强引用,虚线是弱引用
- key使用强引用:当ThreadLocalMap的key使用强引用时,无法回收堆中的ThreadLocal。因为ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除ThreadLocalMap强引用的key,那么堆中的ThreadLocal不会被回收,导致Entry内存泄漏
- key使用弱引用:当ThreadLocalMap的key使用强引用时,可以回收堆中的ThreadLocal。由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。当key为null时,在一下次ThreadLocalMap调用set()、get()、remove()方法的时候会被清楚value值的。
五、sleep、wait、join、yield
- 等待池和锁池
- 等待池:当我们调用wait方法后,线程会放到等待池当中,等待池的线程是不会去竞争同步锁。只有调用了notify或notifyAll后等待池的线程才会开始去竞争锁,notify是随机从等待池选出一个线程放到锁池,而notifyAll是将等待池的所有线程放到锁池当中
- 锁池:所有需要竞争同步锁的线程都会放在锁池当中,比如当前对象的锁已经被其中一个线程得到,则其他线程需要在这个锁池进行等待,当前面的线程释放同步锁后锁池中的线程去竞争同步锁,当某个线程得到后会进入就绪队列进行等待CPU资源分配
- sleep与wait
- sleep方法不依赖同步器synchronized,但是wait需要依赖synchronized关键字
- sleep不需要被唤醒(休眠之后推出阻塞),但是wait需要(不指定时间需要被别人中断)
- sleep一般用于当前线程休眠,或者轮询暂停操作,wait则多用于多线程之间的通信
- sleep会让出CPU执行时间且强制上下文切换,而wait则不一定,wait后可能还是有机会重新竞争到锁继续执行的。
- yield()和join()
- yield()执行后线程直接进入就绪状态,马上释放了CPU的执行权,但是仍然参与CPU调度,所以有可能CPU下次进行线程调度还会让这个线程获取到执行权继续执行。
- join()执行后线程进入阻塞状态,例如在线程B中调用线程A的join(),那线程B会进入到阻塞队列,直到线程A结束或中断线程
public static void main(String[] args) throws InterruptedException{
Thread t1 = new Thread(new Runnable(){
@Override
public void run(){
try{
Thread.sleep(3000);
}catch(InterruptedException)
e.printStackTrace();
}
System.out.println("222222222");
}
});
t1.start();
t1.join();
//这行代码必须要等t1全部执行完毕,才会执行
System.out.println("1111");
}
222222222
1111
六、线程池、解释线程池参数
- 为什么使用线程池
- 降低资源消耗:提高线程利用率,降低创建和销毁线程的消耗。(因为线程的创建和消耗都是比较耗费资源的,所以使用线程池,将创建的线程保存在线程池中,需要使用的时候拿出来,不需要反复创建和销毁)
- 提高响应速度:任务来了,直接从线程池中获取,而不是创建线程再执行
- 提高线程可管理性:线程是稀缺资源(个数有限),使用线程池也可以统一分配调优监控
- 线程池的参数
- corePoolSize:代表核心线程数,也就是正常情况下创建工作的线程数,这些线程创建后并不会消除,而是一种常驻线程。
- workQueue:用来存放待执行的任务,假设我们现在核心线程都已被使用,还有任务进行则全部放入队列,直到整个队列被放满但任务还再持续进入则开始创建新的线程。
- maxinumPoolSize:代表最大线程数,它与核心线程数相对应,表示最大允许被创建的线程数,比如当前任务较多,将核心线程数都用完了,将workQueue都占满了,还无法满足需求时,此时就会创建新的线程,但是线程池内总数不会超过最大线程总数。
- keepAliveTime、unit:表示超过核心线程数之外的线程的空闲存活时间,也就是核心线程不会消除,但是超过核心线程数的部分线程如果空闲一定的时间则会被消除,我们可以通过setKeepAliveTime来设置空闲时间。
- Handler:任务拒绝策略,有两种情况,第一种是当我们调用shutdown等方法关闭线程池后,这时候即使线程池内部还没执行完正在执行,但是由于线程池已经关闭,我们再继续想线程池提交任务就会遭到拒绝。第二种情况就是当达到最大线程数,线程池已经没有能力继续处理提交的任务时,这时候也会拒绝
- ThreadFactory:线程工厂,用来生产线程执行任务。我们可以选择使用默认的创建工厂,产生的线程都在同一个组内,拥有相同的优先级,且都不是守护线程。当然我们也可以选择自定义线程工厂,一般我们会根据业务来制定不同的线程工厂
举个栗子:设corePoolSize=5,workQueue.size=5,maxinumPoolSize = 10
- 现在来第1、2、3、4、5个任务,corePoolSize足够,所以可以直接创建线程。
- 再来第6、7、8、9、10个任务,corePoolSize满了,所以后面来的任务都得在workQueue排队
- 再来第11、12、13、14、15个任务,corePoolSize和workQueue都满了,所以需要创建Queue里面排队的线程(FIFO)
- 后面再来任务,workQueue.size和maxinumPoolSize都满了,所以使用Handler拒绝
注意:最大线程数 = maxinumPoolSize.size;最大任务数 = maxinumPoolSize.size+ workQueue.size
- 线程池的处理流程(按照上面例子看就行了)
- 线程池中的阻塞队列
- 注意:阻塞队列与workQueue并不是同一个队列
- 一般的队列只能保证作为一个有限长度的缓冲区,如果超出了缓冲长度,就无法保留当前的任务了,阻塞队列通过阻塞可以保留住当前想要继续入队的任务
- 阻塞队列可以保证任务队列中没有任务时,阻塞常驻线程(corePoolSize创建的线程,它们会一直访问一个没有任务的workQueue,消耗CPU资源),使得线程进入wait状态,释放cpu资源
- 阻塞队列自带阻塞和唤醒的功能,不需要额外处理,无任务执行时,线程池利用阻塞队列的take方法挂起,从而维持核心线程的存储,不至于一直占用cpu资源
- 为什么是先添加队列而不是先创建最大线程?
- 在创建新线程的时候,是要获取全局锁的,这个时候其他的线程就得阻塞,影响了整体效率。
- 线程池中的线程复用原理
- 线程池将线程和任务进行解耦,线程是线程,任务是任务,摆脱了之前通过Thread创建线程时的一个线程必须对应的一个任务的限制。所以我可以认为线程池的本质就是线程复用。
- 在线程池中,同一个线程可以从阻塞队列中不断获取新任务来执行,其核心原理在于线程池对于Thread进行了封装,并不是每次执行任务都会调用Thread.start()来创建新线程,而是让每个线程去执行一个循环任务,在这个循环任务中不停检查是否有任务需要被执行,如果有则直接执行,也就是调用任务中的run方法,将run方法当成一个普通的方法执行,通过这种方式只使用固定的线程就将所有任务的run方法串联起来。
- 核心:start()方法是创建新线程来执行run()任务,线程池就是不通过start()方法创建新线程,而是用旧现成的run()方法来执行任务。