这样的代码,虽然也能打印hello thread,但是没有创建新的线程,而是直接在main方法所在的主线程中执行了run的逻辑
start方法,是调用系统api,真正在操作系统内部创建一个线程。这个新的线程会以run作为入口方法(执行run)的逻辑,run方法,不需要在代码中显式调用
此处遇见受查异常为什么不能用throws呢?
因为,如果给run方法声明加上throws,此时就意味着和父类Thread的run的方法声明不一致,就无法构成方法重写了。
package Thread;class MyThread extends Thread {@Overridepublic void run() {while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
}
public class Demo1 {
public static void main(String[] args) {System.out.println("你好!");// 创建并启动MyThread线程MyThread myThread = new MyThread();myThread.start();// myThread.run();while(true){System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
}
输出:
但是如果换成这样的代码:
package Thread;class MyThread extends Thread {@Overridepublic void run() {while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
}
public class Demo1 {
public static void main(String[] args) {System.out.println("你好!");// 创建并启动MyThread线程MyThread myThread = new MyThread();// myThread.start();myThread.run();while(true){System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
}
输出:
由此可见run方法并没有创建一个新的进程,从始至终只有一个主线程在运行,只有将run的方法执行完才能去打印hello main 但是由于这里的 run方法是一个死循环,所以一直没有打印hello main
通过观察我们可以发现这里的main和thread的打印,并不是严格交替的。由于两个打印,都加了sleep(1000)。当1000ms时间到的时候,这两个线程哪一个限制性,这个顺序是不确定的。
(操作系统调度线程的顺序是无需,不可预测的,随机的 )
可以通过Java jdk中的jconsole工具来观察进程
我的jdk-jconsole工具目录:C:\Program Files\Java\jdk-17\bin
折线图
这张图列出了当前进程中所有的线程。剩下的线程,都是JVM自带的,这些线程进行了一些背后的操作,比如负责垃圾回收,记录统计信息,记录一些调试信息。
如果将Thread.start()改成Thread.run()——>
我们会发现刚刚的Thread0没有了
一、Java中创建线程的方法
(1)创建子类,继承Thread,重写run,调用start
package Thread;class MyThread extends Thread {@Overridepublic void run() {while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
}
public class Demo1 {
public static void main(String[] args) {System.out.println("你好!");// 创建并启动MyThread线程MyThread myThread = new MyThread();// myThread.start();myThread.run();while(true){System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
}
(2)创建子类,实现Runnable,重写run,搭配Thread对象进行start
package Thread;class MyRunnable implements Runnable {@Overridepublic void run() {while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}
}public class Demo2 {public static void main(String[] args) {Runnable runnable = new MyRunnable();//runnable没有start方法,所以不能直接调用run方法,需要将runnable对象作为参数传递给Thread类的构造方法,然后调用start方法启动线程 Thread thread = new Thread(runnable);thread.start();}
}
使用Runnable描述线程要执行的任务是啥,真正去创建线程的工作,还是由Thread来负责。
interface是接口,不包含具体的方法的实现,只是提供一系列抽象方法,让子类去重写
*接口的的default一般不用,日常开发的时候,用到接口,都是希望全都提供抽象方法。除非是接口提供了一组方法,这一组方法中存在一些“公共的逻辑”,就可以在接口中搞default方法,使得这个方法表示公共逻辑,后面就可以在重写其他抽象方法的时候去调用了。
对于第一种写法(继承Thread)描述任务的时候,任务代码是写到Thread子类中的,意味着任务内容和Thread类的耦合程度更高(*写代码要高内聚/低耦合)
未来如果想把这个任务给别的“主体”去执行(执行一个任务,线程只是其中一个选项,进程也可以,协程亦可以...)
第二种写法,任务是写到Runnable中的,几乎不涉及到任何和“线程”相关的概念,任务内容和Thread概念的耦合是很小的,几乎没有
任务内容和Thread概念的耦合是很小的,几乎没有
后序就可以把这个任务交给进程、协程来执行
(*协程:近几年提出的概念,可以理解为“轻量级线程”)
(3)继承Thread,重写run,调用start 通过匿名内部类
package Thread;public class Demo3 {public static void main(String[] args) {Thread t = new Thread() {@Overridepublic void run() {while (true) {System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}};t.start();}}
此处就是创建了没有名字的匿名内部类,这个类就是Thread的子类,子类重写了run方法,同时也创建了子类的实例,通过t引用指向。
(4)实现Runnable,重写run,搭配Thread调用start 通过匿名内部类
package Thread;public class Demo4 {public static void main(String[] args) {Runnable runnable = new Runnable() {@Overridepublic void run() {while (true) {System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}};Thread t = new Thread(runnable);t.start();while(true){System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}
(5)更简单的写法,基于lambda表达式创建线程
lambda表达式,本质上是“匿名方法”
package Thread;public class Demo5 {public static void main(String[] args) {Thread t = new Thread(()->{while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}});t.start();while(true){System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}
二、后台线程与前台线程
后台线程:线程没运行完,进程可以结束(线程不能够组织进程结束)
前台线程:线程没有运行完,进程就不会结束(线程能够组织进程结束)
main线程和我们自己创建的线程都是前台线程,isDaemon = false;
剩下的线程就是后台线程,isDaemon = true;(守护线程)
通过setDaemon设置线程为后台(*必须在start之前set)
什么样子的线程是前台线程,什么样子的线程是后台线程呢?
如果一个线程做的任务很重要,这个任务必须要昨晚你,就应该设置这个线程为前台线程
如果一个线程做的任务无关紧要/周期性无期限执行(比如说JVM的垃圾回收线程),就应该设置为后台线程
package Thread;public class Demo5 {public static void main(String[] args) {Thread t = new Thread(()->{while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}});t.setDaemon(true);t.start();//把t线程设置为守护线程,当main线程结束时,t线程也会结束for(int i = 0;i<3;i++){System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}
三、判断线程是否存活
package Thread;public class Demo5 {public static void main(String[] args) {Thread t = new Thread(()->{while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}});System.out.println("t线程是否存活:"+t.isAlive());//false,t线程还没有启动,所以isAlive返回falset.setDaemon(true);t.start();//把t线程设置为守护线程,当main线程结束时,t线程也会结束System.out.println("t线程是否存活:"+t.isAlive());//true,t线程已经启动,所以isAlive返回truefor(int i = 0;i<3;i++){System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}System.out.println("t线程是否存活:"+t.isAlive());//false,t线程已经结束,所以isAlive返回false}}