一.有关线程,进程概念性知识总结
1.进程
进程是一个具有独立功能的程序关于某个数据集合的一次执行过程,也是系统进行资源分配和调度的基本单位。例如电脑上打开的记事本,网易云(.exe)等。
2.线程
线程是操作系统CPU运算调度的最小单位
线程属性:每个线程拥有独立的程序计数器、虚拟机栈和本地方法栈
参考文章
Java 并发常见面试题总结(上) | JavaGuide(Java面试+学习指南)
3.线程与进程的区别
1.线程切换与进程切换
每个进程都有独立的代码和数据空间(程序上下文),进程之间的切换会有较大的开销;
同一类线程共享进程的堆和方法区资源,线程之间切换的开销小。
疑问? 进程切换为什么比线程切换的开销大?
因为,每次进程切换时,都会涉及页表的切换,不过切换页表这个操作本身是不太耗时的。但是在切换之后,TLB(页表缓存/快表)就失效了,所以在进行地址转化时就需要重新去查找页表,这就造成了程序运行的效率低下。
而同一个进程的线程之间是共用一个页表的,所以线程之间的切换是不需要切换页表的,因此线程切换不存在上述代价大,效率低的问题。
为什么进程切换比线程切换代价大,效率低?【TLB:页表缓存/快表】_为什么进程切换的开销比线程大_dreamer'~的博客-CSDN博客
2.包含关系
一个进程可以拥有一个或者是若干个线程;
因此,亦可以说线程是进程的一部分,线程也被称作为轻量级进程。
3.影响关系
一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
4.执行过程
每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行
4.并行与并发
并行:在多核cpu中,多个任务同时被执行
并发:在单核cpu中,多个任务在同一时间段内被执行(在单核cpu中按照某个时间片交替执行)
eg:举个走高速公路的例子
并行 就是有很多汽车(任务)同时在多条高速路上(多核CPU)行驶
并发 就是很多汽车(任务)同时在一条高速公路(单核CPU)依次排序行驶
5.同步与异步
同步方法 :调用一旦开始,调用者必须等待方法执行完成,才能继续执行后续方法。
异步方法:方法一旦开始,立即返回,调用者无需等待其中方法执行完成,就可以继续执行后续方法。
异步方法的编写:
Java 异步编程 (5 种异步实现方式详解)_java异步方法怎么写_mikechen的互联网架构的博客-CSDN博客
6.线程的线程的生命周期和状态
- NEW: 初始状态,线程被创建出来但没有被调用
start()
。 - RUNNABLE: 运行状态,线程被调用了
start()
等待运行的状态。 - BLOCKED :阻塞状态,需要等待锁释放。
- WAITING:等待状态,表示该线程需要等待其他线程做出一些特定动作(通知或中断)。
- TIME_WAITING:超时等待状态,可以在指定的时间后自行返回而不是像 WAITING 那样一直等待。
- TERMINATED:终止状态,表示该线程已经运行完毕。
线程状态切换图:
二.线程类(Thread)中常见的方法
1.线程的构造方法
Thread() | 无参构造 |
Thread(String name) | 创建线程实例的同时,设置线程名称 |
Thread(Runnable target) | 创建一个实现Runnable接口的线程 |
Thread(Runnable target,String name) | 创建一个实现Runnable接口的线程,同时设置线程名称 |
2.线程常见的方法
public void start() | 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 |
public void run() | 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。 |
public final void setName(String name) | 改变线程名称,使之与参数 name 相同 |
public final void setPriority(int piority) | 更改线程的优先级。线程优先级10 5 1 |
public final void getNam() | 获取当前线程名称 |
public final void setDaemon(boolean on) | 将该线程标记为守护线程或用户线程。 |
public final void join(long millisec) | 等待该线程终止的时间最长为 millis 毫秒。 |
public void interrupt() | 中断线程 |
public final boolean isAlive() | 测试线程是否处于活动状态 |
public static void static yield() | 暂停当前正在执行的线程对象,并执行其他线程。 |
public static void sleep(long millisec) | 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响 |
public static Thread currentThread() | 返回对当前正在执行的线程对象的引用 |
public final void join() | 让主线程等待子线程执行结束之后再运行主线程; |
3.线程方法与其他影响线程状态方法的对比
1.wait(), sleep(),yield(),join()的区别
wait()方法:是Object类中的final方法 (public final void wait()),必须在同步代码块中使用,释放当前线程的CPU资源,并释放当前持有的锁;
sleep()方法:是Thread类中的方法,仅释放当前线程的CPU资源;
yield()方法:线程让步,线程让出cpu,返回就绪状态,执行同等优先级或者优先级更高的线程;
join()方法:让主线程等待子线程执行结束之后再运行主线程;
【Java多线程】了解线程的锁池和等待池概念_墩墩分墩的博客-CSDN博客_线程等待池
2.notify()与notifyAll()的区别
来自于Object类中的两个方法,唤醒线程;
notify()方法会随机唤醒等待池中的任一线程进入锁池争夺锁,会造成死锁;
notifyAll()方法会唤醒等待池中所有的线程;
4.线程的执行顺序
线程的执行顺序是不确定的;
1.确保线程执行顺序的简单示例
在实际业务场景中,有时,后启动的线程可能需要依赖先启动的线程执行完成才能正确的执行线程中的业务逻辑。此时,就需要确 保线程的执行顺序。那么如何确保线程的执行顺序呢?
答:可以使用Thread类中的join()方法,使主线程进入线程等待状态,直至调用join()方法的线程智执行完run()后,线程会调用notifyAll()方法进行唤醒主线程。
join()方法总结
在主线程中,子线程调用join()方法时,并不是子线程进入线程等待,而是主线程进入线程等待。等待子线程执行完,在执行主线程中子线程后续的任务。
(原因:join方法的底层还是wait()方法,虽然调用这是子线程,但执行wait()方法的是主线程)
Java 多线程join()方法的作用和实现原理解析_艾阳Blog的博客-CSDN博客_多线程join()有什么用
三.创建线程的四种常见方式
1.继承Thread类;
2.实现Runnable接口,重写run()方法;
3.实现Callable接口,重写call()方法 以及Future接口创建线程;
为什么要添加三方式呢?使用继承Thread类和实现Runnable的方法创建线程,无法获取线程执行完成后所得到的结果,也无法获取线程执行中所抛出的异常。
java面试--Future,FutureTask,Callable,Runnable关系_晨沉辰的博客-CSDN博客
4.使用线程池
线程的创建方式_创建线程的方式_junxianboy的博客-CSDN博客
Java面试题之——线程池_梦远星帆的博客-CSDN博客_java 线程池 面试
四.创建线程池
1.线程池是什么?线程池就是利用池化思想建立一个管理和调度线程的池子。
2.线程池的作用是什么?
降低资源的消耗;通过重复利用已经创建好的线程降低线程创建和销毁所消耗的资源以及在一定程度上,减少线程上下文切换导致的消耗;
提高程序响应速度;当任务到达时,线程池存在空闲线程时,任务不用等待线程的创建,可以立即被线程执行;
提高线程的可管理性;线程时稀缺资源,无限制或无计划的创建线程,不仅消耗系统的资源,还会大大降低程序的稳定性,使用线程池可以进行统一的调度,调优和监控;
3.线程池的生命周期?线程池有那些状态?
running: 线程池创建时的状态,允许提交和执行任务;
shutdown: 线程池不允许提交新的任务,但会执行完阻塞队列中已经提交的任务;
stop: 线程池不允许提交新的任务,中断正在执行的任务,不会执行阻塞队列中已经提交的任务;
tidying:整理状态 所有的任务都已经终止,workCount(有效线程数为0)
terminated:终止状态 在terminated方法执行之后进入该状态
shutDown()方法:将线程池的状态由running状态转为shutdown状态;
shutDownNow()方法:将线程池的状态由running状态或shutDown状态转为stop状态;
4.创建线程池
1. 通过 ThreadPoolExecutor 创建的线程池;
2. 通过Executors 创建的线程池。
方式一:原生创建线程池,阿里推荐
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
线程池的七个内置参数,可以有效地帮助我们理解线程池的工作原理。
corePoolSize:核心线程数,默认情况下核心线程一直存活在线程池中;
maximumPoolSize:线程池拥有的最大线程数【包含核心线程数】;
临时线程数 = 最大线程数 - 核心线程数
workQueue:用于缓存任务的阻塞对列;
当调用线程池的execute()方法添加一个线程任务时;
1.如果正在运行的线程数量少于corePoolSize,线程池会创建线程并执行改线程任务;
2.如果正在运行的线程数量大于或等于corePoolSize,该任务将被加入workQueue阻塞队列中等待执行;
3.如果阻塞队列已满,且正在执行的线程数少于maximumPoolSize,线程池创建非核心线程执行任务;
4.如果正在执行的线程大于或等于maximumPoolSize,线程池将采用拒绝策略处理;
5.
方式二:
1. Executors.newFixedThreadPool:创建⼀个固定⼤⼩的线程池,可控制并发的线程数,超出的线程会在队列中等待;
2. Executors.newCachedThreadPool:创建⼀个可缓存的线程池,若线程数超过处理所需,缓存⼀段时间后会回收,若线程数不够,则新建线程;
3. Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执⾏顺序;
4. Executors.newScheduledThreadPool:创建⼀个可以执⾏延迟任务的线程池;
5. Executors.newSingleThreadScheduledExecutor:创建⼀个单线程的可以执⾏延迟任务的线程池;
6. Executors.newWorkStealingPool:创建⼀个抢占式执⾏的线程池(任务执⾏顺序不确定)【JDK1.8 添加】。
扩展:
CompletableFuture使用详解
CompletableFuture使用详解_sermonlizhi的博客-CSDN博客_completablefuture