多线程基础
什么是程序, 进程
什么是线程
什么是单线程,多线程
并发, 并行的概念
单核cpu来回切换, 造成貌似同时执行多个任务, 就是并发;
在我们的电脑中可能同时存在并发和并行;
怎么查看自己电脑的cpu有几核
1.资源监视器查看
2.此电脑图标右键管理- 设备管理器- 处理器
3.Java代码
线程的基本使用
创建线程的两种方式
实例 (第一种继承Thread)
当mian启动了一个子线程, 主线程不会阻塞, 可以看到在同时执行(如果是多核cpu可能是并行, 单核则是并发)
JConsole查看线程
控制台输入jconsole
打开这么一个界面
连接我们的这个类
切换到线程可以看到这里有两个线程还在跑(就是说主线程结束了, 就不代表进程结束了, 只要子线程未结束, 就不会造成进程的结束, 只有所有线程结束了才会挂掉)
当主线程结束时, 界面上的一个状态
为什么用start
为什么用start方法而不直接掉run方法呢, 因为run方法就是一个普通方法, 如果直接去掉run的话, 相当于就是主线程去调用, start才会真正的启动线程
查看源码
// 进入start
// 进入这个方法
// 这个方法里面有一个start0
// 进入start0 , 这个start0 是个native方法, 是本地方法, 是JVM调用的, 底层是c/c++实现
// 真正实现多线程的是start0这个方法, 可以理解为实在start0里面用的多线程的机制调的这个run方法, 而不是run实现了多线程
// start方法调用start0后, 该线程不会立即执行, 只是将线程变成了可运行的状态, 具体什么时候执行, 取决于cpu, 由cpu统一调度
第二种实现Runnable接口
实例
继承接口没有start(), 需要用到另外一种方式
通过创建一个Thread对象去start
// 为什么这里通过叫Dog对象放在Thread就可以执行到Dog类的run方法呢, 这里使用了一种设计模式[静态代理模式]
// 代理就好比你想送一个东西给你的朋友, 但是你没有时间, 你叫了一个美团跑腿, 或者你想买一张票, 但是你没有时间, 你叫别人和你买, 这个就是代理
--用代码模拟代理模式的实现机制
// 假设这个就是Thread代理类, 通过构造器可以接收一个实现了Runnable接口的类, 赋值给target, 通过动态绑定执行构造器传进来的run方法
// 代理就是什么呢, 我自己没有start这个方法, 我让你帮我来做这个事情, 最后实现我的run方法, 就好比是买票我没有时间, 你给我买了票, 最后拿给了我
继承Thread和实现Runnable有什么区别
模拟一个售票系统 , 提出一个互斥同步的问题
第一种方式继承Thread
有一个最大的问题, 三个窗口确实都在卖, 但是票都-1了, 还在卖, 导致超卖了 (互斥同步问题)
因为在都执行到这的时候, 假如是2, 三个线程都通过了, 都到下面去进行减减操作了, 每个线程减一, 就变成了减3, 就会变成-1
第二种方式实现Runnable
将继承改为实现接口, 其余代码一样
main里面
也会导致超卖, 原因和上面一样
怎么解决上面超卖的问题 ☆线程同步机制
使用synchronized解决售票问题
将run方法里面代码提取出来放到一个加了synchronized的同步方法里面, 将这个方法在放到线程run里面
或者加在
分析同步的原理
假如有三个线程, t1, t2, t3, 它们首先会去争夺这把锁(这个锁是放在对象上的), 在t1拿到这个锁的时候,他就进去了, 它会执行完, 然后在把锁放回去, 放回去之后t2和t3再去抢这把锁, t1有没有可能再去抢这把锁, 也是有可能的, 这把锁就要看它是公平锁还是非公平锁(这里是非公平锁), 这个锁实在对象上, 不是在代码块上, 所以我们把这个锁叫对象锁, 它是用对象的某一个位来表示是否有这个锁或者是否被某个线程拿到了
这把锁实在对象加的, 也叫互斥锁
加在this对象的例子:
(也可是其他对象, 但是三个线程必须是同一个对象)
静态上加的使用例子:
注意事项
线程的死锁(一定要避免)
实例:
这是一个 非常经典的死锁, 有两个线程, 第一个线程的flag为true, 进去后先拿到o1的锁, 此时同时线程二flag为false拿到o2的锁, 这时o1, o2的锁都被拿到了, 而第一个线程还想去拿o2的锁就会被阻塞, 第二个线程还想去拿o1的锁也会被阻塞, 都无法往下继续跑就会造成死锁
释放锁
线程的退出
线程常用的方法
第一组
注意事项
方法实例
执行interrupt中断线程的休眠, 继续执行
在Java中,线程的interrupt是一种用于中断线程执行的机制。当一个线程被interrupt时,它会收到一个中断信号,可以根据这个信号来决定如何处理。
Java中的线程中断机制通过Thread类的interrupt()方法来实现。调用该方法会设置线程的中断状态为true。如果线程处于阻塞状态(如调用了sleep()、wait()、join()等方法),那么它会立即抛出InterruptedException异常并清除中断状态。如果线程没有处于阻塞状态,那么它的中断状态会被设置为true,可以通过isInterrupted()方法来检查线程的中断状态。
当一个线程被interrupt后,可以根据具体情况来处理中断。常见的处理方式有:
- 终止线程:可以在run()方法中使用一个循环来检查中断状态,当中断状态为true时,退出循环,终止线程的执行。
- 抛出异常:可以在捕获到InterruptedException异常后,根据具体业务逻辑来决定如何处理异常。
- 忽略中断:如果线程的执行逻辑不受中断影响,可以选择忽略中断信号。
需要注意的是,interrupt()方法只是设置了线程的中断状态,并不会真正中断线程的执行。线程是否真正中断还取决于具体的业务逻辑和代码实现。
第二组
yield:
为什么yield不一定礼让成功? 比如有两条线程t1和t2, t1调用yield的时候, 礼让cpu给t2执行,但是如果cpu在资源充足的情况下, 就是cpu觉得能同时兼顾t1和t2的时候, 就有可能礼让不成功, 只有在资源紧张的情况下才大概率礼让成功
join:
假如有线程t1和t2, 在t1里面去掉t2.join, 这么操作的意思就是在执行t1的时候, 告诉cpu我主动放弃cpu, 让你把t2执行完毕在回头执行我t1
守护线程
设置守护线程示例
以下代码现在的执行情况是在主线程执行完循环10次以后, 子线程还在继续执行
我希望在主线程执行完成之后, 子线程就算还没执行完毕也会结束,将子线程设置为守护线程就好了
线程的生命周期
线程的生命周期官方文档是有6个, 为什么有的说有7个呢, 因为在runnable中可以在分为 ready 就绪状态和running真正运行状态