priority:优先级 Interrupted:中断、打断 yield:让步
sync:n 同步
synchronize:vt 同步
synchronization:同步
synchronized:adj 同步的
async:异步,同步这个词加上s就是异步
1.ThreadLocal如何理解
1.1 定义
定义:提供线程局部变量,一个线程局部变量在多线程中,分别有独立的值(副本)
1.2 场景
资源持有:太常见了,例如存储用户的信息,后面只要是这个线程操作都可以直接取出来
- 线程一致性:暂不清除
- 并发计算:将一个大的计算,分解成多个线程来执行,最后只需要同步数据即可
- 线程安全:它是多线程情况下的,保证资源安全
https://www.imooc.com/video/21053
1.3 模型
左图:代表一个进行中含有多个线程
- 右图:线程独占数据是由进程分配的,它包含ThreadLocalMap(其实就是哈希表)
内存泄漏
线程拥有资源,只有计算资源,那么为什么每个线程都有自己的ThreadLocal,并且每个线程的ThreadLocal互不影响?
因为ThreadLocal整体代表的是进程的资源,整体像一个HashMap,用户使用的时候感觉是属于线程的单独资源
2. 同步异步、阻塞非阻塞
2.1 同步异步
- 同步异步这里指的是被调用者的行为(服务器的行为,微服务环境下也指被调用服务)而不是请求方的行为
- 被调用者是否主动告诉调用者结果;调用者是否等待调用结果
- 同步:在没有收到任何结果前,服务端就不返回任何结果,这时候请求可以等待结果那就是阻塞,也可以不等待结果继续执行那就是非阻塞
异步:调用发出之后,服务端会立刻返回,我收到了你的请求,我会处理的
2.2 阻塞非阻塞
线程的角度
- 阻塞:是指当前线程不能执行了,需要等待一段时间,或者需要其它线程来唤醒它
- 非阻塞:当前线程继续执行
- 站在线程发出请求(通常是http请求)的角度
- 看视频理解:https://coding.imooc.com/lesson/516.html#mid=47579
3. 线程的创建
3.1 继承Thread类
- 一个类继承
Thread
类,重写run()
方法,在run()方法中实现运行在线程上的代码 在
Thread
类中提供了一个start()
方法用于启动新线程,线程启动后,系统会自动调用run()
方法3.1 实现Runnable接口
Thread
类提供了一个构造方法Thread(Runnable target)
,其中Runnable
是一个接口,它只有一个run()
方法只需要一个类实现
Runnable
,然后将这个类对象传递Thrad的构造方法中,这样创建的线程将会调用实现了Runnable
接口中的run()
方法作为运行代码3.3 Runnable的优势
适合多个相同程序代码的线程去处理同一个资源的情况,把线程和程序代码、数据有效分离,很好的体现了面向对象的设计思想
- 可以避免java单继承带来的局限性。因为java是单继承,一旦某个类已经继承了另外一个类,那么则无法通过这个方式实现多线程
事实上大部分程序都会采用实现
Runnable
接口来创建多线程// 将实现Runnable接口的对象实例化
CreateThreadRunnable createThreadRunnable = new CreateThreadRunnable();
// 使用Thread类的构造函数,传入一个实现了Runnable接口的对象
Thread thread_one = new Thread(createThreadRunnable);
Thread thread_two = new Thread(createThreadRunnable);
Thread thread_three = new Thread(createThreadRunnable);
// 开启线程,执行run方法中的代码
thread_one.start();
thread_two.start();
thread_three.start();
实现Callable<?>接口
3.4 启动
main
之后Jvm
自动创建了哪些线程signal dispatcher
:把操作系统发过来的信号分发给适当的处理程序finalizer
:负责对象的finalizer()
方法(垃圾回收)-
3.5 start方法
该方法是线程安全的
- 使当前线程开始执行,
Java
虚拟机调用该线程的run()
方法 - 结果是两个线程同时运行:当前线程(从调用
start()
方法返回)和另一个线程(执行其run()
方法) 多次启动一个线程是不合法的。特别是,线程一旦完成执行就可能不会重新启动。
3.6 后台线程(守护线程)
如果一个进行只有后台线程在运行,那么这个进程就会结束
- 新创建的线程默认都是前台线程
- 如果某个线程在启动之前调用
setDaemon(true)
,这个线程就会变成后台线程 thread.isDaemon()
:判断线程是否为后台线程- 要将某个线程设置为后台线程,必须在该线程启动之前设置,也就是说
setDaemon(true)
必须要在start()
之前,否则会引发IllegalThreadStateException
异常4. 线程的生命周期及状态转换
由下图可知,线程只能从阻塞状态进入到就绪状态,不能直接进入到运行状态,也就是说结束阻塞的线程需要重新进入可运行池中,等待系统调用
新建状态
:创建一个线程对象后,该线程对象就处于新建状态,此时它不能运行,和其它java
对象一样,仅仅是由java
虚拟机为其分配了内存,没有表现任何线程的动态特征就绪状态
:- 当线程调用了
start()
方法后,线程就处于就绪状态(也可以称为可运行状态),处于运行状态的线程可以位于运行池中,此时它只是具备了运行条件,能否获取cpu
的使用权,还需要等待系统调用 Tips
:这个调用需要详细了解操作系统的原理,比如:如何排队、调度算法、权限之类的
- 当线程调用了
运行状态
:- 处于就绪状态的线程获取了
cpu
的使用权,开始执行run()
方法中的线程执行体,则该线程处于运行状态 - 当一个线程处于运行状态的时候,它不可能一直处于运行状态,除非它的执行体足够短,瞬间就完成了
- 当系统分配的时间执行完成了,系统就会剥夺该线程的cpu使用权,让其它线程执行
- 处于就绪状态的线程获取了
阻塞状态
:- 一个线程可能在某种情况下,例如进行耗时的输入/输出操作的时候,会放弃
cpu
的使用权进入阻塞状态,线程进入阻塞状态后,就不能进入队列排队了,当引起阻塞的原因被取消后,才能进入就绪状态 - 常见的引起线程由运行进入到阻塞,以及如何从阻塞进入到就绪
- 同步锁情况:当线程试图获取某个对象的同步锁,如果该锁被其它对象持有,则当前线程会进入阻塞状态。只有获取了锁,才会从阻塞状态进入到就绪状态
- 当线程调用了一个阻塞式的
IO
方法,就会进入阻塞状态,如果想进入就绪状态,就必须要等到这个阻塞的IO
方法返回 - 当线程调用了某个对象的
wait()
方法时,就会进入到阻塞状态,想要进入到就绪状态就需要使用到notify()
方法唤醒该线程 - 当调用线程的
sheep(long millis)
方法时,也会使线程进入阻塞状态,等到线程休眠的时间到了,就会自动进入就绪状态 - 线程中调用了另外一个线程的
join()
方法时,会使当前线程(调用者线程)进入阻塞状态,这时候需要等到新加入的线程执行完毕才会结束阻塞状态,进入就绪状态
- 一个线程可能在某种情况下,例如进行耗时的输入/输出操作的时候,会放弃
死亡状态
:现成的run()
方法执行完毕,或者线程抛出一个未捕获的异常、错误,线程就进入死亡状态5. 线程调度
5.1 简单的操作系统知识
java
虚拟机会按照特定的机制为程序中的每个线程分配cpu的使用权,这种机制就被称作线程的调度- 线程调度的两种模型:
优先级常量
static int max_priority 最高优先级,=10
static int norm_priority 普通优先级,=5
static int min_priority 最低优先级,=1
main线程的优先级默认是”5”
可以通过setPriority(int newPriority)设置线程的优先级,参数可以接受1~10之间的整数,或者三个常量 ```
5.3 线程休眠 sleep()
- 如果人为的控制线程,使正在执行的线程暂停,将cpu让给别的线程,可以使用
sleep(long millis)
sleep(long millis)
方法会声明抛出InterruptedException
异常sleep
是一个静态的并且也是一个native
的方法,只能控制正在运行
的线程休眠(休眠就是进入阻塞状态),而不能控制其它线程休眠,休眠结束后会返回到就绪状态,而不是立即开始运行5.4 线程让步
yield()
线程让步可以通过
yield()
,方法来实现,该方法和sleep()
类似,都可以使线程暂停- 区别是:
yield()
方法不会阻塞该线程
,它只是让系统的调度器重新调度一次 使用之后,只有与当前线程优先级相同或者更高的线程才能获得机会
5.5 线程插队
join()
某个线程调用其它线程的join()方法,调用的线程将被阻塞,
- 直到被join()的线程执行完成之后(是整体执行完成,不是单纯的获取一次cpu使用权限),才会回到就绪状态等待执行
6. 多线程同步
线程安全问题就是“并发编程-锁相关”的临界条件部分 多线程同步这一块,仅仅是入门了解,具体的还是需要看“并发编程-锁相关”
6.1 线程安全
- 多线程并发执行可以提高程序效率,多线程访问同一资源,会引发线程安全问题
- 存在线程安全的前提