1.创建线程的三种方式
- 方式一:直接使用Thread
Thread t = new Thread(){
@Override
public void run(){
}
};
t.start();
- 方式二:使用Runnable配合Thread
Runnable runnable = new Runnable(){
@Override
public void run(){
}
};
Thread t = new Thread(runnable);
t.start();
- 方式三:使用FutureTask配合Thread
FutureTask<String> futureTask = new FutureTask<>(new Callable<String>{
return "返回值";
});
Thread t = new Thread(futureTask);
t.start();
futureTask.get();
2.观察多个线程同时运行
- 交替运行
- 哪个线程优先运行不由我们控制,是由操作系统去控制
3.查看java线程、进程运行的方法
- Windows
- 任务管理器可以查看进程和线程运行状态,也可以通过“结束任务”来杀死进程或线程
- 打开cmd,输入“tasklist”查看进程列表
- 打开cmd,输入“taskkill”杀死进程
- linux
ps -fe查看所有进程
ps -fT -p <PID>查看某个进程的所有线程
kill 杀死进程
top实时查看进程运行状态
top -H -P <PID>实时查看某个进程的所有线程运行状态
- java
jps查看所有的java进程
jstack<PID>查看某个java进程的所有线程信息
jconsole来查看某个进程中线程的运行状态(图形界面)
4.原理之线程运行
- Java Virtual Machine Stacks(Jva虚拟机栈)
- 栈与栈帧 ,JVM由堆、方法区、栈所组成。每个线程启动后,虚拟机会为其分配一块栈内存
- 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所调用的内存
- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
- 线程上下文切换(Thread Context Switch)
- 因为有一些原因导致CPU不再执行当前线程,转而执行另外一个线程的代码
- 线程的CPU时间片用完
- 垃圾回收
- 有更高优先级的线程运行
- 调用sleep、join、wait、park、synchronized、lock等方法
- 当Context Switch发生时,需要操作系统保存当前线程的状态,并恢复另一个线程的状态,java中对应的概念就是程序计数器,它的作用是记住下一条JVM指令的执行地址,是线程私有的
- 状态包括程序计数器、虚拟机栈中每个栈帧的信息,栈帧里面包括局部变量,操作数栈,返回地址等
- Context Switch频繁发生会影响性能
5.start和run
- 直接调用run方法,实际上还是主线程在执行任务,没有启动新的线程
- 使用start是使用了新的线程,通过新的线程简介执行run方法
6.yield和sleep
- sleep
- 调用sleep会让当前线程从Running进入Timed Waiting状态(阻塞)
- 其他线程可以使用interrupt方法打断正在睡眠的线程,这是sleep方法会抛出InterruptedException
- 睡眠结束后的线程未必会立刻得到执行
- 建议使用TimeUnit的sleep代替Thread的Sleep来获取更好的可读性
- yield
- 调用yield的线程会从Running进入Runnable状体(就绪),然后调度执行其他线程
- 具体实现依赖于操作系统的任务调度器,当线比较空闲时基本上没有什么直观的效果
- 线程优先级
- 线程优先级会提示调度器优先调度该线程,但仅仅只是一个提示,调度器可以忽略这个提示
- 在CPU比较忙时,那么优先级高的线程可以获得更多的时间片,但CPU空闲时,几乎没有什么效果
7.join方法详解
static int a = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
a = 10;
}, "t1");
t1.start();
System.out.println(a);
}
- 分析
- 因为主线程和线程“t1”是同时执行的,所以主线程并不会等待“t1”线程执行完才输出结果
- 解决办法
- 使用“Thread.join()”,放在“t1.start()”后面即可
- 使用Sleep,不过不建议这么使用,因为我们无法预知“t1”线程究竟要执行多久
- 应用
- 需要等待结果返回,才能继续运行就是同步
- 不需要等待结果返回还能继续运行就是异步