JavaEE-多线程(基础篇二)Thread类

文章目录

  • 线程中的状态
    • 新生状态
    • 就绪状态
    • 运行状态
    • 阻塞状态
    • 死亡状态
  • 阻塞状态详细解释
    • Java中sleep()和wait()的区别
  • thread的方法
    • 构造方法
    • 属性
    • 启动一个线程-start()
    • 中断一个线程
    • yield()让出CPU资源
    • 等待一个进程-join()
    • 获取当前线程的引用
      • 总结
    • 休眠当前线程
  • 各个状态的转换
    • NEW,RUNNABLE,TERMINATED
    • WAITING,BLOCKED,TIMED_WAITING


线程中的状态

NEW: 安排了工作, 还未开始行动
RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作.
BLOCKED: 这几个都表示排队等着其他事情
WAITING: 这几个都表示排队等着其他事情
TIMED_WAITING: 这几个都表示排队等着其他事情
TERMINATED: 工作完成了.

在正式学习Thread类中的具体方法之前,我们先来了解一下线程有哪些状态,这个将会有助于后面对Thread类中的方法的理解。
  线程从创建到最终的消亡,要经历若干个状态。一般来说,线程包括以下这几个状态:创建(new)、就绪(runnable)、运行(running)、阻塞(blocked)、time waiting、waiting、消亡(dead)
  当需要新起一个线程来执行某个子任务时,就创建了一个线程。但是线程创建之后,不会立即进入就绪状态,因为线程的运行需要一些条件(比如内存资源,在前面的JVM内存区域划分一篇博文中知道程序计数器、Java栈、本地方法栈都是线程私有的,所以需要为线程分配一定的内存空间),只有线程运行需要的所有条件满足了,才进入就绪状态。
  当线程进入就绪状态后,不代表立刻就能获取CPU执行时间,也许此时CPU正在执行其他的事情,因此它要等待。当得到CPU执行时间之后,线程便真正进入运行状态。
  线程在运行状态过程中,可能有多个原因导致当前线程不继续运行下去,比如用户主动让线程睡眠(睡眠一定的时间之后再重新执行)、用户主动让线程等待,或者被同步块给阻塞,此时就对应着多个状态:time waiting(睡眠或等待一定的事件)、waiting(等待被唤醒)、blocked(阻塞)。
  当由于突然中断或者子任务执行完毕,线程就会被消亡。
我们在上篇博客中也讲到了这五个状态,因为thread类中的五个状态的运用也十分广泛,所以我们在本篇博客再次为大家一一讲解
JavaEE-多线程(基础篇二)Thread类_第1张图片

新生状态

Thread t = new Thread();

正如我们前面所说,一个线程开始时之后有自己的内存空间,这些空和主内存进行交互,从主内存拷贝数据到工作空间.
当这个语句执行的时候,线程创建,开辟工作空间,也就是线程进入了新生状态

就绪状态

一旦调用了

t.start();

start方法被调用,线程立即进入了就绪状态,表示这个线程具有了运行的条件,但是还没有开始运行,这就是就绪状态.
线程就绪,并不意味着立即调度执行,因为要等待CPU的调度,所以我们一般说是进入了就绪状态
但是还有另外的一些情况,线程也会因此进入就绪状态,分别是:

  1. start()方法调用
  2. 本来处于阻塞状态,后来阻塞解除
  3. 如果运行的时候调用yield()方法,避免一个线程占用资源过多,终端一下,会让线程重新进入就绪状态,如果调用yield()方法之后,没有其他等待执行的线程,此线程就会马上恢复执行.注:这里的yield()方法后面会介绍到,就是让一个正在执行的线程先去摸鱼,让CPU调度其他的线程去执行
  4. JVM本身将本地线程切换到了其他线程,那么这个线程就进入就绪状态.

运行状态

当CPU选定了一个就绪状态的进程,进行执行,这时候线程就进入了运行状态,线程真正开始执行线程体的具体代码块,基本是run()方法
注意,一定是从就绪状态到运行状态,不会从阻塞状态到运行状态的

阻塞状态

