多线程解析

1.概念

1.1. 回顾线程创建方式

  • 继承Thread
  • 实现Runnable

1.2. 线程的状态

在这里插入图片描述

  • New:刚刚创建,没做任何操作
Thread thread = new Thread(); 
System.out.println(thread.getState());
  • RUNNABLE:调用run,可以执行,但不代表一定在执行(RUNNING,READY)
thread.start();
System.out.println(thread.getState());
  • BLOCKED:抢不到锁
final byte[] lock = new byte[0]; 
new Thread(new Runnable() { 
	public void run() { 
		synchronized (lock){ 
			try {
				Thread.sleep(3000); 
			} catch (InterruptedException e) {
				e.printStackTrace(); 
			}
		} 
	}
}).start(); 
Thread thread2 = new Thread(new Runnable() { 
	public void run() { 
		synchronized (lock){ 
		}
	} 
});
thread2.start(); 
Thread.sleep(1000); 
System.out.println(thread2.getState());
  • WAITING
Thread thread2 = new Thread(new Runnable() {
	public void run() { 
		LockSupport.park();
	} 
}); 
thread2.start(); 
Thread.sleep(500); 
System.out.println(thread2.getState()); 
LockSupport.unpark(thread2); 
Thread.sleep(500); 
System.out.println(thread2.getState());
  • TIMED_WAITING
