创建线程的三种方式、线程运行原理、常见方法、线程状态

时间: 2023-07-11 admin 互联网

创建线程的三种方式、线程运行原理、常见方法、线程状态

创建线程的三种方式、线程运行原理、常见方法、线程状态

文章目录

  • 1.创建线程的三种方式
    • 1.1 继承Thread类并重写run方法
    • 1.2 使用Runnable配合Thread
    • 1.3 通过Callable和FutureTask创建线程
  • 2.Runnable和Thread的区别
  • 3.Thread类源代码剖析
  • 4.线程运行原理
    • 4.1 栈与栈帧
    • 4.2 线程上下文切换(Thread Context Switch)
  • 5.Thread的常见方法
    • 5.1 调用start 与 run方法的区别
    • 5.2 sleep 与 yield
    • 5.3 线程优先级
    • 5.4 join方法详解
    • 5.5 interrupt 方法详解
    • 5.6 设计模式-两阶段终止模式
    • 5.7 interrupt 打断 park
    • 5.8 不推荐使用的方法
    • 5.9 主线程与守护线程
    • 5.10 sleep、yield、wait、join 对比
  • 6.线程状态
    • 6.1 操作系统-五种状态
    • 6.2 线程-六种状态
  • 7.应用之统筹规划

1.创建线程的三种方式

1. 继承Thread类并重写run方法。
2. 实现Runnable接口,然后实现其run方法。
3. 通过Callable和Future创建线程。

将我们希望线程执行的代码放到 run 方法中,然后通过 start 方法来启动线程,start 方法首先为线程的执行准备 好系统资源,然后再去调用run方法。

1.1 继承Thread类并重写run方法

  • 定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体(线程体)。
  • 创建Thread子类的实例,即创建了线程对象。
  • 调用线程对象的start()方法来启动该线程。
  • Java不允许多继承。
/*** @ClassName MyThread* @author: shouanzh* @Description 继承Thread类创建线程类 (extends)* @date 2022/3/2 20:26*/
@Slf4j
public class MyThread extends Thread{// 通过构造函数传递参数private String name;public MyThread(String name) {this.name = name;}@Overridepublic void run() {// 具体任务逻辑System.out.println("继承Thread类创建线程类...");log.info("继承Thread类创建线程类...");}
}/*** @ClassName CreateThread* @author: shouanzh* @Description 测试* @date 2022/3/2 20:29*/
public class CreateThread {public static void main(String[] args) {MyThread myThread = new MyThread("T1");myThread.setName("T1");MyThread myThread2 = new MyThread("T2");myThread.setName("T2");// 启动线程myThread.start();myThread2.start();}}

1.2 使用Runnable配合Thread

  • 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
  • 创建 Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
  • 调用线程对象的start()方法来启动该线程。
  • Runnable接口支持多继承
/*** @ClassName RunnableThreadTest* @author: shouanzh* @Description 使用Runnable配合Thread* @date 2022/3/2 20:38*/
@Slf4j
public class RunnableThreadTest implements Runnable{// 通过变量和方法传递数据private String name;public void setName(String name) {this.name = name;}@Overridepublic void run() {// 创建线程任务System.out.println("继承Thread类创建线程类...");log.info("继承Thread类创建线程类...");}
}/*** @ClassName CreateThread* @author: shouanzh* @Description 测试* @date 2022/3/2 20:29*/
public class CreateThread {public static void main(String[] args) {RunnableThreadTest runnableThread = new RunnableThreadTest();// Runnable可以实现多个相同的程序代码的线程去共享同一个资源Thread  thread = new Thread(runnableThread);Thread  thread2 = new Thread(runnableThread);thread.start();thread2.start();}}

1.3 通过Callable和FutureTask创建线程

