目录
一、线程创建
方式 1:继承 Thread 类
方式 2:实现 Runnable 接口
方式3:继承 Thread, 重写 run, 使用匿名内部类
方式4:实现 Runnable, 重写 run, 使用匿名内部类
方式5:使用 lambda 表达式
二、线程中断
1.通过共享的标记来进行沟通
2.调⽤ interrupt() 方法来通知
三、线程等待 join ()
示例
为什么此处不使用 sleep 代替 join
四、线程休眠 sleep ()
五、获取线程实例 currentThread ()
总结速记
一、线程创建
注意:
run()只是定义任务代码;start()才真正向操作系统申请创建线程,开启独立执行流。只调用 run 只是普通方法调用,不会产生新线程。
方式 1:继承 Thread 类
重写run()方法,把线程要执行的业务写在 run 中。
package thread; public class Demo1 { private static class MyThread extends Thread { @Override public void run() { while (true) { System.out.println("hello thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) { MyThread t1 = new MyThread(); t1.start(); while (true) { System.out.println("hello main"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }查看线程
方式 2:实现 Runnable 接口
解耦任务与线程对象,推荐使用。把任务放到 Runnable,交给 Thread 构造器。
package thread; public class Demo2 { private static class MyRunnable implements Runnable { @Override public void run() { while (true) { System.out.println("hello runnable"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) { MyRunnable runnable = new MyRunnable(); Thread t2 = new Thread(runnable); t2.start(); while (true) { System.out.println("hello main"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }方式3:继承 Thread, 重写 run, 使用匿名内部类
package thread; public class Demo3 { public static void main(String[] args) { // 匿名内部类创建线程 Thread t3 = new Thread() { @Override public void run() { while (true) { System.out.println("hello 匿名内部类"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }; t3.start(); while (true) { System.out.println("hello main"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }方式4:实现 Runnable, 重写 run, 使用匿名内部类
package thread; public class Demo4 { public static void main(String[] args) { // 匿名内部类:实现Runnable接口,重写run方法 Runnable runnable = new Runnable() { @Override public void run() { while (true) { System.out.println("hello 实现Runnable匿名内部类"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }; // 把runnable对象传入Thread构造方法,创建线程 Thread t4 = new Thread(runnable); t4.start(); // 主线程循环 while (true) { System.out.println("hello main"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }方式5:使用 lambda 表达式
package thread; public class Demo4 { public static void main(String[] args) { // lambda表达式 Thread t4 = new Thread(() -> { while (true) { System.out.println("hello lambda"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); t4.start(); while (true) { System.out.println("hello main"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }关键区分
run():普通成员方法,描述任务,不会新建线程。start():native 方法,调用操作系统 API,真正创建内核线程,执行 run。
二、线程中断
李四⼀旦进到⼯作状态, 他就会按照⾏动指南上的步骤去进行⼯作,不完成是不会结束的。但有时我们需要增加⼀些机制,例如老板突然来电话了, 说转账的对方是个骗子,需要赶紧停⽌转账,那张三该如何通知李四停⽌呢?这就涉及到我们的停⽌线程的方式了。
⽬前常⻅的有以下两种⽅式:
- 通过共享的标记来进行沟通
- 调⽤interrupt()方法来通知
1.通过共享的标记来进行沟通
定义布尔变量running作为退出标记,子线程循环判断running,主线程输入 0 后修改running=false,通知子线程退出。
package thread; import java.util.Scanner; public class Demo6 { //中断标记:static成员变量 private static boolean running=true; public static void main(String[] args) throws InterruptedException { Thread t = new Thread(() -> { while (running) { System.out.println("hello thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println("t 线程结束"); }); t.start(); Scanner sc = new Scanner(System.in); System.out.println("请输入整数,0表示让t线程终止"); int n = sc.nextInt(); if (n == 0) running = false; } }思考:如果把 running 定义在 main 方法内部,变成局部变量会发生什么?
原因解析:lambda 表达式捕获方法里的局部变量,要求变量必须是final或者事实 final(effectively final)。 事实 final:变量初始化之后,代码中不能再次对它重新赋值。
- 上面代码最后写了
running = false,对局部变量做了二次赋值,破坏了事实 final 规则,编译器直接拒绝。 - 而原始 Demo6 中
running是类的 static 成员变量,不属于 main 的局部变量,lambda 是访问类成员,不受 effectively final 限制,所以编译正常。
底层原理:lambda 捕获局部变量不是直接引用原变量,而是做值拷贝。如果允许两边同时修改原变量,拷贝副本和原始变量会出现数据不一致,Java 语言层面直接禁止该行为
2.调⽤interrupt()方法来通知
t.interrupt():给目标线程设置中断标记。如果线程正在sleep/wait/join阻塞,会立刻抛出 InterruptedException,并且自动清空中断标记。Thread.currentThread():在线程内部,拿到当前线程对象。
package thread; import java.util.Scanner; public class Demo7 { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(() -> { Thread cur=Thread.currentThread(); while (!cur.isInterrupted()) { System.out.println("hello thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println("t 线程结束"); }); t.start(); Scanner sc = new Scanner(System.in); System.out.println("请输入整数,0表示让t线程终止"); int n = sc.nextInt(); if (n == 0){ t.interrupt(); } } }思考:明明cur和外部变量t是同一个线程对象,为什么不直接在 lambda 里面写while(!t.isInterrupted())?
原因:
lambda 是构造方法的实参,解析 lambda 的时候,局部变量 t 还没有完成赋值。 lambda 内部想要访问t,属于变量尚未定义完成就引用自己,编译器禁止这种前向引用。
三、线程等待 join ()
线程之间是随机调度的,join()方法就是用来解决执行顺序不可控的问题:调用 join 的这个线程会进入阻塞,等待目标线程执行完毕之后,该线程才解除阻塞,继续执行后续代码。
例如,张三只有等李四转账成功,才决定是否存钱
示例
package thread; public class Demo9 { //创建一个线程,让这个线程计算1+2……1000=>500500 //主线程在这个计算线程执行完毕后,打印此处的结果 private static long result=0; public static void main(String[] args) throws InterruptedException { // Thread t1=new Thread( ()->{ for(int i=1;i<=1000_000;i++){ result+=i; } System.out.println("t 线程计算完毕"); }); t1.start(); t1.join();//等待t1线程结束 System.out.println(result); } }t1.start():仅仅是创建并启动线程,t1 线程和 main 主线程就进入并发状态。start 调用完,t1 不一定立刻就执行完。- 如果没有
t1.join():主线程不会等待 t1,主线程会直接执行System.out.println(result);。 此时 t1 子线程可能还没完成累加计算,主线程读到的result是一个不完整的中间值,结果错误。
join()作用:让调用 join 的主线程阻塞,等待目标线程 t1 全部执行完毕之后,主线程才继续往下执行打印逻辑。 保证打印result这行代码,一定发生在 t1 累加循环全部结束之后。
为什么此处不使用 sleep 代替 join
有人会想:既然要等子线程做完,那主线程直接写Thread.sleep(某个时间)休眠一段时间行不行?
- sleep 是等待固定时间,不知道子线程实际多久执行完:时间设短,子线程没做完就打印,结果错误;时间设长,白白浪费等待时间。
- join 是等待目标线程执行结束,线程干完活就立刻唤醒主线程,精准等待,无需估算时间。
四、线程休眠 sleep ()
sleep()是静态方法,让当前正在执行的线程休眠指定毫秒,让出 CPU,进入 TIMED_WAITING 状态。就绪->阻塞
注意:sleep 不会释放锁;休眠时间只是最低保证,受操作系统线程调度影响,实际休眠时间 ≥ 设置时间。
示例:
public class SleepDemo { public static void main(String[] args) { Thread t = new Thread(() -> { for (int i = 0; i < 3; i++) { System.out.println(i); try { Thread.sleep(1000); // 当前线程休眠1秒 } catch (InterruptedException e) { e.printStackTrace(); } } }); t.start(); } }sleep 会抛出受检异常
InterruptedException,必须捕获或向上抛出。
五、获取线程实例 currentThread ()
Thread.currentThread()静态方法,返回当前正在执行这行代码的线程对象引用。
示例:
package thread; public class Demo11 { public static void main(String[] args) { Thread t1=new Thread(){ @Override public void run(){ System.out.println(this.getName()); } }; t1.start(); } }错误:
正确:
package thread; public class Demo11 { public static void main(String[] args) { Thread t=new Thread(new Runnable(){ @Override public void run(){ //此处的run是Runnable的方法,this指向Runnable //Runnable中没有getName这样的方法 Thread cur=Thread.currentThread(); System.out.println(cur.getName()); } }); t.start(); } }继承 Thread 类,run 内部可以直接用
this代表线程对象;使用 Runnable 接口时,Runnable 对象的 this 不是线程对象,必须用
Thread.currentThread()拿到当前线程。lambda也一样
总结速记
- 创建线程:继承 Thread / 实现 Runnable;
start()启动,run()只是任务。 - 线程中断:
interrupt()设置标记;阻塞会抛异常;线程自己决定是否退出。 - 线程等待:
join(),等待别的线程执行完,控制执行顺序。 - 线程休眠:
Thread.sleep(ms),让当前线程睡一会,不释放锁。 - 获取线程实例:
Thread.currentThread(),拿到执行当前代码行的线程对象。