背诵 Java多线程
1、进程
进程是系统进行资源分配和调度的独立单位,每一个进程都有它自己的内存空间和系统资源;进程实现多处理机环境下的进程 调度、分派、切换时,都需要花费较大的时间和空间开销;为了提高系统的执行效率,减少处理机的空转时间和调度切换的时间,以及便于系统管理,所以有了线程,线程取代了进程调度的基本功能;简单来说,进程作为资源分配的基本单位,线程作为资源调度的基本单位
2、进程之间的通信方式
进程间通信主要有以下7种方式:管道/匿名管道、有名管道、信号、消息队列、共享内存、信号量、Socket
3、Java线程通信方式
① wait()、notify()、notifyAll():wait()方法可以让当前线程释放对象锁并进入阻塞状态;notify()方法用于唤醒一个正在等待相应对象锁的线程,使其进入就绪队列
② await()、signal()、signalAll():使用Condition 的 await() + signal()/signalAll() 这种方式能够更加安全和高效地实现线程间协作
③ BlockingQueue:程序的两个线程通过交替向BlockingQueue中放入元素、取出元素,即可很好地控制线程的通信
4、创建线程的四种方式
1、继承Thread类
2、实现Runnable接口
3、实现Callable接口
4、使用线程池创建
一般推荐采用实现接口的方式来创建多线程
5、线程中start()和run()的区
start() ;它的作用是启动一个新线程,start()不能被重复调用
run()方法称为线程体,它包含了该线程要执行的内容,run()方法运行结束,此线程随即终止;和普通的成员方法一样,可以被重复调用
注意:如果直接调用run方法,并不会启动新线程!
6、线程的生命周期和状态
① 新建:线程对象已经创建,但是还没有调用start()方法
② 就绪:线程对象调用start()方法,但是还未开始运行就是就绪状态;一旦获取CPU时间片,就开始运行
③ 运行:当线程对象调用了start()方法,并且获取了CPU时间片,就是运行状态
③ 阻塞:等待获取一个排它锁,如果其线程释放了锁就会结束此状态;如调用wait()方法进入阻塞状态
⑥ 死亡:可以是线程结束任务后自己结束,或者产生了异常而结束
7、线程死锁
死锁:两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象
产生死锁的4个必要条件:
① 互斥条件:某一资源在任意⼀个时刻只由⼀个线程占⽤
② 请求与保持条件:⼀个线程因请求资源⽽阻塞时,对已获得的资源保持不放
③ 不可剥夺条件:线程已获得的资源在末使⽤完之前不能被其他线程强⾏剥夺,只有⾃⼰使⽤完毕后才释放资源
④ 循环等待条件:若⼲线程之间形成⼀种头尾相接的循环等待资源关系
避免死锁最简单的方法就是 破坏循环等待条件 ,将系统中所有的资源设置标志位、排序, 规定所有的进程申请资源必须以一定的顺序(升序或降序)申请
8、sleep() 和 wait() 的区别
-
sleep()是属于Thread类中的方法;让当前正在执行的线程在指定的时间内暂停执行,进入阻塞状态,但是在此期间线程不释放锁,只阻塞线程,当指定的时间到了又会自动恢复运行状态,可中断,sleep()给其他线程运行机会时不考虑线程的优先级,高优先级和低优先级的线程都有机会执行
-
wait()是属于Object 类中的方法,线程会释放对象锁,只有当其他线程调用 notify() 或 notifyAll() 才能唤醒此线程;wait()方法在使用时必须先获取对象锁,即必须在 synchronized 修饰的方法或代码块中使用,那么相应的 notify() 或 notifyAll() 方法同样必须在 synchronized 修饰的代码块中使用,如果没有在 synchronized 修饰的代码块中使用时运行时会抛出IllegalMonitorStateException的异常
9、wait()方法为什么要在while()循环中使用?
10、线程池
线程池就是首先创建一些线程,它们的集合称为线程池;使用线程池可以很好地提高性能;
程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务,执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务
11、线程池的工作机制
在线程池的编程模式下,任务是提交给整个线程池,而不是直接提交给某个线程,线程池在拿到任务后,就在内部寻找是否有空闲的线程,如果有,则将任务交给某个空闲的线程;一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务
12、什么是线程复用?线程复用的原理
线程复用:在线程池中,通过同一个线程去执行不同的任务
线程复用的原理:在线程池中,同一个线程可以从阻塞队列中不断获取新任务来执行,其核心原理在于线程池对 Thread 进行了封装,并不是每次执行任务时都会调用 Thread.start() 来创建新线程,而是让每个线程去执行一个“循环任务”,在这个“循环任务”中不停的检查是否有任务需要被执行,如果有则直接执行,也就是调用任务中的 run()方法,将 run()方法当成一个普通的方法执行
总结:线程池将线程和任务进行解耦,摆脱了之前通过 Thread 创建线程时的一个线程必须对应一个任务的限制
13、为什么使用线程池
(1) 降低资源消耗;通过重复利用已创建的线程降低线程创建和销毁造成的消耗
(2) 提高响应速度;当任务到达时,任务可以不需要等到线程创建就能立即执行
(3) 提高线程的可管理性;线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控
14、线程池有哪些参数?每个参数的作用是什么?
序号 | 参数 | 含义 | 解释 |
---|---|---|---|
1 | corePoolSize | 核心工作线程数 | 当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize时 |
2 | maximumPoolSize | 最大线程数 | 线程池所允许的最大线程个数;当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。另外,对于无界队列,可忽略该参数 |
3 | keepAliveTime | 多余线程存活时间 | 当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数 |
4 | unit | 空闲线程存活时间单位 | keepAliveTime的计量单位 |
5 | workQueue | 工作队列 | 用于传输和保存等待执行任务的阻塞队列;即任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务 |
6 | threadFactory | 线程创建工厂 | 用于创建新线程;threadFactory创建的线程也是采用newThread()方式,threadFactory创建的线程名都具有统一的风格: pool-m-thread-n (m为线程池的编号,n为线程池内的线程编号) |
7 | handler | 拒绝策略 | 当线程池和队列都满了,再加入线程会执行此策略 |
15、线程池的拒绝策略和阻塞队列
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· Vue3状态管理终极指南:Pinia保姆级教程