  • 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
  • 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。(FutureTask是一个包装器,它通过接受Callable来创建,它同时实现了Future和Runnable接口。)
  • 使用FutureTask对象作为Thread对象的target创建并启动新线程。
  • 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
/*** @ClassName CallableThreadTest* @author: shouanzh* @Description 通过Callable和FutureTask创建线程* @date 2022/3/2 20:46*/
public class CallableThreadTest implements Callable<String> {@Overridepublic String call() throws Exception {return "hello word";}
}*** @ClassName CreateThread* @author: shouanzh* @Description 测试* @date 2022/3/2 20:29*/
public class CreateThread {public static void main(String[] args) throws ExecutionException, InterruptedException {CallableThreadTest callableThread = new CallableThreadTest();FutureTask<String> futureTask = new FutureTask<>(callableThread);Thread thread = new Thread(futureTask,"T1");thread.start();// 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值String result = futureTask.get();System.out.println(result);}}

2.Runnable和Thread的区别

Runnable与Thread,前者的实现方式是实现其接口即可,后者的实现方式是继承其类。两者实现方式带来最明显的区别就是,由于Java不允许多继承,因此实现了Runnable接口可以再继承其他类,但是Thread明显不可以。

Runnable可以实现多个相同的程序代码的线程去共享同一个资源,而Thread不可以?

以经典的卖票案列:总共只有5张动车票了,有2个窗口在卖。

1. Runnable方式:

/*** @ClassName RunnableThreadTest* @author: shouanzh* @Description 使用Runnable配合Thread* @date 2022/3/2 20:38*/
@Slf4j
public class RunnableThreadTest implements Runnable {private int ticket = 5;@Overridepublic void run() {while (true) {System.out.println("Runnable ticket = " + ticket--);if (ticket < 0) {break;}}}
}/*** @ClassName CreateThread* @author: shouanzh* @Description 测试* @date 2022/3/2 20:29*/
public class CreateThread {public static void main(String[] args) throws ExecutionException, InterruptedException {RunnableThreadTest runnableThread = new RunnableThreadTest();// Runnable可以实现多个相同的程序代码的线程去共享同一个资源Thread  thread = new Thread(runnableThread);Thread  thread2 = new Thread(runnableThread);thread.start();thread2.start();}}

运行结果:

Runnable ticket = 5
Runnable ticket = 3
Runnable ticket = 2
Runnable ticket = 1
Runnable ticket = 0
Runnable ticket = 4Process finished with exit code 0

一个任务可以启动多个线程,通过Runnable方式实现的线程,实际是开辟一个线程,将任务传递进去,由此线程执行。可以实例化多个 Thread对象,将同一任务传递进去,也就是一个任务可以启动多个线程来执行它。这些线程执行的是同一个任务,所以他们的资源是共享。

2. Thread方式:

*** @ClassName MyThread* @author: shouanzh* @Description 继承Thread类创建线程类 (extends)* @date 2022/3/2 20:26*/
@Slf4j
public class MyThread extends Thread{private int ticket = 5;@Overridepublic void run() {while (true) {System.out.println("Runnable ticket = " + ticket--);if (ticket < 0) {break;}}}
}

测试:
方式一:

因为一个线程只能启动一次,通过Thread实现线程时,线程和线程所要执行的任务是捆绑在一起的。也就使得一个任务只能启动一个线程,不同的线程执行的任务是不相同的,所以没有必要,也不能让两个线程共享彼此任务中的资源。

/*** @ClassName CreateThread* @author: shouanzh* @Description 测试* @date 2022/3/2 20:29*/
public class CreateThread {public static void main(String[] args) throws ExecutionException, InterruptedException {// 每个线程都独立,不共享资源,每个线程都卖出了5张票,总共卖出了10张。MyThread myThread1 = new MyThread();MyThread myThread2 = new MyThread();myThread1.start();myThread2.start();}}

运行结果

Runnable ticket = 5
Runnable ticket = 4
Runnable ticket = 3
Runnable ticket = 2
Runnable ticket = 1
Runnable ticket = 0
Runnable ticket = 5
Runnable ticket = 4
Runnable ticket = 3
Runnable ticket = 2
Runnable ticket = 1
Runnable ticket = 0Process finished with exit code 0

方式二:

/*** @ClassName CreateThread* @author: shouanzh* @Description 测试* @date 2022/3/2 20:29*/
public class CreateThread {public static void main(String[] args) throws ExecutionException, InterruptedException {MyThread myThread = new MyThread();Thread myThread2 = new Thread(myThread);Thread myThread1 = new Thread(myThread);myThread1.start();myThread2.start();}
}

运行结果

Runnable ticket = 5
Runnable ticket = 3
Runnable ticket = 2
Runnable ticket = 1
Runnable ticket = 0
Runnable ticket = 4Process finished with exit code 0

其中方式一创建了两个MyThread对象,每个对象都有自己的ticket成员变量,当然会多卖。
方式二只创建了一个Thread对象,效果和Runnable一样。
实现Runable只是方便资源共享。当然继承Thrad也可以资源共享。

3.Thread类源代码剖析