阻塞状态指的是代码不继续执行,而是在等待,阻塞解除之后,重新进入就绪状态.
也就是说,阻塞状态肯定是有运行状态转换过去的,运行状态–>阻塞状态,并不会从就绪状态转换到阻塞状态.

阻塞的四种方法有:

  1. sleep()方法,是占用资源让调度器睡眠,可以利用构造方法限制睡眠多久
  2. wait()方法,和sleep()的不同之处在于,是不占用资源的,限制等待多久;
  3. join()方法,假如,合并或是插队,这个方法阻塞线程到另一个线程完成以后再继续执行;
  4. 有些IO阻塞比如write()或者read(),因为IO方法是通过操作系统调用的.

上面的方法和start()一样,不是说调用了就立即阻塞了,而是要看CPU的调度情况

死亡状态

死亡状态是指,线程的代码执行完毕或者中断执行.一旦进入死亡状态,就不能再调用start().
让线程进入死亡状态的方法是stop()和destory()但是都不推荐使用,jdk里面也写了已过时.
一般的做法为,在线程内,让线程自身自然死亡,或者加一些代码,想办法让线程执行完毕

  1. 自然死亡:这个线程体里就是多少次的循环,几次调用,执行完了就完了
  2. 如果不能自然死亡,那么就加一些终止变量,然后用它作为run 的条件,这样,外部调用的时候根据时机,把变量设置为false

阻塞状态详细解释

  • sleep(时间)指定当前线程阻塞的毫秒数
  • sleep存在异常:interruptException;
  • sleep时间到了之后线程进入就绪状态
  • sleep可以模拟网络延时,倒计时等
  • 每一个对象都有一个无形的锁,sleep不会释放锁.(也就是我们所说i的,抱着资源睡觉)

JavaEE-多线程(基础篇二)Thread类_第2张图片

Java中sleep()和wait()的区别

  1. 这两个方法来自不同的类.分别是,sleep来自Thread类,和wait来自Object类

sleep是Thread的静态类方法,谁调用的谁去睡觉,即使在A线程里面调用了B额sleep方法,实际上还是A去睡觉,要让B线程睡觉要在B的代码中调用sleep

  1. 最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法.

sleep不让出系统资源,wait是进入线程等待池中等待,让出系统资源,其他线程可以占用CPU.

一般wait不会加时间限制,因为如果wait线程的运行资源不够,wait结束出来也没用,要等待其他线程调用notfy/notifyAll唤醒等待池中的所有线程,才会进入就绪队列等待OS分配系统资源.sleep(milliseconds)可以用时间指定使它自动唤醒过来,如果时间不到只能调用interrupt()强行打断.
Thread.Sleep(0)的作用是"触发操作系统立刻重新进行一次CPU竞争".
3. 使用范围:wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用

synchronized(x){ 
   x.notify() 
   //或者wait() 
}
  1. sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常 都要捕获异常

总结:

两者都可以暂停线程的执行
对于sleep方法,我们首先要知道该方法是属于Thread类中的,而wait()方法,则是属于Object类中的.
Wait通常被用于线程间交互/通信,sleep通常被用于暂停执行.
sleep()方法导致了程序暂停执行指定的时间,让出CPU该线程,但是他的监控状态依然保持,当指定的时间到了又会自动恢复运行状态
在调用sleep()方法的过程中,线程不会释放对象锁
而当调用wait()方法的时候,线程会放弃对象锁,进入等到此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备,获取对象锁进入运行状态.线程不会自动苏醒.

