目录
2. Java线程
Java线程的执行顺序是由底层的任务调度器去实现的,不由我们控制
2.1. 线程的创建方式
2.1.1. 直接使用Thread
Thread:将线程的创建和任务的创建合并在一起
//通过Thread创建任务 public static void main(String[] args) { Thread t1 = new Thread("t1"){ @Override public void run() { log.info("t1"); } }; t1.start(); log.info("main"); }
2.1.2. Runnable配合Thread
Runnable:将任务的创建和线程的创建分开,脱离了Thread继承体系,更容易与线程池等高级API结合
//先创建Runnable任务再创建线程 public static void main(String[] args) { Runnable runnable = new Runnable() { @Override public void run() { log.info("t1"); } }; Thread t1 = new Thread(runnable,"t1"); t1.start(); log.info("main"); }
2.1.3. FutureTask配合Thread
public static void main(String[] args) throws ExecutionException, InterruptedException { FutureTask<String> task = new FutureTask<>(()->{ log.debug("task"); return "task"; }); Thread t1 = new Thread(task,"t1"); t1.start(); log.debug("main"); log.debug("结果是{}",task.get()); }
2.1.4. lambda表达式
JDK8后会将只有一个抽象方法的接口(例如Runnable)加上@FunctionalInterface表示当前接口可以使用lambda表达式
编辑
//lambda表达式简化写法 public static void main(String[] args) { Thread t1 = new Thread(()-> log.info("t1"),"t1"); t1.start(); log.info("main"); }
2.2. 查看进程线程
2.2.1. window
- tasklist:查看进程列表
- tasklist | findstr xxx:查看指定字符串的进程列表
- task kill /F /PID:强制杀死指定PID的进程
2.2.2. Linux
- ps -ef:查看进程列表
- ps -ef | grep java:查看java进程命令
- kill -f pid:杀死指定pid的进程
- top:查看详细进程信息
2.2.3. java
- jps:查看所有java进程
- jstack pid:查看这个时刻指定pid的java进程所有线程状态
- jconsole:图形化界面连接Java进程中的线程运行情况
2.3. 线程运行原理
2.3.1. 栈与栈帧
JavaVirtualMachineStacks(Java虚拟机栈),JVM中由堆、栈、方法区所组成,其中栈内存是给线程用的
- 每个线程启动后,虚拟机就会为其分配一块栈内存。·
- 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存·
- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
断点右键指定线程可以调试多个线程运行情况
编辑
2.3.2. 线程上下文切换
线程会因为一些原因导致CPU不能继续执行当前线程,转而执行另外一个线程:
- 当前线程的CPU时间片用完
- 垃圾回收时,只有执行垃圾回收的线程能运行,其他的工作线程都会被阻塞
- 有比当前线程更高优先级的线程需要运行
- 当前线程内部调用了sleep,wait,lock,synchronized,yield,join,park等方法
当线程发生上下文切换(Context Switch)时,操作系统会保留当前线程的运行状态,同时恢复另外一个线程的状态,Java中的完成这个过程的是程序计数器,它会记录下一条字节码指令的地址,线程上下文切换时,由程序计数器提供地址给Java找到继续运行的线程
- Java中的线程状态包括:程序计数器,栈帧信息,例如局部变量,操作数栈,返回地址等等
- 频繁发生线程上下文切换会影响程序的性能
2.4. 线程方法对比
2.4.1. start与run
start:
- 开启线程,并用开启的线程执行run方法.
- 调用start方法会让线程状态从new状态进入runnable状态
- start方法只能调用一次,因为线程处于runnable状态时不能再次开启线程
run:
- 执行run方法,不开启新的线程,调用的只是普通方法.
- 调用run方法不会对线程状态产生影响
2.4.2. sleep与yield
sleep:
- 调用sleep方法会让线程状态从running状态进入timewaiting状态
- 其他线程可以调用interrupt方法打断正在睡眠的线程,此时sleep方法会抛出InterruptedException
- 睡眠结束的线程可能不会立即被调用
- TimeUnit的sleep方法比Thread的sleep方法有更好的可读性
yield:
- 调用yield方法会让线程状态从running状态进入runnable状态,然后调度其他同优先级的runnable状态线程,如果没有同优先级的线程,当前线程还是会运行,相当于是当前线程做出一次让步
- 具体的实现依赖于操作系统的任务调度器