创建线程
方式一:使用Thread,重写run方法
//method one
new Thread("t1"){
@Override
public void run() {
System.out.println("t1");
}
}.start();
方式二:使用Runable接口,并重写run方法
//method two
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("t2");
}
}, "t2").start();
//method three
new Thread(() -> System.out.println("t3")).start();
//method four
Runnable r = () -> System.out.println("t4");
new Thread(r,"t4").start();
方式三:使用FutureTask+callable
//method five
FutureTask<String> ft = new FutureTask<>(new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(3000);
return "t5";
}
});
new Thread(ft,"t5").start();
System.out.println(ft.get());//等待线程执行完毕后获取结果
Start 和 Run的区别
-
直接调用 run 是在主线程中执行了 run,没有启动新的线程
-
使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码
sleep 与 yield
sleep
-
调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
-
其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
-
睡眠结束后的线程未必会立刻得到执行
-
建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
yield
- 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
- 具体的实现依赖于操作系统的任务调度器
线程优先级
- 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它
- 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用
join 方法详解
static int r = 0;
public static void main(String[] args) throws InterruptedException {
test1();
}
private static void test1() throws InterruptedException {
log.debug("开始");
Thread t1 = new Thread(() -> {
log.debug("开始");
sleep(1);
log.debug("结束");
r = 10;
});
t1.start();
log.debug("结果为:{}", r);
log.debug("结束");
}
分析
- 因为主线程和线程 t1 是并行执行的,t1 线程需要 1 秒之后才能算出 r=10
- 而主线程一开始就要打印 r 的结果,所以只能打印出 r=0
解决方法
- 用 sleep 理论上可以,但是不知道线程运行多久,无法明确Sleep时间。
- 用 join,加在 t1.start() 之后即可
- join会等待线程结束后再开始下一步工作。
interrupt 方法详解
- 打断 sleep,wait,join 的线程
- 这几个方法都会让线程进入阻塞状态,且线程的isInterrupt为false
- 打断 sleep 的线程, 会清空打断状态,以 sleep 为例
- 打断正常运行的线程, 不会清空打断状态
两阶段模型
public static void main(String[] args) throws InterruptedException {
Thread t1 =new Thread(() ->{
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
System.out.println("t1");
if(Thread.currentThread().isInterrupted()){
System.out.println("t1 is interrupted");
break;
}
}
},"t1");
t1.start();
Thread.sleep(5000);
t1.interrupt();
}
主线程与守护线程
-
默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守
-
护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。
注意
- 垃圾回收器线程就是一种守护线程
- Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等待它们处理完当
线程的状态
五种状态
这是从 操作系统 层面来描述的
-
【初始状态】 仅是在语言层面创建了线程对象,还未与操作系统线程关联
-
【可运行状态】(就绪状态) 指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行
-
【运行状态】 指获取了 CPU 时间片运行中的状态,当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换
-
【阻塞状态】 如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入【阻塞状态】等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】 与【可运行状态】的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考虑调度它们
-
【终止状态】 表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态
-
六种状态
这是从 Java API 层面来描述的, 根据 Thread.State 枚举,分为六种状态
- NEW 线程刚被创建,但是还没有调用 start() 方法
- RUNNABLE 当调用了 start() 方法之后,注意,Java API 层面的 RUNNABLE 状态涵盖了 操作系统 层面的【可运行状态】、【运行状态】和【阻塞状态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为是可运行)BLOCKED , WAITING , TIMED_WAITING 都是 Java API 层面对【阻塞状态】的细分
- TERMINATED 当线程代码运行结束
-