  • Thread 类也实现了 Runnable接口,因此实现了Runnable 接口中的run方法。

  • 构造方法


  • 当使用第一种方式来生成线程对象时,我们需要重写 run 方法,因为 Thread 类的run 方法此时什么事情也不做。

  • 当使用第二种方式来生成线程对象时,我们需要实现 Runnable 接口的run 方法,然后使用 new Thread(new MyThread()) MyThread 已经实现了Runnable接口,来生成线程对象,这时的线程对象的run方法 会调用 MyThread 类的run方法,这样我们自己编写的run 方法就执行了。

4.线程运行原理

4.1 栈与栈帧

我们都知道JVM中由堆,栈,方法区所组成,其中栈内存是给谁用的呢?其实就是线程,每个线程启动 后,虚拟机就会为其分配一块栈内存。

  • 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
  • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法


每个线程下的栈都是私有的,因此每个线程都有自己各自的栈,并且每个栈里面都有很多栈帧,栈帧的大小主要由局部变量表 和 操作数栈决定的

栈帧debug

/*** @ClassName TestFrame* @author: shouanzh* @Description TestFrame* @date 2022/3/2 23:28*/
public class TestFrame {public static void main(String[] args) {method1(10);}public static void method1(int x) {int y = x + 1;Object object = method2();System.out.println(object);}public static Object method2() {Object object = new Object();return object;}
}


method2()执行完出栈

method1()执行完出栈

结束整个代码的运行

4.2 线程上下文切换(Thread Context Switch)

因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程

  • 线程的 cpu 时间片用完
  • 垃圾回收(STW)
  • 有更高优先级的线程需要运行
  • 线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法

当Thread Context Switch发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的

  • 线程的状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等
  • Context Switch 频繁发生会影响性能

5.Thread的常见方法


5.1 调用start 与 run方法的区别

  • 直接调用 run() 是在主线程中执行了 run(),没有启动新的线程
  • 使用 start() 是启动新的线程,通过新的线程间接执行 run()方法中的代码
@Slf4j
public class CreateThread {public static void main(String[] args) {Thread thread = new Thread("T1"){@Overridepublic void run() {log.info("T1...");}};// 使用 start() 是启动新的线程,通过新的线程间接执行 run()方法中的代码thread.start(); // 2022-03-03 20:49:49 [T1] - T1...// 直接调用 run() 是在主线程中执行了 run(),没有启动新的线程thread.run(); // 2022-03-03 20:49:19 [main] - T1...}}

5.2 sleep 与 yield


sleep的状态

@Slf4j
public class CreateThread {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread("T1"){@SneakyThrows@Overridepublic void run() {Thread.sleep(2000);log.info("T1...");}};thread.start();// T1的线程先执行了,这时T1的状态为RUNNABLElog.info("T1的State:{}",thread.getState()); // T1的State:RUNNABLE// 主线程休眠Thread.sleep(500);log.info("T1的State:{}",thread.getState()); // T1的State:TIMED_WAITING}}


sleep的打断

@Slf4j
public class CreateThread {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread("T1"){@Overridepublic void run() {try {log.info("T1 enter sleep...");Thread.sleep(2000);} catch (InterruptedException e) {log.info("T1 wake up ...");e.printStackTrace();}}};thread.start();Thread.sleep(1000);log.info("interrupt...");thread.interrupt();}}


TimeUnit

TimeUnit.SECONDS.sleep(1);

yield使cpu调用其它线程,但是cpu可能会再分配时间片给该线程;
而sleep需要等过了休眠时间之后才有可能被分配cpu时间片

5.3 线程优先级