举个例子

 public static void main(String[] args) {
        new Thread(new Thread1()).start();
        try {
            Thread.sleep(5000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        new Thread(new Thread2()).start();
    }

    private static class Thread1 implements Runnable{
        @Override
        public void run(){
            synchronized (MyTestDemo.class) {
                System.out.println("enter thread1...");
                System.out.println("thread1 is waiting...");
                try {
                    //调用wait()方法,线程会放弃对象锁,进入等待此对象的等待锁定池
                    MyTestDemo.class.wait();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println("thread1 is going on ....");
                System.out.println("thread1 is over!!!");
            }
        }
    }

    private static class Thread2 implements Runnable {
        @Override
        public void run() {
            synchronized (MyTestDemo.class) {
                System.out.println("enter thread2....");
                System.out.println("thread2 is sleep....");
                //只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
                MyTestDemo.class.notify();
                //==================
                //区别
                //如果我们把代码:MyTestDemo.class.notify();给注释掉,MyTestDemo.class调用了wait()方法,但是没有调用notify()
                //方法,则线程永远处于挂起状态。
                try {
                    //sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,
                    //但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
                    //在调用sleep()方法的过程中,线程不会释放对象锁。
                    Thread.sleep(5000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println("thread2 is going on....");
                System.out.println("thread2 is over!!!");
            }
        }
    }

thread的方法

构造方法

方法 说明
Thread() 创建线程对象
Thread(Runnable target) 创建Runnable对象创建线程对象
Thread(String name) 创建线程对象,并命名
Thread(Runnable target, String name) 使用 Runnable 对象创建线程对象,并命名
Thread(ThreadGroup group,Runnable target) 线程可以被用来分组管理,分好的组即为线程组
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnbale());
Thread t3 = new Thread("name");
Thread t4 = new Thread(new MyRunnable(),"name");

属性

属性 获取方法
ID getId()
名称 getName()
状态 getState()
优先级 getPriority()
是否后台线程 isDaemon()
是否存活 isAlive()
是否被中断 isInterrupted()
  • ID是线程的唯一表示,不同线程不会重复
  • 名称是各种调试工具会用到
  • 状态表示线程当前所处的一个状态
  • 优先级高的线程理论上来说更容易被调度器调度到
  • 关于后台线程,要记住JVM会在一个进程的所有非后台线程结束后,才会结束运行
  • 是否存活可以认为是,run方法是否运行结束
package thread;

public class Test2 {
    public static void main(String[] args) {
        Thread thread = 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(Thread.currentThread().getName()
                +"的ID:"+thread.getId());
        System.out.println(Thread.currentThread().getName()
                +"的状态:"+thread.getState());
        System.out.println(Thread.currentThread().getName()
                +"的优先级:"+thread.getPriority());
        System.out.println(Thread.currentThread().getName()
                +"的后台线程:"+thread.isDaemon());
        System.out.println(Thread.currentThread().getName()
                +"的存活状态:"+thread.isAlive());
        System.out.println(Thread.currentThread().getName()
                +"是否被中断:"+thread.isInterrupted());
        thread.start();
        while(thread.isAlive()){
            System.out.println(Thread.currentThread().getName()
                    +"的状态:"+thread.getState());
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
E:\develop\Java\jdk-11\bin\java.exe "-javaagent:E:\IDEAIU\IntelliJ IDEA 2022.2\lib\idea_rt.jar=51350:E:\IDEAIU\IntelliJ IDEA 2022.2\bin" -Dfile.encoding=UTF-8 -classpath E:\JAVAcode\gyljava\system_code\out\production\system_code thread.Test2
main的ID:24
main的状态:NEW
main的优先级:5
main的后台线程:false
main的存活状态:false
main是否被中断:false
main的状态:RUNNABLE
Thread-0还活着
main的状态:TIMED_WAITING
Thread-0还活着
main的状态:TIMED_WAITING
main的状态:TIMED_WAITING
Thread-0还活着
main的状态:TIMED_WAITING
main的状态:TIMED_WAITING
Thread-0还活着
main的状态:TIMED_WAITING
main的状态:TIMED_WAITING
Thread-0还活着
main的状态:TIMED_WAITING
main的状态:TIMED_WAITING
Thread-0还活着
main的状态:TIMED_WAITING
main的状态:TIMED_WAITING
Thread-0还活着
main的状态:TIMED_WAITING
main的状态:TIMED_WAITING
Thread-0还活着
main的状态:TIMED_WAITING
main的状态:TIMED_WAITING
Thread-0还活着
main的状态:TIMED_WAITING
main的状态:TIMED_WAITING
Thread-0还活着
main的状态:TIMED_WAITING
main的状态:TIMED_WAITING
Thread-0即将死去

Process finished with exit code 0

启动一个线程-start()

我们已经知道了如何听过重写run方法创建一个线程对象,但是线程对象被创建出来并不以为这线程就已经开始运行了

  • 重写run方法是提供给线程要做的事情,相当于一个指令清单
  • 而调用start()方法,就是给他们一声令下,让他们赶紧去行动起来,线程才真正地进入就绪状态,进入系统链表中等待被调度执行了.

意思就是说,调用start方法地时候,才是真正地在操作系统的底层创建出一个线程

中断一个线程

那么一个线程一旦进入了就绪状态,然后被调度器调度之后,他就会按照我们在run方法中的语句执行命令,不完成这个线程是不会结束的,但有的时候我们需要增加一些机制,来应付特殊情况,比如我们突然发现代码中有一道逻辑上的问题,设计程序的安全,如果继续执行下去整个程序都会崩溃,那么这个时候
我们就要让线程赶紧停下来.
目前常见的两种方式有:

  1. 通过共享的标记来进行信息传递和沟通
  2. 通过interrupt()方法来通知

我们写一段银行转账的背景代码,故事围绕李四给骗子转账,然后及时被制止展开.

package thread;

public class Test3 {
    private static class MyRunnable implements Runnable{
        public volatile boolean isQuit = false;
        //浅浅定义一个状态来确定是否停下来了

        @Override
        public void run() {
            while(!isQuit) {//这里就用到啦
                System.out.println(Thread.currentThread().getName()
                        + "说:我在转账!嗨嗨嗨!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()
                    + "说:哎呀,差点误了大事");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyRunnable target = new MyRunnable();
        Thread thread = new Thread(target,"李四");
        System.out.println(Thread.currentThread().getName()
                + "开始运行,李四开始转账");
        thread.start();
        Thread.sleep(10000);
        System.out.println(Thread.currentThread().getName()
                + ":有新情况,得赶紧通知李四对方是个大骗子");
        target.isQuit = true;
    }
}


E:\develop\Java\jdk-11\bin\java.exe "-javaagent:E:\IDEAIU\IntelliJ IDEA 2022.2\lib\idea_rt.jar=55363:E:\IDEAIU\IntelliJ IDEA 2022.2\bin" -Dfile.encoding=UTF-8 -classpath E:\JAVAcode\gyljava\system_code\out\production\system_code thread.Test3
main开始运行,李四开始转账
李四说:我在转账!嗨嗨嗨!
李四说:我在转账!嗨嗨嗨!
李四说:我在转账!嗨嗨嗨!
李四说:我在转账!嗨嗨嗨!
李四说:我在转账!嗨嗨嗨!
李四说:我在转账!嗨嗨嗨!
李四说:我在转账!嗨嗨嗨!
李四说:我在转账!嗨嗨嗨!
李四说:我在转账!嗨嗨嗨!
李四说:我在转账!嗨嗨嗨!
main:有新情况,得赶紧通知李四对方是个大骗子
李四说:哎呀,差点误了大事

Process finished with exit code 0

我们可以看到.在主线程中,让主线程睡眠了10秒钟之后,把isQuit的值改了,线程thread就停下来了.
那么上面的是第一种方法,我们下面来介绍第二种方法.
使用Thread.interrupted()或者Thread.currentThread().isInterrupt()代替自定义标志位.

Thread的内部包含了一个boolean类型的变量作为线程是否被中断的标记

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

那么下面使用thread对象的interrupted()方法来通知线程结束.

package thread;

public class Test4 {
    private static class MyRunnable implements Runnable {

        @Override
        public void run() {
            //while里面放这两个都可以
            while(!Thread.interrupted()){
            //while(!Thread.currentThread.isInterrupted(){
                System.out.println(Thread.currentThread().getName()
                        + ":转账咯,嗨嗨嗨!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println(Thread.currentThread().getName()
                            +":不好,有内鬼!终止交易!");
                    //注意此处要有break;
                    break;
                }
            }
            System.out.println(Thread.currentThread().getName()
                    +":还好还好,差点钱全都转过去了");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyRunnable target = new MyRunnable();
        Thread thread = new Thread(target,"葛玉米");
        System.out.println(Thread.currentThread().getName()
                +":让葛玉米开始转账");
        thread.start();
        Thread.sleep(8000);
        System.out.println(Thread.currentThread().getName()
                +":坏了!得赶紧通知葛玉米对方是电信诈骗");
        thread.interrupt();
    }
}


E:\develop\Java\jdk-11\bin\java.exe "-javaagent:E:\IDEAIU\IntelliJ IDEA 2022.2\lib\idea_rt.jar=58406:E:\IDEAIU\IntelliJ IDEA 2022.2\bin" -Dfile.encoding=UTF-8 -classpath E:\JAVAcode\gyljava\system_code\out\production\system_code thread.Test4
main:让葛玉米开始转账
葛玉米:转账咯,嗨嗨嗨!
葛玉米:转账咯,嗨嗨嗨!
葛玉米:转账咯,嗨嗨嗨!
葛玉米:转账咯,嗨嗨嗨!
葛玉米:转账咯,嗨嗨嗨!
葛玉米:转账咯,嗨嗨嗨!
葛玉米:转账咯,嗨嗨嗨!
葛玉米:转账咯,嗨嗨嗨!
main:坏了!得赶紧通知葛玉米对方是电信诈骗
葛玉米:不好,有内鬼!终止交易!
葛玉米:还好还好,差点钱全都转过去了
java.lang.InterruptedException: sleep interrupted
	at java.base/java.lang.Thread.sleep(Native Method)
	at thread.Test4$MyRunnable.run(Test4.java:14)
	at java.base/java.lang.Thread.run(Thread.java:833)

Process finished with exit code 0

由上可知,thread收到通知的方式有两种:

  1. 如果线程因为调用wait/join/sleep等方法而阻塞挂起,则以InterruptedException异常的形式通知,清除中断标志
    • 当出现InterruptedException的时候,要不要结束线程取决于catch中代码怎么写,我们可以忽略这个异常,也可以跳出循环结束线程,
  2. 否则的话,只是内部的一个中断标志被设置,thread可以通过
    • Thread.interrupted()判断当前线程的中断标志被设置,清楚中断标志
    • Thread.currentThread().isInterrupted()判断指定线程的中断标志被设置,不清除中断标志这种方式通知收到的更及时,即使线程正在sleep也可以马上收到.

那么我们上面提到了一个东西,叫做标志位,那么我们用代码来验证一下用上述方法实现之后,标志位的具体情况
稍微解释一下:
标志位是否清楚,就好像一个开关
Thread.isInterrupted()就相当于按下了开关
Thread.currentThread().isInterrupted()相当于按下开关之后,开关弹不起来了,这个我们称为不清楚标志位

  • 使用Thread.isInterrupted(),线程中断会清楚标志位
package thread;

public class Test5 {
    private static class MyRunnable implements Runnable {

        @Override
        public void run() {
            for (int i = 0;i<5;i++){
                System.out.println(Thread.interrupted());
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    public static void main(String[] args) {
        MyRunnable target = new MyRunnable();
        Thread thread = new Thread(target,"葛玉米");
        thread.start();
        thread.interrupt();
    }
}

运行结果:

true//这里只有一开始是ture,说明中断之后就把标志位改为false,因为标志位被清
false
false
false
false

这里我存在一个问题,既然interrupted()把标志位改成了false,那会影响while()循环的退出,这样的话就需要在InterruptException异常抛出的时候就break退出循环,这一点我能理解,那isInterrupted不会更改标志位,它并不会影响while判断条件,也不会影响循环的退出,那为啥它会一直循环下去,依然需要在Exception抛出的时候用break退出循环呢?

  • 第二种情况,使用Thread.currentThread().isInterrupted(),线程中断标记不会清除.
package thread;

public class Test6 {
    private static class MyRunnable implements Runnable {

        @Override
        public void run() {
            for (int i = 0;i<5;i++){
                System.out.println(Thread.currentThread().getName()
                        +"的标志位:"+Thread.currentThread().isInterrupted());
//
//                问:为何加上这段try-catch之后,标志位就改成了false呢?
//                try {
//                    Thread.sleep(1000);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                    System.out.println(Thread.currentThread().getName()+":被中断啦!");
//                }
            }
        }
    }

    public static void main(String[] args) {
        MyRunnable target = new MyRunnable();
        Thread thread = new Thread(target,"葛玉米");
        thread.start();
        thread.interrupt();
    }
}

输出结果:

葛玉米的标志位:true//这里就全部都是ture了,因为标志位没有清除
葛玉米的标志位:true
葛玉米的标志位:true
葛玉米的标志位:true
葛玉米的标志位:true

ATTENTION!!那么我在代码中发现一个问题,比如上面的银行取钱的问题,既然用interrupted()会清除标志位,从而对循环有所影响,所以我们要在try-catch里面用break;简答粗暴地退出循环
但是isInterrupted并不会清除标志位,不会影响到循环的退出,为什么还是依然要在try-catch里面写一个break强制退出循环呢?
一开始我觉得很奇怪,但后来对比了一下代码运行结果.
我发现异常的捕获会使得标志位直接被清除,isInterrupted()的结果将从true变成false

用下面这段代码就可以验证:

JavaEE-多线程(基础篇二)Thread类_第3张图片
JavaEE-多线程(基础篇二)Thread类_第4张图片
而如果我们把try-catch那一段代码去掉,结果将会是:
JavaEE-多线程(基础篇二)Thread类_第5张图片

如果一个线程处于了阻塞状态(如线程调用了thread.sleep、thread.join、thread.wait、1.5中的condition.await、以及可中断的通道上的 I/O 操作方法后可进入阻塞状态),则在线程在检查中断标示时如果发现中断标示为true,则会在这些阻塞方法(sleep、join、wait、1.5中的condition.await及可中断的通道上的 I/O 操作方法)调用处抛出InterruptedException异常并且在抛出异常后立即将线程的中断标示位清除即重新设置为false。抛出异常是为了线程从阻塞状态醒过来,并在结束线程前让程序员有足够的时间来处理中断请求。

yield()让出CPU资源

package thread;

public class Test13 {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){
                    System.out.println("t1线程正在执行");
                    Thread.yield();
                }
            }
        },"t1");
        t1.start();
        
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){
                    System.out.println("t2线程正在执行");
                }
            }
        },"t2");
        t2.start();
    }
}

我们不难发现,当不适用yield的时候,t1t2大概五五开,但是当使用yield的时候,t1的数量会远远小于t2.
yield不改变线程的状态,但是会让线程重新等待被调度器调度执行,也就是让出CPU资源

等待一个进程-join()

有的时候,我们需要等待一个线程完成它的工作之后才能进行自己的下一步工作,比如以下情况

葛玉米只有等待何二傻转账成功,才决定是否存钱,因为ATM机只有一个,而且里面没有之前的存款.
所以这个时候我们需要一个方法来明确让一个线程等待另一个线程的结束

package thread;

public class Test7 {
    public static void main(String[] args) throws InterruptedException {
        Runnable target = () -> {
          for (int i=0;i<3;i++){
              System.out.println(Thread.currentThread().getName()
              +":我在工作中~");
              try {
                  Thread.sleep(1000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
            System.out.println(Thread.currentThread().getName()
            +":我运行结束了");
        };
        Thread thread1 = new Thread(target,"何二傻");
        Thread thread2 = new Thread(target,"葛玉米");
        System.out.println(Thread.currentThread().getName()+
                ":何二傻先开始工作");
        thread1.start();
        thread1.join();
        System.out.println(Thread.currentThread().getName()
                +":何二傻工作结束了,换葛玉米上来干活");
        thread2.start();
        thread2.join();
        System.out.println(Thread.currentThread().getName()
                +":葛玉米工作结束");
    }
}

main:何二傻先开始工作
何二傻:我在工作中~
何二傻:我在工作中~
何二傻:我在工作中~
何二傻:我运行结束了
main:何二傻工作结束了,换葛玉米上来干活
葛玉米:我在工作中~
葛玉米:我在工作中~
葛玉米:我在工作中~
葛玉米:我运行结束了
main:葛玉米工作结束

Process finished with exit code 0

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

获取当前线程的引用

package thread;

public class Test8 {
    public static void main(String[] args) {
        myThread mt = new myThread();
        new Thread(mt).start();
        new Thread(mt, "Name1").start();
        new Thread(mt, "Name2").start();

        System.out.println(Thread.currentThread().getName()); // main主方法
        //      System.out.println(this.getName());     // this获取不到线程对象
    }
    static class myThread extends Thread {
        @Override public void run() {
            try {
                Thread.sleep(3000);
                Thread t = Thread.currentThread();
                System.out.println("当前线程名字:" + t.getName() + " 当前线程的优先级别为:" + t.getPriority() + " ID:" + t.getId());
                //           System.out.println("当前线程名字:" + this.getName() + " 当前线程的优先级别为:" + this.getPriority() + " ID:"+ this.getId());
            } catch(InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


执行结果

main
当前线程名字:Thread-1 当前线程的优先级别为:5 ID:10
当前线程名字:Name1 当前线程的优先级别为:5 ID:11
当前线程名字:Name2 当前线程的优先级别为:5 ID:12

切换注释后执行的结果是:

main
当前线程名字:Thread-0 当前线程的优先级别为:5 ID:9
当前线程名字:Thread-0 当前线程的优先级别为:5 ID:9
当前线程名字:Thread-0 当前线程的优先级别为:5 ID:9

二次输出结果一致,说明此时this并不是new Thread(mt),而是mt,所以输出一致,都是mt的名称和id。

总结

  1. Thread提供静态方法currentThread()来供我们调用,既可以避免this无法获取到main线程的问题,又可以避免this无法获取到Thread t2=new Thread(Thread t)来开启的线程的问题,可以说currentThread方法可以在所有场合获取到正确的当前线程。
    2. 在自定义线程类时,如果线程类是继承java.lang.Thread的话,那么线程类就可以使用this关键字去调用继承自父类Thread的方法,this就是当前的对象。

休眠当前线程

这也是我们非常熟悉的一组方法,有一点我们要记得,因为线程的调度是不可控的,所以,这个方法只能保证实际休眠时间大于等于参数设置的休眠时间的.

方法 说明
public static void sleep(long milles) thorws InterruptedException 休眠当前线程millis毫秒
public static voidsleep(long mills,int nanos) throws InterruptedException 可以更高精度得睡眠
package thread;

public class Test9 {
    public static void main(String[] args) throws InterruptedException {
        System.out.println(System.currentTimeMillis());
        Thread.sleep(3000);
        System.out.println(System.currentTimeMillis());
    }
}
E:\develop\Java\jdk-11\bin\java.exe "-javaagent:E:\IDEAIU\IntelliJ IDEA 2022.2\lib\idea_rt.jar=52389:E:\IDEAIU\IntelliJ IDEA 2022.2\bin" -Dfile.encoding=UTF-8 -classpath E:\JAVAcode\gyljava\system_code\out\production\system_code thread.Test9
1659249272824
1659249275837

Process finished with exit code 0

各个状态的转换

NEW: 安排了工作, 还未开始行动
RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作.
BLOCKED: 这几个都表示排队等着其他事情
WAITING: 这几个都表示排队等着其他事情
TIMED_WAITING: 这几个都表示排队等着其他事情
TERMINATED: 工作完成了.

NEW,RUNNABLE,TERMINATED

package thread;

public class Test10 {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for (int i = 0;i<10;i++){
            }
        },"葛玉米");
        System.out.println(thread.getName()+":"+thread.getState());
        thread.start();
        while(thread.isAlive()){
            System.out.println(thread.getName()+":"+thread.getState());
        }
        System.out.println(thread.getName()+":"+thread.getState());
    }
}

葛玉米:NEW
葛玉米:RUNNABLE
葛玉米:RUNNABLE
葛玉米:RUNNABLE
葛玉米:TERMINATED

这里解释一下,首先我们定义一个线程,并用lambda表达式告诉他要做些什么任务,并且命名该线程为葛玉米
那么下面有很多System.out.println,都是为了打印出即时的状态
thread.start();之前,线程的状态为新生状态,
start之后,就进入就绪状态,并且等待被调度器调用,进入while循环时,线程已经在开始for循环的工作,所以运行状态为RUNNABLE,等到10个循环结束了,thread的任务就完成了,它就是TERMINATED的状态,
那么这个时候为什么RUNNABLE显示的次数不是10次呢?
因为main线程和thread线程的执行要看调度器的安排,我们是看作随机执行的
所以如果你重复运行该代码段,得到的结果是不一样的,RUNNABLE出现的次数均在0-10(当然出现0和10的概率不大,这也要看调度器怎么安排了)

WAITING,BLOCKED,TIMED_WAITING

package thread;

/**
 * 这里我们引入对象锁的应用
 */
public class Test11 {
    public static void main(String[] args) {
        final Object object = new Object();
        //先定义一个final修饰的object类,这样才能用进thread的lambda表达式中
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (object){
                    while(true){
                        try {
                            Thread.sleep(1000);
                            System.out.println(Thread.currentThread().getName()
                            +"的状态:"+Thread.currentThread().getState());
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        },"t1");
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()
                        +"的状态:"+Thread.currentThread().getState());
                synchronized (object){
                    System.out.println("我是t2,我在执行中");
                }

            }
        },"t2");
        t2.start();
    }
}

JavaEE-多线程(基础篇二)Thread类_第6张图片
JavaEE-多线程(基础篇二)Thread类_第7张图片
这里因为在程序中,上锁之后t2和t1的状态用System.out.println来表示不太方便,所以我们用jconsole来打开查看线程状态,我们可以发现,t1的状态是TIMED_WAITING,t2的状态是BLOCKED,对应程序中t1正在sleep,t2获取不到object锁,所以是线程在等待监视器锁的时候的阻塞状态.
那么我们再来整个活,我们把上述代码中的sleep换成wait,再来观察状态,并且对sleep和wait做一个区别

package thread;
/**
 * 这里我们引入对象锁的应用
 */
public class Test12 {
    public static void main(String[] args) {
        final Object object = new Object();
        //先定义一个final修饰的object类,这样才能用进thread的lambda表达式中
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (object){
                    try {
                        System.out.println("我是t1,我开始等待");
                        object.wait();
                        System.out.println("我是t1,我结束等待");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
//                    while(true){
//                        try {
//                            Thread.sleep(1000);
//                            object.wait();
                            Thread.currentThread().wait();
                            System.out.println(Thread.currentThread().getName()
                                    +"的状态:"+Thread.currentThread().getState());
//                        } catch (InterruptedException e) {
//                            e.printStackTrace();
//                        }
//                    }
                }
            }
        },"t1");
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
//                System.out.println(Thread.currentThread().getName()
//                        +"的状态:"+Thread.currentThread().getState());
                synchronized (object){
                    System.out.println("我是t2,我在执行中");
                    object.notify();
                }

            }
        },"t2");
        t2.start();
    }
}


我是t1,我开始等待
我是t2,我在执行中
我是t2,我结束等待

package thread;

/**
 * 这里我们引入对象锁的应用
 */
public class Test11 {
    public static void main(String[] args) {
        final Object object = new Object();
        //先定义一个final修饰的object类,这样才能用进thread的lambda表达式中
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (object){
                    while(true){
                        try {
                            //Thread.sleep(1000);
                            object.wait();
                            System.out.println(Thread.currentThread().getName()
                            +"的状态:"+Thread.currentThread().getState());
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        },"t1");
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()
                        +"的状态:"+Thread.currentThread().getState());
                synchronized (object){
                    System.out.println("我是t2,我在执行中");
                }

            }
        },"t2");
        t2.start();
    }
}

这里我们再使用jconsole就可以看出t1的状态是WAITING
总结:

  • BLOCKED表示等待获取锁,WAITING和TIMED_WAITING表示等待其他线程发来通知
  • TIMED_WAITING线程在等待唤醒,但设置了实现,WAITING线程表示在无限等待唤醒

更新不易,希望大家多多支持,有什么不对的地方希望大佬指出

你可能感兴趣的:(JavaEE冲冲冲,java-ee,java,jvm)