1 线程
1.1 什么是线程?什么是进程?
进程:是指在系统中正在运行的一个应用程序,每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存
线程:进程的基本执行单元,一个进程的所有任务都在线程中执行,进程要想执行任务,必须得有线程,进程至少要有一条线程
1.2 java中线程的实现方式有几种?
继承Thread类
public class MyThread extends Thread{ @Override public void run() { // 执行自己代码逻辑 System.out.println("自己线程被执行"); } public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.start(); } }
实现Runnable接口
public class MyRunnable implements Runnable{ @Override public void run() { // 执行自己代码逻辑 System.out.println("自己Runnable被执行"); } public static void main(String[] args) { Thread thread = new Thread(new MyRunnable()); thread.start(); } }
1.3 线程的生命周期是什么?
- 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());
2 线程存在的问题
2.1 一个线程只能执行一个任务
2.2 线程执行完后销毁,无法复用
public class MyThread extends Thread{ @Override public void run() { // 执行自己代码逻辑 System.out.println("自己线程被执行"); } public static void main(String[] args) throws InterruptedException { MyThread myThread = new MyThread(); myThread.start(); // main线程休息5秒,等待myThread执行完成 TimeUnit.SECONDS.sleep(10); // 执行业务代码 System.out.println("执行其他业务代码"); // 业务代码执行完成,需要再次执行线程 myThread.start(); } }
2.3 线程过多,导致JVM宕机
3 初识线程池
简介:
在多线程编程中,任务都是一些抽象且离散的工作单元,而线程是使任务异步执行的基本机制。随着应用的扩张,线程和任务管理也变得非常复杂。为了简化这些复杂的线程管理模式,我们需要一个“管理者”来统一管理线程及任务分配,这就是线程池。
在主要大厂的编程规范中,不允许在应用中自行显式地创建线程,线程必须通过线程池提供。由于创建和销毁线程需要时间以及系统资源开销,使用线程池的好处是减少这些开销,解决资源不足的问题。
3.1 了解J.U.C
J.U.C全称:java.util.concurrent,在并发编程中很常用的实用工具类。
在并发编程中很常用的实用工具类,用于完成高并发、处理多线程的一个工具包。此包包括了几个小的、已标准化的可扩展框架,以及一些提供有用功能的类,没有这些类,这些功能会很难实现或实现起来冗长乏味
3.2 线程池解决了什么问题
- 降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
- 提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行
- 方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM
- 节省CPU切换线程的时间成本(需要保持当前执行线程的现场,并恢复要执行线程的现场)。
- 提供更强大的功能,延时定时线程池。(Timer vs ScheduledThreadPoolExecutor)
3.3 线程池引发了什么问题
- 异步任务提交后,如果JVM宕机,已提交的任务会丢失,需要考虑确认机制。
- 使用不合理,可能导致内存溢出问题
- 参数过多,代码结构引入数据结构与算法,增加学习难度。
4 线程池的设计思想
5 线程池的原理
5.1 了解线程池类继承结构图
说明:
- 最常用的是ThreadPoolExecutor
- 调度用ScheduledThreadPoolExecutor,类似Timer和TimerTask。
- 任务拆分合并用ForkJoinPool
- Executors是工具类,协助你创建线程池的
5.2 线程池工作状态
线程池状态
- RUNNING:初始化状态是RUNNING。线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0。RUNNING状态下,能够接收新任务,以及对已添加的任务进行处理。
- 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
- 注意:容易引发不可预知的结果!运行中的任务也许还会打印,直到结束,因为调的是Thread.interrupt
//改为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("terminated"); } //调用 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