Java 多线程2——Thread类常见属性 + 常见方法 + 线程状态


前言

本人是一个刚刚上路的IT新兵,菜鸟!分享一点自己的见解,如果有错误的地方欢迎各位大佬莅临指导,如果这篇文章可以帮助到你,劳请大家点赞转发支持一下!

多线程基础篇2来喽,本篇文章带你了解Thread类的常见属性与方法以及线程的六种状态。


一、Thread类

1️⃣Thread 类是 JVM 用来管理线程的一个类,换句话说,每个线程都有一个唯一的 Thread 对象与之关联。

2️⃣每个执行流,也需要有一个对象来描述,类似下图所示,而 Thread 类的对象
就是用来描述一个线程执行流的,JVM 会将这些 Thread 对象组织起来,用于线程调度,线程管理。

在这里插入图片描述


Thread 的常见构造方法

方法说明
Thread()创建线程对象
Thread(Runnable target)使用 Runnable 对象创建线程对象
Thread(String name)创建线程对象,并命名
Thread(Runnable target, String name)使用 Runnable 对象创建线程对象,并命名

Thread(Runnable target, String name)Thread(String name),只不过是创建新线程的方法不同,name的作用是相同的😉😉。

以上构造方法除了命名其他都在多线程基础1中都使用过了。
所以这里就介绍一下😲😲😲这个命名Thread(Runnable target, String name)

在你jdk的文件夹中有一个可以监视线程的程序


在这里插入图片描述


在这里插入图片描述


在这里插入图片描述


在这里插入图片描述

点开就可以在本地进程中看到正在执行的线程😏😏😏。


咱们运行下面这段代码,创建并运行一个新线程

public class ThreadDemo1 {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            while (true) {
                System.out.println("hello thread");
            }
        });
        thread.start();
    }
}

选中创建的新线程,点击连接。
在这里插入图片描述


如果点击连接后,遇到如下情况,直接点击不安全的连接即可。
🙁🙁🙁因为咱们只是用来测试的,并没有任何价值。
在这里插入图片描述


假如我们没有给创建的新线程命名,那么系统会自动为新线程命名Thread-0,Thread-1,按递增顺序一直排下去。

红框是线程名
蓝框中是选中的线程的一些信息
在这里插入图片描述


🤔🤔🤔此时大家一定很好奇,为什么没有看到主线程main线程???

原因是他在我们查询线程之前就已经执行完毕,结束了。
我们给主线程也加一个死循环看一下。

public class ThreadDemo1 {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            while (true) {
                System.out.println("hello thread");
            }
        });
        thread.start();
        while (true) {
            System.out.println("hello main");
        }
    }
}

在这里插入图片描述


public class ThreadDemo1 {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            while (true) {
                System.out.println("hello thread");
            }
        },"我的新线程");
        thread.start();
    }
}

这就是我们给线程起的新名字,主要是方便以后改多线程程序的bug😅😅😅,

多线程程序是不好调试的,因为你即使打断点了,其他线程仍在执行,就造成了与执行情况的误差😓😓😓。
在这里插入图片描述


Thread 的一些常见属性