Thread thread3 = new Thread(new Runnable() {
    public void run() {
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
});
thread3.start();
Thread.sleep(500);
System.out.println(thread3.getState());
  • TERMINATED
//等待1s后再来看 
Thread.sleep(1000); 
System.out.println(thread.getState());

1.3.线程池基本概念

根据上面的状态,普通线程执行完,就会进入TERMINATED销毁掉,而线程池就是创建一个缓冲池存放线程,执 行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等候下次任务来临,这使得线程池比手动 创建线程有着更多的优势:

  • 降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗
  • 提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行
  • 方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM
  • 节省cpu切换线程的时间成本(需要保持当前执行线程的现场,并恢复要执行线程的现场)
  • 提供更强大的功能,延时定时线程池。(Timer vs ScheduledThreadPoolExecutor)

1.4. 常用线程池类结构

可以通过idea查看到 (查看:ScheduledThreadPoolExecutor,ForkJoinPool类图)
在这里插入图片描述
说明:

  • 最常用的是ThreadPoolExecutor
  • 调度用ScheduledThreadPoolExecutor
  • 任务拆分合并用ForkJoinPool
  • Executors是工具类,协助你创建线程池的

2. 工作机制

在线程池的编程模式下,任务是提交给整个线程池,而不是直接提交给某个线程,线程池在拿到任务后,就在内部 协调空闲的线程,如果有,则将任务交给某个空闲的线程。一个线程同时只能执行一个任务,但可以同时向一个线 程池提交多个任务。

2.1. 线程池状态

在这里插入图片描述

  • RUNNING:初始化状态是RUNNING。线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务 数为0。RUNNING状态下,能够接收新任务,以及对已添加的任务进行处理。
  • SHUTDOWN:SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。调用线程池的shutdown()接口 时,线程池由RUNNING -> SHUTDOWN。
//shutdown后不接受新任务,但是task1,仍然可以执行完成 
ExecutorService poolExecutor = Executors.newFixedThreadPool(5);
poolExecutor.execute(new Runnable() {
    public void run() {
        try {
            Thread.sleep(1000);
            System.out.println("finish task 1");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
});
poolExecutor.shutdown();
poolExecutor.execute(new Runnable() {
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
});
System.out.println("ok");
  • STOP:不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。调用线程池的shutdownNow()接 口时,线程池由(RUNNING 或 SHUTDOWN ) -> STOP
    注意:运行中的任务还会打印,直到结束,因为调的
//改为shutdownNow后,任务立马终止,sleep被打断,新任务无法提交,task1停止 
poolExecutor.shutdownNow();
  • TIDYING:所有的任务已终止,队列中的”任务数量”为0,线程池会变为TIDYING。线程池变为TIDYING状态 时,会执行钩子函数terminated(),可以通过重载terminated()函数来实现自定义行为
//自定义类,重写terminated方法 
public class MyExecutorService extends ThreadPoolExecutor {
    public MyExecutorService(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    @Override
    protected void terminated() {
        super.terminated();
        System.out.println("treminated");
    }

    //调用 shutdownNow, ternimated方法被调用打印 
    public static void main(String[] args) throws InterruptedException {
        MyExecutorService service = new MyExecutorService(1, 2, 10000, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(5));
        service.shutdownNow();
    }
}
  • TERMINATED:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED

2.2. 结构说明

(源码查看:两个集合,一个queue,一个hashset)
在这里插入图片描述

2.3.任务的提交

  • 添加任务,如果线程池中线程数没达到coreSize,直接创建新线程执行
  • 达到core,放入queue
  • queue已满,未达到maxSize继续创建线程
  • 达到maxSize,根据reject策略处理
  • 超时后,线程被释放,下降到coreSize

3. 源码解析

//任务提交阶段:(4个if条件路线) 
    public void execute(Runnable command) {
        if (command == null) throw new NullPointerException();
        int c = ctl.get();
        //判断工作数,如果小于coreSize,addWork,注意第二个参数core=true 
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true)) return;
            c = ctl.get();
        }
        //否则,如果线程池还在运行,offer到队列 
        if (isRunning(c) && workQueue.offer(command)) {
            //再检查一下状态
            int recheck = ctl.get();
            //如果线程池已经终止,直接移除任务,不再响应 
            if (!isRunning(recheck) && remove(command)) reject(command);
                //否则,如果没有线程干活的话,创建一个空work,该work会从队列获取任务去执行 
            else if (workerCountOf(recheck) == 0) addWorker(null, false);
        }
        //队列也满,继续调addWork,但是注意,core=false,开启到maxSize的大门 
        // 超出max的话,addWork会返回false,进入reject 
        else if (!addWorker(command, false)) reject(command);
    }

    //线程创建
    private boolean addWorker(Runnable firstTask, boolean core) {
        //第一步,计数判断,不符合条件打回false
        retry:
        for (; ; ) {
            int c = ctl.get();
            int rs = runStateOf(c);
            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN && !(rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty())) return false;
            for (; ; ) {
                int wc = workerCountOf(c);
                //判断线程数,注意这里! 
                // 也就说明线程池的线程数是不可能设置任意大的。 
                // 最大29位(CAPACITY=29位二进制) 
                if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false;
                if (compareAndIncrementWorkerCount(c)) break retry;
                c = ctl.get();// Re‐read ctl
                if (runStateOf(c) != rs) continue retry;
                // else CAS failed due to workerCount change; retry inner loop 
            }
        }
        //第二步,创建新work放入线程集合works(一个HashSet) 
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            //符合条件,创建新的work并包装task 
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // Recheck while holding lock. 
                    // Back out on ThreadFactory failure or if 
                    // shut down before lock acquired. 
                    int rs = runStateOf(ctl.get());
                    if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive())// precheck that t is startable 
                            throw new IllegalThreadStateException();
                        //在这里!!!
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize) largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    //注意,只要是成功add了新的work,那么将该新work立即启动,任务得到执行 
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (!workerStarted) addWorkerFailed(w);
        }
        return workerStarted;
    }
    //任务获取与执行 
    // 在worker执行runWorker()的时候,不停循环,先查看自己有没有携带Task,如果有,执行 
    while(task!=null||(task=getTask())!=null)

    //如果没用,会调用getTask,从队列获取任务 
    private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?
        for (; ; ) {
            int c = ctl.get();
            int rs = runStateOf(c);
            // Check if queue empty only if necessary. 
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }
            int wc = workerCountOf(c);
            // Are workers subject to culling? 
            // 判断是不是要超时处理,重点!!!决定了当前线程要不要被释放 
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
            //线程数超出max,并且上次循环中poll等待超时了,那么说明该线程已终止
            // 将线程队列数量原子性减
            if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c)) return null;
                continue;
            }
            try {
                //重点!!! 
                // 如果线程可被释放,那就poll,释放的时间为:keepAliveTime 
                // 否则,线程是不会被释放的,take一直被阻塞在这里,知道来了新任务继续工作 
                Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();
                if (r != null) return r;
                //到这里说明可被释放的线程等待超时,已经销毁,设置该标记,下次循环将线程数减少 
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

完整流程回顾:
在这里插入图片描述

4. 注意点

4.1. 线程池是如何保证线程不被销毁的呢?

答案:如果队列中没有任务时,核心线程会一直阻塞在获取任务的方法,直到返回任务。而任务执行完后,又会进 入下一轮 work.runWork()中循环
验证:秘密就藏在核心源码里 ThreadPoolExecutor.getTask()

//work.runWork(): 
while (task != null || (task = getTask()) != null) //work.getTask(): 
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
Runnable r = timed ? 
	workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS):workQueue.take();

4.2. 那么线程池中的线程会处于什么状态?

答案:TIMED_WAITING,RUNNABLE,WAITING
验证:起一个线程池,放置一个任务sleep,debug查看结束前后的状态

//debug add watcher: 
((ThreadPoolExecutor)poolExecutor).workers.iterator().next().thread.getState()
ThreadPoolExecutor poolExecutor = Executors.newFixedThreadPool(5);
poolExecutor.execute(new Runnable() {
     public void run() {
         try {
             Thread.sleep(5000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
     }
 });
 System.out.println("ok");

4.3. 核心线程与非核心线程有区别吗?

答案:没有。被销毁的线程和创建的先后无关。即便是第一个被创建的核心线程,仍然有可能被销毁
验证:看源码,每个works在runWork的时候去getTask,在getTask内部,并没有针对性的区分当前work是否是核 心线程或者类似的标记。只要判断works数量超出core,就会调用poll(),否则take()

5. Executors工具

以上构造函数比较多,为了方便使用,提供了一个Executors工具类

  • newCachedThreadPool() : 弹性线程数
  • newFixedThreadPool(int nThreads) : 固定线程数
  • newSingleThreadExecutor() : 单一线程数
  • newScheduledThreadPool(int corePoolSize) : 可调度,常用于定时
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值