目录
🥇一、创建线程
🎒二、Thread 的常用属性
📒1.启动一个线程————start()
📕2.中断一个线程
📗3.等待一个线程-join()
📘4.获取当前线程引用
📙5.休眠当前线程
📒6.线程的状态
📖java 在进行多线程编程时,java标准库提供了一个类 Thread 能够表示一个线程
1️⃣使用继承 Thread,重写 run 的方法来创建线程 ————使用Thread的run描述线程入口
class MyThread extends Thread {@Overridepublic void run() {System.out.println("hello world");}
}public class ThreadDemo1 {public static void main(String[] args) {Thread t = new MyThread();//创建一个MyThread实例,t的引用实际上指向子类的实例t.start();//启动线程,在进程中搞了另外一个流水线,新的流水线开始并发的执行另外一个逻辑了}
}
点击运行程序,其实是idea对应的进程,创建了一个新的java进程,这个java进程来执行咱们自己写的代码
这个java进程中有两个线程,一个是main,一个是t上述代码涉及到两个线程:
1.main方法所对应的线程(一个进程中至少得有一个线程),也可以称为主线程
2.通过 t.start 创建新的线程
多线程class MyThread extends Thread {@Overridepublic void run() {while (true) {System.out.println("hello t");}}
}public class ThreadDemo1 {public static void main(String[] args) {Thread t = new MyThread();//创建一个MyThread实例,t的引用实际上指向子类的实例t.start();//启动线程,在进程中搞了另外一个流水线,新的流水线开始并发的执行另外一个逻辑了//点击运行程序,其实是idea对应的进程,创建了一个新的java进程,这个java进程来执行咱们自己写的代码//这个java进程中有两个线程,一个是main,一个是t//上述代码涉及到两个线程//1.main方法所对应的线程(一个进程中至少得有一个线程),也可以称为主线程//2.通过 t.start 创建新的线程while (true) {System.out.println("hello main");}}
}
此时可以看到的效果是,hello t 和 hello main 都能打印出来
此时 t.start(); 另外启动了一个执行流,新的执行流(新的线程)来执行 System.out.println("hello t");
单线程class MyThread extends Thread {@Overridepublic void run() {while (true) {System.out.println("hello t");}}
}public class ThreadDemo1 {public static void main(String[] args) {Thread t = new MyThread();//创建一个MyThread实例,t的引用实际上指向子类的实例t.run();while (true) {System.out.println("hello main");}}
}
run 不会创建新的线程,run是在 main 线程中执行的,所以会一直输出hello t死循环
此时,代码没有创建其他线程,两个死循环都在同一个线程中的,执行到底一个死循环,代码就出不来了,第二个循环进不去了
class MyThread extends Thread {@Overridepublic void run() {while (true) {System.out.println("hello t");try {sleep(1000);//sleep是Thread 的静态方法,参数单位是ms} catch (InterruptedException e) {//打断/中断————意思就是sleep睡眠过程中,还没到点就提前唤醒了e.printStackTrace();}}}//run 不是一个随便的方法,是重写了父类的方法//这种重写一般就是功能的扩展,一般这样的重写方法是不需要咋们自己手动调用,已经有其他代码来调用了//run 可以称为是一个特殊的方法,线程的入口
}public class ThreadDemo1 {public static void main(String[] args) throws InterruptedException {Thread t = new MyThread();//创建一个MyThread实例,t的引用实际上指向子类的实例t.start();//启动线程,在进程中搞了另外一个流水线,新的流水线开始并发的执行另外一个逻辑了while (true) {System.out.println("hello main");try {sleep(1000);//sleep是Thread 的静态方法,参数单位是ms} catch (InterruptedException e) {//打断/中断————意思就是sleep睡眠过程中,还没到点就提前唤醒了e.printStackTrace();}}}
}
此时打印是交替出现,但并不是真正的交替,每一秒过后,是先打印main还时先打印t是不确定的
多线程在CPU上调度执行的顺序是不确定(随机的)
2️⃣使用实现 Runnable,重写 run 的方法来创建线程 ————使用 Runnable interface 来描述线程入口
class MyRunnable implements Runnable {//Runnable字面意思是可运行的,使用Runnable 描述一个具体的任务,通过 run 方法来描述@Overridepublic void run() {while (true) {System.out.println("hello t");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}//使用 Runnable interface 来描述线程入口
}public class ThreadDemo2 {public static void main(String[] args) {MyRunnable runnable = new MyRunnable();Thread t = new Thread(runnable);t.start();while (true) {System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
这两种方法没有本质区别,第一种是使用Thread的run描述线程入口,第二种是使用 Runnable interface 来描述线程入口
3️⃣继承 Thread ,使用匿名内部类
public class ThreadDemo3 {public static void main(String[] args) {Thread t = new Thread() {@Overridepublic void run() {while (true) {System.out.println("hello t");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}};t.start();while (true) {System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
4️⃣继承 Runnable,使用匿名内部类(定义在类里边的类)
public class ThreadDemo4 {public static void main(String[] args) {Thread t = new Thread(new Runnable() {@Overridepublic void run() {while (true) {System.out.println("hello t");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}});t.start();while (true) {System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
5️⃣使用 lambda 表达式
lambda 表达式,本质就是一个匿名函数(没有名字的函数,这种一般都是一次性的),java里边函数(方法)是无法脱离类的,在java里边lambda就相当于是一个例外
lambda 表达式的基本写法: (放参数,如果是一个参数,()可以省略) -> { 放函数体,如果只有一行代码,也可以省略{ } }
public class ThreadDemo5 {public static void main(String[] args) {Thread t = new Thread( () -> {while (true) {System.out.println("hello t");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t.start();while (true) {System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
打印:hello main hello t hello main hello t hello t hello main hello t hello main 循环
start 方法:真正从系统这里,创建一个线程,新的线程将会执行 run 方法
run 方法:表示了线程的入口方法是啥(线程启动起来,要执行哪些逻辑)(不是让程序员调用的,要交给系统去自动调用)
给线程中设定一个结束标志位:
public static void main(String[] args) {Thread t = new Thread(() -> {while (true) {System.out.println("hello t");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t.start();}
此时这个代码为死循环,导致入口方法无法执行完毕,自然不能结束线程==========》 把循环条件用一个变量来控制
🌈中断这里就是字面意思,就是让一个线程停下来,线程的终止。本质上来说,让一个线程终止办法就一种,让该线程的入口方法执行完毕:
public class ThreadDemo1 {public static boolean isQuit = false;public static void main(String[] args) {Thread t = new Thread(() -> {while (!isQuit) {System.out.println("hello t");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("t 线程终止");});t.start();//在主线中,修改 isQuittry {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}isQuit = true;}
}//输出:
//hello t
//hello t
//hello t
//t 线程终止
🙈如果把 isQuit 从成员变量改到局部变量 main,改代码是否能正常运行?
🙉不能————变量捕获
lambda 表达式能否访问外面的局部变量?可以————变量捕获语法规则
java要求变量捕获,捕获的是变量必须是 final 或者 “实际final”(变量没有用 final 修饰,但是代码中并没有做出修改)
没有final,但是最后修改isQuit,所以不可以改到局部变量 main
但是可以直接变成成员变量————lambda访问成员变量不受变量捕获的规则限制
当前我们使用自己创建的变量来控制循环;Thread 类内置了一个标志位,让我们更方便地实现上述效果
多线程代码顺序不是“从上到下”,而是每个线程都是独立执行的
执行到 start 方法,代码兵分两步:主线程继续往下执行,新线程进入到 run 方法执行labmda 表达式的内容就是新线程 run 方法的内容
public class ThreadDemo2 {public static void main(String[] args) {Thread t = new Thread(() -> {//currentThread 是获取当前线程实例 此处currentThread 得到的对象就是t// isInterrupted 就是t对象自带的一个标志位————初始标志位位false while (!Thread.currentThread().isInterrupted()) {System.out.println("hello t");try { ============> run 方法(并发执行)Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();//注意!!! 一个非常重要的问题!!! 当 sleep 被唤醒的时候,sleep会自动的把 isInterrupted 标志位给清空 (true -> false)//break;//如果需要结束循环,需要在catch中加入break//为什么 sleep 要清空标志位呢?//目的就是为了让线程自身能够对于线程何时结束,有一个更明确的控制}}});t.start();//执行到 start 方法,代码兵分两步:主线程继续往下执行,新线程进入到 run 方法执行try {Thread.sleep(3000);} catch (InterruptedException e) { ============> 主线程(并发执行)e.printStackTrace();}//把 t 内部的标志位给设置成 truet.interrupt();//interrupt方法的作用://1.设置标志位为true,2.如果该线程正在阻塞(比如在执行sleep),此时就会把阻塞状态唤醒,通过抛出异常的方法让sleep立即结束//为什么 sleep 要清空标志位呢?//目的就是为了让线程自身能够对于线程何时结束,有一个更明确的控制//当前,interrupt 方法,效果不是让线程立即结束,而是告诉他,你该结束了,至于他是否真的要结束,//立即结束还是等会结束,都是代码来灵活控制的//interrupt 只是通知,而不是“命令”}}//输出结果:
/*hello t
hello t
hello t
java.lang.InterruptedException: sleep interruptedat java.lang.Thread.sleep(Native Method) =====》 e.printStackTrace();//打印当前位置的调用栈at Thread.ThreadDemo2.lambda$main$0(ThreadDemo2.java:19)at java.lang.Thread.run(Thread.java:748)
hello t
hello t
hello t
hello t*/
此时发现3s时间到,调用 t.interrupt 方法的时候,线程并没有真的就结束了,而是打印了异常信息,又继续执行了
interrupt方法的作用:
1.设置标志位为true,2.如果该线程正在阻塞(比如在执行sleep),此时就会把阻塞状态唤醒,通过抛出异常的方法让sleep立即结束注意!!! 一个非常重要的问题!!! 当 sleep 被唤醒的时候,sleep会自动的把 isInterrupted 标志位给清空 (true -> false)
这里只有第一次抛异常,第二次就不会抛异常:
首先 isInterrupted 的初始标志位为 false ======> 如果 sleep 执行的时候看到这个标志位为 false, sleep 正常进行休眠操作【t.interrupt();】 如果当前标志位为 true ,sleep 无论是刚刚执行完是已经执行了一半,都会触发两件事 1.立即抛出异常 2.清空标志位为 false
再下次循环到 sleep ,由于当前标志位本身就是 false,就啥也不干了
🌈当前,interrupt 方法,效果不是让线程立即结束,而是告诉他,你该结束了,至于他是否真的要结束,立即结束还是等会结束,都是代码来灵活控制的interrupt 只是通知,而不是“命令”
1️⃣无视要求
Thread t = new Thread(() -> {//currentThread 是获取当前线程实例 此处currentThread 得到的对象就是t// isInterrupted 就是t对象自带的一个标志位while (!Thread.currentThread().isInterrupted()) {System.out.println("hello t");try { //============> run 方法(并发执行)Thread.sleep(1000);} catch (InterruptedException e) {//e.printStackTrace();//break;}}});
2️⃣立即执行
Thread t = new Thread(() -> {//currentThread 是获取当前线程实例 此处currentThread 得到的对象就是t// isInterrupted 就是t对象自带的一个标志位while (!Thread.currentThread().isInterrupted()) {System.out.println("hello t");try { //============> run 方法(并发执行)Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();break;}}});
3️⃣等会执行
Thread t = new Thread(() -> {//currentThread 是获取当前线程实例 此处currentThread 得到的对象就是t// isInterrupted 就是t对象自带的一个标志位while (!Thread.currentThread().isInterrupted()) {System.out.println("hello t");try { //============> run 方法(并发执行)Thread.sleep(1000);} catch (InterruptedException e) {//e.printStackTrace();try {Thread.sleep(2000);} catch (InterruptedException ex) {ex.printStackTrace();}//注意!!! 一个非常重要的问题!!! 当 sleep 被唤醒的时候,sleep会自动的把 isInterrupted 标志位给清空 (true -> false)break;//如果需要结束循环,需要在catch中加入break//这里只有第一次抛异常,第二次就不会抛异常://首先 isInterrupted 的初始标志位为 false======> 如果sleep执行的时候看到这个标志位为 false, sleep 正常进行休眠操作//t.interrupt();】 如果当前标志位为 true ,sleep 无论是刚刚执行完是已经执行了一半,都会触发两件事 //1.立即抛出异常 2.清空标志位为 false//为什么 sleep 要清空标志位呢?//目的就是为了让线程自身能够对于线程何时结束,有一个更明确的控制}}});
✨线程之间是并发执行的,操作系统对于线程的调度,是无序的,无法判定两个线程谁先执行结束,谁后执行结束
public static void main(String[] args) {Thread t = new Thread(() -> {System.out.println("hello t");});t.start();System.out.println("hello main");}
此时先执行 hello t 还是先执行 hello main 是无法知道的,并发执行的
这个代码实际执行的时候,大部分情况下都是先执行 hello main (因为线程创建也有开销),但是不排除特定情况下,主线程 hello main 没有立即执行到
👉有的时候就需要明确线程的结束顺序,可以使用 “线程等待” 来实现 ———— join 方法
public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {System.out.println("hello t");});t.start();t.join();System.out.println("hello main");}
🔎是在 main 线程中,调用 t.join ,意思就是让 mian 线程等待 t 先结束,再往下执行;别的线程不受影响
(如果是 t1 线程中,调用 t2.join, 就是让 t1 线程等待 t2 先结束)在 t.join 执行的时候,如果 t 线程还没结束呢,main 线程就会 “阻塞”(Blocking) 等待。即代码走到这一行就停下来,当前这个线程暂时不参与 cpu 的调度执行了
✅t.join();
1) main 线程调用 t.join 的时候,如果 t 还在运行,此时 main 线程阻塞,直到 t 执行完毕(t 的 run 执行完了),mian 才从阻塞中解除,才能继续执行
2) main 线程调用 t.join 的时候,如果 t 已经结束了,此时 join 不会阻塞,就会立即往下执行
public static Thread currentThread(); 返回当前线程对象的引用
public class ThreadDemo {public static void main(String[] args) {Thread thread = Thread.currentThread();System.out.println(thread.getName());}
}
public static void sleep(long millis) throws InterruptedException 休眠当前线程 millis毫秒
public static void sleep(long millis, int nanos) throws InterruptedException 可以更高精度的休眠
public class ThreadDemo {public static void main(String[] args) throws InterruptedException {System.out.println(System.currentTimeMillis());Thread.sleep(3 * 1000);System.out.println(System.currentTimeMillis());}
}
✨操作系统里的线程,自身是有一个状态的,但是 Java Thread 是对系统线程的封装,把这里的状态又进一步的精细化了
1️⃣NEW ———系统中的线程还没创建出来呢,只是有个 Thread 对象
public class ThreadDemo4 {public static void main(String[] args) {Thread t = new Thread(() -> {System.out.println("hello");});//在启动之前,获取线程状态————NEWSystem.out.println(t.getState());//NEWt.start(); }
}
2️⃣TERMINATED——系统中的线程已经执行完了, Thread 对象还在
public class ThreadDemo4 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {System.out.println("hello");});//在启动之前,获取线程状态————NEWSystem.out.println(t.getState());//NEWt.start();Thread.sleep(2000);System.out.println(t.getState());//TERMINATED}
}
3️⃣RUNNABLE——就绪状态(1.正在CPU上运行 2.准备好随时可以去CPU上运行)
public class ThreadDemo5 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {while (true) {//为了防止 hello 把线程状态冲没了,先注释掉//System.out.println("hello");}});//在启动之前,获取线程状态————NEWSystem.out.println(t.getState());//NEWt.start();Thread.sleep(2000);System.out.println(t.getState());//RUNNABLE}
}
4️⃣TIMED_WAITING———指定时间等待,sleep 方法
public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {while (true) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});//在启动之前,获取线程状态————NEWSystem.out.println(t.getState());//NEWt.start();Thread.sleep(2000);System.out.println(t.getState());//TIMED_WAITING}
5️⃣BLOCKED———表示等待锁出现的状态
6️⃣WAITING———使用 wait 方法出现的状态
上一篇:【云原生】K8S中的pod控制器