  • 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它,
  • 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用
thread.setPriority(Thread.MAX_PRIORITY);

5.4 join方法详解

等待调用join() 的线程结束。

@Slf4j
public class CreateThread {static int r = 0;public static void main(String[] args) {log.debug("开始");Thread t1 = new Thread(() -> {log.debug("开始");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}log.debug("结束");r = 10;},"t1");t1.start();t1.join();// 这里如果不加t1.join(), 此时主线程不会等待t1线程给r赋值, 主线程直接就输出r=0结束了// 如果加上t1.join(), 此时主线程会等待到t1线程执行完才会继续执行.(同步), 此时r=10;log.debug("结果为:{}", r);log.debug("结束");}}

如果加上t1.join(), 此时主线程会等待到t1线程执行完才会继续执行.(同步), 此时r=10;

这里如果不加t1.join(), 此时主线程不会等待t1线程给r赋值, 主线程直接就输出r=0结束了

图解

下图, 因为开辟了t1线程. 此时程序中有两个线程; main线程和t1线程; 此时在main线程中调用t1.join, 所以main线程只能阻塞等待t1线程执行完. t1线程在1s后将r=10, t1线程执行完, 此时main线程才会接着执行


有时效的join(long n)

@Slf4j
public class CreateThread {static int r = 0;public static void main(String[] args) throws InterruptedException {log.debug("开始");Thread t1 = new Thread(() -> {log.debug("开始");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}log.debug("结束");r = 10;},"t1");t1.start();t1.join(1000);// 这里如果不加t1.join(), 此时主线程不会等待t1线程给r赋值, 主线程直接就输出r=0结束了// 如果加上t1.join(), 此时主线程会等待到t1线程执行完才会继续执行.(同步), 此时r=10;log.debug("结果为:{}", r);log.debug("结束");}}

5.5 interrupt 方法详解

该方法用于打断 sleep,wait,join的线程, 在阻塞期间cpu不会分配给时间片