属性属性说明获取方法
IDID 是线程的唯一标识,不同线程不会重复getId()
名称名称是各种调试工具用到getName()
状态状态表示线程当前所处的一个情况,下面我们会进一步说明getState()
优先级优先级高的线程理论上来说更容易被调度到getPriority()
是否为后台线程关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行isDaemon()
是否存活是否存活,即简单的理解,为 run 方法是否运行结束了isAlive()
是否被中断线程的中断问题,下面进一步说明isInterrupted()

    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            for (int i = 0; i < 10; i++) {
                                    // 获取当前线程引用,文章后面会讲解
                try {
                    System.out.println(Thread.currentThread().getName() + "仍存活");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + "将要死去");
        },"Thread");
        System.out.println(thread.getName() + "的ID :" + thread.getId());
        System.out.println(thread.getName() + "的名称 :" + thread.getName());
        System.out.println(thread.getName() + "的状态 :" + thread.getState());
        System.out.println(thread.getName() + "的优先级 :" + thread.getPriority());
        System.out.println(thread.getName() + "是否为后台线程 :" + thread.isDaemon());
        // 前台线程会阻止java进程结束
        // 必须所有前台线程执行完毕,java进程才能结束
        // 创建的线程默认是前台线程
        // 可以通过setDaemon方法设置成后台线程
        System.out.println(thread.getName() + "是否活着 :" + thread.isAlive());
        // true表存活,false表死亡
        System.out.println(thread.getName() + "被中断 :" + thread.isInterrupted());

        thread.start();
        while (thread.isAlive()) {
            try {
                System.out.println(thread.getName() + "的状态 :" + thread.getState());
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

在这里插入图片描述


二、Thread的常用操作

1.启动一个线程

重写run方法,包括new出一个线程,只是创建出了一个线程对象,但是这不意味着线程真的创建出来并开始执行了。

1️⃣ 重写run方法,就好比将工厂里的生产线图纸画好了。

2️⃣ new出一个线程,就好比将生产线的各个零部件安排到位了。

3️⃣ 执行start方法,就好比将这些零部件组装成生产线,并开始独立运行生产线。

调用 start 方法, 才真的在操作系统的底层创建出一个线程


2.休眠一个线程

方法说明
public static void sleep(long millis) throws InterruptedException休眠当前线程 millis毫秒
public static void sleep(long millis, int nanos) throwsInterruptedException可以更高精度的休眠

public class ThreadDemo1 {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            while (true) {
                try {
                    long a = System.currentTimeMillis();
                    Thread.sleep(3*1000);
                    long b = System.currentTimeMillis();
                    System.out.println(b-a);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
    }
}

sleep方法是让线程休眠的方法,
因此如果线程在休眠时被唤醒了,就会抛出InterruptedException异常🤐🤐🤐。

因此sleep方法会有一个必须要处理的受查异常,
这里咱们使用try catch语句来处理。


因为线程的调度是不可控的,所以,这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间的。(线程睡醒后,再去被cpu调度,因此可能睡醒后需要等待一段时间,才能被执行,所以前后时间差大于等于3000毫秒)
在这里插入图片描述


3.获取当前线程引用

方法说明
public static Thread currentThread()返回当前线程对象的引用
public class ThreadDemo {
    public static void main(String[] args) {
    	//在哪个线程中使用就返回哪个线程对象的引用
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName());
   }
}

在这里插入图片描述

4.等待一个线程

有时,我们需要等待一个线程完成它的工作后,才能进行自己的下一步工作。

方法说明
public void join()等待线程结束
public void join(long millis)等待线程结束,最多等 millis 毫秒
public void join(long millis, int nanos)同理,但可以更高精度

1️⃣无参数版 join(死等)

	                       //join方法也有受查异常,这里使用声明异常处理
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "正在工作");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + "结束工作");
        },"张三");
        Thread thread2 = new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "正在工作");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + "结束工作");
        },"李四");
        System.out.println("先让张三工作");

        thread1.start();
        //在main线程中调用的thread1的join
        //效果就是main方法会等待thread1线程执行完,才会继续执行
        thread1.join();
        System.out.println("张三工作完了");
        System.out.println("李四开始工作");
        thread2.start();
         //在main线程中调用的thread2的join
        //效果就是main方法会等待thread2线程执行完,才会继续执行
        thread2.join();
        System.out.println("李四工作完了");
    }

在a线程中调用b线程的join方法,

a线程走到join方法这一行代码后,a线程不再参与CPU调度(进入阻塞状态),等待b线程执行完,才继续去参与CPU调度(解除阻塞状态),去执行下面的语句。
在这里插入图片描述


2️⃣有参数版 join(参数为等待的最大时间,超过最大时间,无论那个线程有没有执行完,都不再等待)

    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            while (true) {
                System.out.println("thread线程正在工作");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"thread");
        thread.start();
        try {
            thread.join(3000);
            System.out.println("我已经等了Thread线程3000毫秒了,不等了,我要去排队执行了");

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

主线程等待了thread线程3000毫秒后,开始去排队执行,所以算上排队的时间,前后时间差仍会大于3000毫秒


在这里插入图片描述


关于 join 还有一些细节内容,我们留到后续文章再讲解。


5.中断一个线程

目前常见的有以下两种方式:

1️⃣通过共享的标记来进行沟通
2️⃣调用 interrupt() 方法来通知


1️⃣通过共享的标记来进行沟通

public class ThreadDemo1 {
				//后续再为大家介绍volatile关键字
    public static volatile boolean isQuit = false;
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            while (!isQuit) {

							//currentThread方法是获得当前线程的引用
                System.out.println(Thread.currentThread().getName() + "正在工作");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + "结束工作");
        });
        System.out.println(Thread.currentThread().getName() + "控制生产线开始工作");
        thread.start();
        try {
            Thread.sleep(10*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        isQuit = true;
        System.out.println(Thread.currentThread().getName() + "控制生产线停止工作");
    }
}

