java线程
创建和运行线程
直接使用 Thread 创建线程
1 | // 新建线程对象 参数为线程名称 |
使用 Runnable 配合 Thread
runnable 代表了可运行的任务,将线程和任务进行分离
1 | Runnable runnable = new Runnable() { |
FutureTask 配合 Thread
FutureTask 也是表示了任务,他和 runnable 的区别在于他有返回值,而 Runnable 没有返回值。
1 | // 创建任务对象 泛型表示返回的对象 |
多个线程同时运行
- 交替运行
- 先后顺序不由我们控制,而由底层的操作系统进行控制
查看进程线程的方法
windows
taskList
查看进程taskkill
杀死进程
linux
ps -fe
查看所有进程ps -fT -p <PID>
查看某个进程(PID)的所有线程kill
杀死进程
java
jps
查看所有的 java 进程jstack <PID>
查看某个 Java 进程的所有线程状态jconsole
查看运行状态(图形界面)
线程运行的原理
栈与栈帧
每个线程启动之后,虚拟机会为其分配一块栈内存。
每个栈由多个栈帧(Frame) 组成,对应着每次方法调用时候所占用的内存
每个线程只能有一个活动栈帧,对应着当前正在执行的方法
线程的上下文切换
上下文切换,即 CPU 分配的时间片由一个线程转为另一个线程,也即切换线程
导致上下文切换可能的原因:
- 线程的 CPU 时间片用完
- 垃圾回收
- 有更高级的线程需要运行
- 线程自己调用了 sleep,yield,wait,join,park,synchronized,lock 等方法
当上下文切换执行后,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态。java 中通常通过程序计数器来实现,上下文切换的频繁发生会影响性能。
线程的常见方法
run() 和 Start()
- run() 方法是线程中一个重写执行逻辑的方法,如果在主程序单独的调用线程的run方法,只是实现了其中的逻辑,而没有启动线程。
- start() 启动了新的线程,在新的线程中执行 run() 方法等方法。
sleep() 和 yield()
- sleep()
○ 调用他会让线程从 Running 进入到 Timed Waiting 状态(阻塞)
○ 可以使用 interrupt 打断正在睡眠的线程,此时会抛出 InterruptedException错误。 - yield()
○ 调用他会让线程从 Running 进入到 Runnable 状态
○ 具体执行逻辑取决于任务调度器
join()
等待 join() 的调用者的任务执行完成之后,在进行下一步的操作。可以理解成局部线程的同步。其中参数可以添加时间,取到线程执行任务时间和参数传递的时间的最小值作为最多等待的时间。
interrupt
打断 sleep, wait, join 的线程,并抛出InterruptedException异常,如果是 sleep 状态的线程被打断,会清空打断状态,即t.isInterrupted() = false
如果是打断正常运行的线程,那么t.isInterrupted() = true
主线程与守护线程
只要非守护线程运行结束了,那么即使守护线程的代码没有执行完成,也会强制结束。
设置守护线程:t1.setDaemon(true);
线程的状态
五种状态(从操作系统层面来讲)
- [初始状态],刚 new 出来的对象,仅仅是对象层面,还没有和线程相关联。
- [可运行状态],指该线程已经被创建,可以被 CPU 调度运行。
- [运行状态],获取了CPU时间片的运行中的状态,当 CPU 时间片用完时,会从运行状态转换为可运行状态,导致线程的上下文切换。
- [阻塞状态],线程进行上下文切换到其他的状态,只要一直不被唤醒,调度器就一直不会考虑调度他们。
- [终止状态],表示线程已经执行完毕,生命周期已经结束,不会再转变为其他的状态。
六种状态(从 JAVA API 的层面描述)
- new, 如同操作系统中的初始状态,指线程刚刚被创建
- runnable, 包括了操作系统中的可运行状态,运行状态和阻塞状态
- blocked,加锁导致的阻塞
- waiting,调用 wait()方法之后的阻塞
- timed_waiting, 调用 wait()方法之后的阻塞,但是有时限
- terminated,终止状态。