  • 如果是打断因sleep、 wait 、join方法而被阻塞的线程,会将打断标记置为false
  • 如果一个线程在在运行中被打断,打断标记会被置为true

打断sleep的线程,会清空打断状态,以 sleep为例

@Slf4j
public class CreateThread {public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {try {log.info("sleep...");Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}},"t1");t1.start();Thread.sleep(500);log.debug("111是否被打断?{}",t1.isInterrupted());t1.interrupt();log.debug("222是否被打断?{}",t1.isInterrupted());Thread.sleep(500);log.debug("222是否被打断?{}",t1.isInterrupted());log.debug("主线程");}
}

打断正常运行的线程
如果一个线程在在运行中被打断,打断标记会被置为true

@Slf4j
public class CreateThread {public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {while(true) {boolean interrupted = Thread.currentThread().isInterrupted();if(interrupted) {System.out.println("被打断了, 退出循环");break;}}}, "t1");t1.start();Thread.sleep(1000);System.out.println("interrupt");t1.interrupt();System.out.println("打断标记为: "+t1.isInterrupted());}}

5.6 设计模式-两阶段终止模式



代码举例:

public class Test7 {public static void main(String[] args) throws InterruptedException {Monitor monitor = new Monitor();monitor.start();Thread.sleep(3500);monitor.stop();}
}class Monitor {Thread monitor;/*** 启动监控器线程*/public void start() {//设置线控器线程,用于监控线程状态monitor = new Thread() {@Overridepublic void run() {//开始不停的监控while (true) {//判断当前线程是否被打断了if(Thread.currentThread().isInterrupted()) {System.out.println("处理后续任务");//终止线程执行break;}System.out.println("监控器运行中...");try {//线程休眠Thread.sleep(1000); // 情况1 阻塞打断System.out.println("记录日志"); // 情况二:正常运行线程被打断} catch (InterruptedException e) {e.printStackTrace();//如果是在休眠的时候被打断,不会将打断标记设置为true,这时要重新设置打断标记Thread.currentThread().interrupt();}}}};monitor.start();}/*** 	用于停止监控器线程*/public void stop() {//打断线程monitor.interrupt();}
}

5.7 interrupt 打断 park

@Slf4j
public class CreateThread {public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {log.info("park");LockSupport.park();log.info("uppark");log.info("打断状态{}",Thread.currentThread().isInterrupted());// log.info("打断状态{}",Thread.interrupted());// LockSupport.park(); // 打断状态为 true 时 无效 Thread.interrupted() 清除打断标记log.info("uppark");}, "t1");t1.start();Thread.sleep(1000);System.out.println("interrupt");t1.interrupt();}
}

5.8 不推荐使用的方法

5.9 主线程与守护线程

默认情况下, Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其 它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。

代码举例

@Slf4j
public class CreateThread {public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {while (true) {// 判断当前线程是否被打断了if (Thread.currentThread().isInterrupted()) {System.out.println("处理后续任务");//终止线程执行break;}}log.info("结束");}, "Daemon");t1.setDaemon(true);t1.start();Thread.sleep(1000);log.info("结束");}}

  • 垃圾回收器线程就是一种守护线程
  • Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等待它们处理完当前请求

5.10 sleep、yield、wait、join 对比

  • sleep,join,yield,interrupted是Thread类中的方法
  • wait/notify是object中的方法
  • sleep 不释放锁、释放cpu
  • join 释放锁、抢占cpu
  • yiled 不释放锁、释放cpu
  • wait 释放锁、释放cpu

6.线程状态

6.1 操作系统-五种状态

从操作系统方面描述


6.2 线程-六种状态

这是从 Java API 层面来描述的
根据Thread.State 枚举,分为六种状态


代码举例:

@Slf4j(topic = "c.TestState")
public class TestState {public static void main(String[] args) {// new 状态 没有调用start()Thread t1 = new Thread("t1") {@Overridepublic void run() {log.debug("running...");}};// runnable 状态Thread t2 = new Thread("t2") {@Overridepublic void run() {while(true) {}}};t2.start();// TERMINATEDThread t3 = new Thread("t3") {@Overridepublic void run() {log.debug("running...");}};t3.start();// timed_waiting 显示阻塞状态Thread t4 = new Thread("t4") {@Overridepublic void run() {synchronized (TestState.class) {try {Thread.sleep(1000000);} catch (InterruptedException e) {e.printStackTrace();}}}};t4.start();// waiting 状态Thread t5 = new Thread("t5") {@Overridepublic void run() {try {// 等待t2结束。一直等待t2.join();} catch (InterruptedException e) {e.printStackTrace();}}};t5.start();// blocked 状态Thread t6 = new Thread("t6") {@Overridepublic void run() {synchronized (TestState.class) {try {Thread.sleep(1000000);} catch (InterruptedException e) {e.printStackTrace();}}}};t6.start();try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}log.debug("t1 state {}", t1.getState());log.debug("t2 state {}", t2.getState());log.debug("t3 state {}", t3.getState());log.debug("t4 state {}", t4.getState());log.debug("t5 state {}", t5.getState());log.debug("t6 state {}", t6.getState());}
}

7.应用之统筹规划

代码实现

/*** @ClassName Test5* @author: shouanzh* @Description 应用之统筹* @date 2022/3/4 21:50*/
@Slf4j(topic = "c.Test")
public class Test5 {public static void main(String[] args) {//  洗水壶 烧水 串行Thread t1 = new Thread(() -> {log.debug("洗水壶");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}log.debug("烧水");try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}},"王昊");//  洗茶壶 洗茶杯 拿茶叶Thread t2 = new Thread(() -> {log.debug("洗茶壶");log.debug("洗茶杯");log.debug("拿茶叶");try {// 等待t1结束t1.join();} catch (InterruptedException e) {e.printStackTrace();}// t1结束log.debug("泡茶");},"小赵");t1.start();t2.start();}
}