Java线程
创建和运行线程
- Thread
public static void main(String[] args) {
// 创建线程对象 lambda
Thread thread = new Thread(() -> log.info("子线程执行"));
// 设置线程名称
thread.setName("thread-0000");
// 启动线程
thread.start();
log.info("主线程执行完毕");
}
- Runnable 配合 Thread
public static void main(String[] args) {
Runnable task = new Runnable() {
@Override
public void run() {
log.info("子线程已执行");
}
};
Thread thread = new Thread(task, "thread-0001");
thread.start();
log.info("主线程执行完毕");
}
-
使用Runnable小结
- 将线程与任务解耦
- 从而更容易与线程池等高级API配合使用
- 脱离Thread继承体系,更灵活
-
FutureTask 配合 Thread
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> futureTask = new FutureTask<>(() -> {
log.info("FutureTask运行");
Thread.sleep(1000);
return 100;
});
Thread thread = new Thread(futureTask, "thread-0002");
thread.start();
// 获得返回值,阻塞线程未执行完时,将阻塞等待返回结果
Integer result = futureTask.get();
log.info("获得返回值 {} ", result);
log.info("主线程执行完毕");
}
观察多个线程运行情况
- 交替执行
- 执行的先后顺序不由我们控制
查看进程与线程
-
windows
- 查看进程:tasklist
- 杀死进程:taskkill
-
linux
- 查看所有进程 ps -ef
- 查看某进程下所有线程 ps -fT -p <PID>
- 杀死进程 kill
- 显示进程 top 按 H 切换线程
- top -H -p <PID> 查看某个PID下所有线程
-
Java相关
- 查看所有Java 进程 jps
- 查看某个Java进程所有线程 状态 jstack <PID>
- 图形界面,支持远程 jconsole
线程运行的原理
-
栈与栈桢
- JVM主要由堆、栈、方法区组成
- 堆内存其实就是分给一个个线程使用的
- 每个线程启动后,虚拟机会为其分配一块栈内存
- 每个栈由多个栈桢(Frame)组成,栈桢对应每次方法调用所占内存
- 一个线程同时只能有一个活动栈桢,对应正在执行的那个method
-
线程上下文切换
-
由于CPU会在不同线程间执行切换,切换时,操作系统将保存当前线程状态,并恢复另一个线程状态
-
对应Java中的程序计数器概念,记录下一条JVM指令的执行地址,为线程私有
-
线程状态信息包括
- 程序计数器
- 每个栈桢的信息
- 如:局部变量、操作数栈、返回地址等
-
频繁发生线程上下文切换影响性能
-
常见方法
- start() // 启动新线程,让线程进入就绪状态
- run() //线程被运行后执行的方法
- join() // 等待线程运行结束
- join(long n) // 等待结束带超时时间
- getId() // 线程ID
- getName() // 线程名称
- setName(String name) //修改名称
- getPriority() // 获取线程执行的优先级
- setPriority(int i) // 设置优先级,1-10,值越大被CPU调度的几率越高
- getState() // 获取状态,一共6个
- isInterrupted() // 判断是否被打断,不会清除打断标记
- isAlive() // 判断线程是否存活,还没运行完毕
- interrupt() // 打断线程,将抛出异常,并清除打断标记(线程未在运行)
- interrupted() // 判断是否被打断,同时会清除打断标记
- currentThread() // 获取当前正在执行的线程
- sleep(long n) // 让线程休眠 n 毫秒,让出CPU时间片
- yield() // 提示线程调度器让出当前线程的CPU,一般用于测试或调试
start 与 run
- 直接调用run,则会在主线程中直接执行run,不会启动新的线程
- 调用 start 才会启动新线程,并通过线程间接执行run方法
sleep 与 yield
-
sleep会让线程状态从Running 进入 Timed Waiting,让线程睡眠,染出CPU的时间片给其他线程
-
yield 是 让线程 从Running进入Runnable,主动让出CPU对线程调度,一般用于调试
-
线程优先级
- 仅用于提示调度器优先调度该线程,但调度器可能忽略
- 仅在CPU比较繁忙时才有一定效果,CPU较空闲时几乎没有作用
join
- 用于等待指定线程执行完毕
- 可设置超时时间,超过时间后,将不再等待
interrupt
- 打断线程执行,并抛出异常
- 打断调用了sleep的线程,被清空其打断标记,打断状态为false
- 打断正在运行的线程,不会清空,打断状态为true
- 如果线程打断标记是true,此时调用LockSupport.park()暂停线程会失效
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
Sleeper.sleep(1);
}, "t1");
t1.start();
Sleeper.sleep(0.5);
// 打断t1线程
t1.interrupt();
log.info("打断线程t1后,t1的状态为 {}", t1.isInterrupted());
}
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at cn.com.ebidding.test.Sleeper.sleep(Sleeper.java:14)
at cn.com.ebidding.test.TestInterrupt.lambda$main$0(TestInterrupt.java:15)
at java.lang.Thread.run(Thread.java:748)
14:37:19.495 [main] INFO cn.com.ebidding.test.TestInterrupt - 打断线程t1后,t1的状态为 false
几个不推荐方法
- stop(停止)、suspend(挂起,暂停)、resume(恢复)
- 已过时,容易破坏同步代码块,最后造成死锁
主线程与守护线程
- Java进程默认需等待所有线程运行结束,进程才会结束
- 守护线程即其他非守护线程已结束后,自己会强制结束
- 设置:t1.setDaemon(true)
- 垃圾回收线程就是守护线程
- Tomcat中的Acceptor 与 Poller 也是,故当Tomcat 执行 shutdown后,不会等待他们处理完当前正在处理的请求
操作系统中的线程状态(五种)
- 初始:刚创建,还未与操作系统关联
- 可运行:与操作系统已关联,可由CPU调度
- 运行:已获取到了CPU时间片,正在运行中;CPU时间片用完,运行中将转换为可运行,其会导致线程上下文切换
- 阻塞:此时线程不会用到CPU,但会导致线程上下文切换,例如BIO读写文件场景;注意阻塞与可运行加以区分,阻塞状态的线程只要不唤醒,调度器一直不会考虑调度;
- 终止:已执行完毕生命周期接结束,不会再转换为其他状态
Java JVM中的线程状态(六种)
- New:刚创建,还未调用start
- Runnable:覆盖了操作系统中的可运行、运行、阻塞(IO读写)
- Blocked:阻塞状态的细分
- Waiting:阻塞状态的细分
- Timed_waiting:阻塞状态的细分
- Terminated:线程代码运行结束