• 详细讲解 —— 多线程(二)Thread类及常见方法(Java EE初阶)


    Thread类及常见方法

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

    1 Thread类常见构造方法

    方法作用
    Thread()创建线程对象
    Thread(Runnable target)使用 Runnable 对象创建线程对象。
    Thread(String name)创建线程对象,并命名,命名是为了更好
    Thread(Runnable target, String name)使用 Runnable 对象创建线程对象,并命名。
    【了解】Thread(ThreadGroup group,Runnable target)线程可以被用来分组管理,分好的组即为线程组,这个目前我们了解即可

    2 Thread 的几个常见属性

    属性获取方法
    IDgetId()
    名称getName()
    状态getState()
    优先级getPriority()
    是否后台线程isDaemon()
    是否存活isAlive()
    是否被中断isInterrupted()
    1. ID 是线程的唯一标识,不同线程不会重复
    2. 名称是各种调试工具时会用到
    3. 状态表示线程当前所处的一个情况(堵塞和就绪状态),下面我们会进一步说明
    4. 优先级高的线程理论上来说更容易被调度到
    5. 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。
      线程是后台线程,不影响进程的退出,线程不是后台线程,影响进程的退出。
      举例:如果main方法中有t1和t2两个进程。如果t1和t2是前台程序,即使mian函数执行完了,进程也不能退出,要等到t1和t2都执行完,整个进程才退出。如果t1和t2是后台程序,main函数执行完了,进程就可以退出了。
    6. 是否存活,即简单的理解,为 run 方法是否运行结束了
      Thread t 对象的生命周期和内核中对应的线程生命周期并不完全一致。
      创建出 t 对象之后,在调用start之前,系统中没有对应的线程,在run方法执行完之后,系统中的线程就销毁了,但是 t 这个对象可能还存在。
      通过isAlive就能知道当前系统中先线程的状态。如果在调用start之后并且run方法执行完之前,返回true。如果在调用start之前或者是run方法之后,返回false。
    7. 线程的中断问题,在下面我们会详细的讲解。

    3 启动一个线程-start()

    之前我们已经看到了如何通过覆写 run 方法创建一个线程对象,但线程对象被创建出来并不意味着线程就开始运行了。覆写 run 方法是提供给线程要做的事情的指令清单而调用 start() 方法,线程才真正独立去执行了。

    start和run的区别
    run单纯的只是一个普通的方法,描述了任务的内容,你在main线程中调用run,其实并没有创建新的线程,这个函数仍然是在main线程中执行的。既然是在一个线程中执行的就要按照顺序执行。
    start则是一个特殊的方法,调用它可以在系统的内部创建线程。

    //start和run的区别
    public class TestDome8 {
        public static void main(String[] args) {
            Thread thread1 = new Thread(()->{
                while(true){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("hello thread1!!!");
                }
            });
            //使用那一个函数放开那一个函数
    		//使用start函数
            //thread1.start();
    		//使用run函数
            //thread1.run();
    
            while(true){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("hello main!!!");
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    使用start函数
    上面这个图片是使用start这个函数,所运行的结果,使用start就开始创建了线程,thread1和main线程抢占式执行,就形成了上面的结果。
    使用run函数
    上面这个图片是使用run这个函数,所运行的结果,使用run这个函数是单纯的执行这个程序,没有创建线程,所以在main线程中按顺序执行,把run这个函数执行完了,才能执行下面的程序。

    4 中断一个线程

    中断线程,让一个线程停下来。线程停下来的关键是要让线程对应的run方法执行完。

    1)手动的设置一个标志位(自己创建的变量,boolean类型)来控制线程是否要执行结束。

    public class TestDome9 {
    	//定义标志位
        private static boolean isQuit = false;
        
        public static void main(String[] args) {
            Thread thread1 = new Thread(()->{
                while(!isQuit){   //当isQuit是false时,执行线程。其他的线程控制这个标志位,就可以影响到这个线程的执行。
                    try {
                        Thread.sleep(1000);   //thread1线程堵塞1秒,然后往下执行
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("hello thread1");
                }
            });
            thread1.start();
            
            try {
                Thread.sleep(5000);   //main线程堵塞5秒,然后往下执行,把isQuit变成true
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            isQuit = true;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    代码运行结果图
    从运行结果图就可以看出来,main线程堵塞5秒后把isQuit标志位变成true,然后thread线程终止运行。

    上面的代码并不够严谨。

    2)使用Thread中内置的一个标志位来进行判定
    可以通过:
    使用 Thread.interrupted() ,这是一个静态的方法。
    使用Thread.currentThread().isInterrupted() 这是实例方法,其中currentThread能够获取当前线程的实例。

    public class TestDome10 {
        public static void main(String[] args) {
            Thread thread1 = new Thread(()->{
            //其中currentThread能够获取当前线程的实例,isInterrupted()获取实例成员的标志位。
               while(!Thread.currentThread().isInterrupted()){
                   System.out.println("hello thread1");
                   try {
                       Thread.sleep(1000);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                       System.out.println("执行收尾工作");
                       break;   //中断程序
                   }
               }
            });
            thread1.start();
    
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //调用这个方法,可能产生两种情况:
            //1)如果t线程是处于就绪状态,就是设置线程标志位为true。
            //2)如果t线程处于堵塞状态(休眠了),就会触发一个interruptException异常。
            thread1.interrupt();   //由于thread1这个线程长时间处于休眠状态,所以就会出现中断异常。
            		//虽然出现了中断异常但是程序并不会停下来。想要程序终止,还要在catch 中加上break。
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    执行结果图

    Thread.interrupted()这个方法判定的标志位是Thread的static成员(一个程序只有一个标志位),如果多个线程同时用一个标志位就会产生影响。

    Thread.currentThread().isInterrupted()这个方法是实例方法的标志位,每一个线程都有一个自己的标志位,这个是普通的成员方法,每一个示例都有一个自己的标志位。

    5 等待一个线程-join()

    多线程之间,调度顺序是不确定的
    线程之间的执行是按照调度器来安排的,这个过程可以视为是“随机的”,有的时候我们需要能够控制线程之间的顺序。

    线程等待,就是其中一种,控制线程执行顺序的手段,此处的线程等待,主要是控制线程结束的先后顺序。

    调用join的时候,如果在A线程里面调用的join,A线程就会堵塞等待。要等到调用join的那个B线程里面的run执行完成,才能继续执行A线程里面的程序。

    public class TestDome11 {
        public static void main(String[] args) {
            Thread thread = new Thread(()->{    //创建线程
                while(true){
                    System.out.println("hello thread1");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            thread.start();
            try {
                thread.join();   //代码执行这里,就要等待thread线程中run函数结束。
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    上面这个代码,其中thread线程是一直执行的,所以main线程执行到thread.join就会进入堵塞状态(暂时无法在CPU上执行),等待thread这个线程执行完毕,main函数就再次往下执行代码。
    这样就在一定程度上干预了,这两个线程的执行顺序。

    join操作在默认的情况下,是会一直等待,这样就很不合理。
    所以join提供了另外一个版本,可以控制执行等待的时间,最长等待多久,如果等不到登了,继续向下执行。

    t.join(10000); —— 这个代码意思是,最长等待10秒钟,如果超出这个时间就不等了。

    6 获取当前线程引用

    Thread.currentThread() —— 就能够获得当前线程的引用(Thread实例的引用)
    哪个线程调用的这个currentThread,就获取到的是那个线程的实例。

    public class TestDome12 {
        public static void main(String[] args) {
            Thread thread = new Thread(){
                @Override
                public void run() {
                //获取thread 线程的引用,并使用引用获得名字
                    System.out.println(Thread.currentThread().getName());
                }
            };
            thread.start();
            //获取thread 线程的引用,并使用引用获得名字
            System.out.println(Thread.currentThread().getName());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    代码运行结果图
    上面代码使用匿名内部类创建的线程,其中的Thread.currentThread().getName()可以换成this.getName。
    但是如果是使用lambad表达式和Runnable接口来创建线程,就不能使用this.getName,因为this获取的就是Runnable或者lambad的引用了,这时就只能使用Thread.currentThread().getName()来获取当前线程的引用和名称。

    7 休眠当前线程

    线程休眠使用sleep这个函数。

    休眠做了什么
    如果是一个进程有多个线程,每一个线程就会有一个PCB。
    这样一个进程就对应了一组PCB了。

    PCB上有一个字段tgroupld,这个id其实就相当于进程的id,同一个进程的tgroupld是相同的。
    在这里插入图片描述
    系统中会有一个就绪队列和一个阻塞队列。
    如果某个线程调用了sleep方法,这个PCB就会进入到阻塞对列之中。

    操作系统在调度线程的时候,从就绪队列中挑选合适的PCB到CPU中执行。阻塞队列就只能等着,等到睡眠结束了,就回到绪队列中。

  • 相关阅读:
    关于动态规划算法
    python高级面试题
    邮件功能-python中的SMTP协议邮件发送
    消息队列概述
    SpringBoot中读取配置文件的几个注解
    在算法研究过程中如何进行算法创新
    SSM整合
    分享图片或链接到抖音
    openwrt RK3568_EVB移植
    UI自动化测试框架搭建-标记性能较差用例
  • 原文地址:https://blog.csdn.net/IT_Infector/article/details/125578981