1.创建和运行线程
方法一,直接用Thread
例如:
方法二,使用Runnable配合Thread。Thread 代表线程,Runnable 可运行的任务(线程要执行的代码)。
例如:
用lambda精简代码
方法三,FutureTask 配合 Thread
2.多个线程同时运行
- 交替执行
- 谁先谁后,不由我们控制
3.查看进程线程的方法
windows
- 任务管理器可以查看进程和线程数,也可以用来杀死进程
- tasklist查看进程
- taskkill杀死进程
Linux
- ps -fe查看所有进程
- ps -fT -p查看某个进程(PID)的所有线程
- kill杀死进程
- top按大写H切换是否显示线程
- top -H -p查看某个进程(PID)的所有线程
Java
- jps命令查看所有Java进程
- jstack查看某Java进程(PID)的所有线程状态
- jconsole来查看某个Java进程中线程的运行情况(图形界面)
4.线程运行的原理
栈与栈帧
每个线程启动后,虚拟机就会为其分配一块栈内存。每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存。每个线程只能有一个活动栈帧(位于栈顶),对应着当前正在执行的那个方法。
线程上下文切换
CPU不再执行当前的线程,转而执行另外一个线程称为线程上下文切换。频繁发生线程上下文切换会影响性能。有以下原因导致线程上下文切换。
- 线程的CPU时间片用完
- 垃圾回收
- 有更高优先级的线程需要运行
- 线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法
当发生线程上下文切换时,JVM通过程序计数器来保存当前线程的状态,并且记住下一条JVM指令的执行地址,是线程私有的。
5.常见方法
6.start与run
- 直接调用run是在主线程中执行了run,没有启动新的线程
- 使用start是启动新的线程,通过新的线程间接执行run中的代码
7.sleep 与 yield
Sleep
- 调用sleep会让当前线程从Running进入Timed Waiting状态(阻塞)。
- 其它线程可以使用interrupt方法打断正在睡眠的线程,这时sleep方法会抛出 InterruptedException。
- 睡眠结束后的线程未必会立刻得到执行。
Yield
- 调用yield会让当前线程从Running进入Runnable就绪状态,然后调度执行其它线程。
- 具体的实现依赖于操作系统的任务调度器。
线程优先级
- 线程优先级会提示调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它。
- 如果CPU比较忙,那么优先级高的线程会获得更多的时间片,但CPU空闲时,优先级几乎没作用。
- 可以通过setPriority方法来为线程设置优先级。最小优先级是1,最大是10。默认优先级是5。
8.join
join用来等待调用join方法的线程执行结束。用于线程同步。
join(long time)有时间限制的等待,如果时间到了没有执行完也直接结束,如果提前执行完了就提前结束等待。
9.interrupt
Interrupt方法用于打断阻塞线程(wait、sleep、join),join的底层原理就是wait。打断阻塞线程会清空打断状态。比如打断sleep时,isInterrupted方法会显示false。
打断正常运行的线程不会清空打断状态。isInterrupted方法会显示true。
打断park线程, 不会清空打断状态。果打断标记已经是true, 则park会失效。可以使用 Thread.interrupted()清除打断状态,这样可以重新调用park()。
10.主线程和守护线程
默认情况下,Java进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。
垃圾回收器线程就是一种守护线程。
11.五种状态
初始状态:仅是在语言层面创建了线程对象,还未与操作系统线程关联。
可运行状态(就绪状态):指该线程已经被创建(与操作系统线程关联),可以由CPU调度执行。
运行状态:指获取了 CPU 时间片运行中的状态。当CPU时间片用完,会从运行状态转换至可运行状态,会导致线程的上下文切换。
阻塞状态:如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入阻塞状态。等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至可运行状态。与可运行状态的区别是,对阻塞状态的线程来说只要它们一直不唤醒,调度器就一直不会考虑调度它们。
终止状态:表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态。