值得注意的是此处的标记必须是成员变量,不能是局部变量。
因为Lambda表达式的变量捕获规则。
Lambda表达式及其变量捕获


在这里插入图片描述


2️⃣调用 interrupt() 方法来通知

使用 Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 代替自定义标志位。(Thread 内部包含了一个 boolean 类型的变量作为线程是否被中断的标记)

方法说明
public void interrupt()中断对象关联的线程,如果线程正在阻塞,则以异常方式通知,否则设置标志位
public static booleaninterrupted()判断当前线程的中断标志位是否设置,调用后清除标志位
public booleanisInterrupted()判断对象关联的线程的标志位是否设置,调用后不清除标志位

  • 使用 thread 对象的 interrupt() 方法通知线程结束。
    public static void main(String[] args) {
        Thread thread = new Thread(()->{

			//如果标志位被设置成true那么就会跳出while语句
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("标志位未被设置");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

		//设置标志位为true
        thread.interrupt();
    }

在这里插入图片描述
这里就只抛出了一个异常,然后该进程仍继续执行???
和预期的不一样啊🤔🤔🤔!!!

说明标志位没有被设置成true,why???


先来分析 interrupt方法
作用

  1. 线程正在CPU上执行,设置标志位为true
  2. 线程正在阻塞中,(比如调用wait/join/sleep 等方法而阻塞挂起),通过抛出异常的方式,让这些方法立刻结束。

问题就出在这里 wait/join/sleep 等方法结束时,会自动的把标志位清空
(true->false)
😲😲😲这就导致循环仍然可以继续。

如果sleep刚执行完,设置的标志位,那么循环就可以结束了。
不过这种概率非常低,因为CPU的执行效率非常之高,一秒钟可以执行调度上千上万次,所以相对来说sleep的时间就占据整个循环体99.99%的时间,因此大多数时间该线程都在sleep。

总结来说:

  • 如果标志位是false,sleep正常执行休眠操作。
  • 如果标志位是true,无论此时,sleep刚开始执行,还是执行了一半,都会做出两个反应,1.立即抛出异常 2.清空标志位为false。

因此interrupt方法只是通知线程要结束了,而不是命令线程立刻结束。

可以通过代码去灵活的控制线程是否结束。

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{

            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("标志位未被设置");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                	// 利用try-catch语句来使循环结束
                    System.out.println("线程结束了");
                    // 通过break来跳出循环
                    break;
                }
            }
        });
        thread.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();

    }

在这里插入图片描述


三、线程的状态

操作系统中的线程,自身是带有一个状态的。

但是Java Thread是对系统线程的封装,把这里的状态又进一步的精细化了。

观察线程的状态

线程状态说明
NEW创建了线程的引用, 但是没有让该线程开始工作
RUNNABLE就绪状态(正在执行状态或可执行状态)
BLOCKED等待获取锁
WAITING线程在无限等待唤醒
TIMED_WAITING线程在等待唤醒,但设置了最长等待时限
TERMINATED线程执行完毕,结束了

线程的状态是一个枚举类型 Thread.State


1️⃣NEW状态

    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("hello");
        });

		// 线程创建好,但未执行
        System.out.println(thread.getState());
    }

在这里插入图片描述


2️⃣RUNNABLE状态

    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            while (true) {
            	// 空循环
            }
        });
        thread.start();

		// 线程正在执行,或可以随时去CPU上执行
        System.out.println(thread.getState());
    }

在这里插入图片描述


3️⃣TIMED_WAITING状态

    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

		//线程在sleep时,是TIMED_WAITING状态
        System.out.println(thread.getState());
    }

绝大多数时间,线程都是在sleep的。

在这里插入图片描述


4️⃣TERMINATED状态

    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("hello");
        });
        thread.start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

		// 线程执行完毕,Thread对象还存在
        System.out.println(thread.getState());
    }

在这里插入图片描述


BLOCKED状态WAITING状态,需要用到后续知识,在此先不做讲解,
等后续文章更新后,再回来讲解。


线程状态之间的转换

每个线程分为三个主要阶段
在这里插入图片描述

1️⃣当线程创建完成时,就进入到了阶段1

2️⃣线程调用了start方法后,便开始了阶段2
阶段2,会根据代码中使用的线程方法,在这几个状态中来回切换。

3️⃣当线程执行完毕后,进入阶段3


上面提到的 isAlive() 方法,可以认为是处于不是 NEW 和 TERMINATED 的状态都是活着的。


总结

以上就是今天要分享的多线程内容了,多线程还有很多后续知识要与大家分享,期待下一篇文章见!!🥰🥰🥰

路漫漫,不止修身也养性。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值