• 三、Thread 类和Runnable 接口详解





    一、Thread 类

            了解如何使用Thread 类实现多线程之后,继续学习Thread 类实现多线程之后的相关功能及方法。其中的一些方法是 static 的,由类名直接调用,通常都是在那个线程中执行,这些静态的方法就指定的那个线程。



    1、操作线程名称的方法

    • 构造方法(实现 Runnable 接口时候使用)
      • public Thread(Runnable target,String name); 创建线程时设置线程名称。
    • 成员方法
      • public final void setName(String name); 设置线程的名称。
      • public final String getName(); 获取线程的名称。

    • Demo 代码示例:

      public class TestThread extends Thread{
      
      @Override
      public void run() {
          for (int i = 1; i <= 10; i++) {
              System.out.println("我正在编写多线程代码"+ i);
          }
      }
      
      //程序主线程 main 线程
      public static void main(String[] args) {
      
          //创建子类对象
          TestThread thread = new TestThread();
      
          // 设置线程名称
          thread.setName("姚青新创建的线程");
          
          //调用 start() 方法开启线程
          thread.start();
      
          for (int i = 1; i <= 10; i++) {
              System.out.println("我正在学习多线程"+ i);
          }
      
      	// 获取线程名称
          System.out.println(thread.getName());
         }
      }
      
      • 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

    运行结果:
    在这里插入图片描述




    2、获取当前正在执行的线程对象:currentThread()

    • public static Thread currentThread(); 返回当前正在执行的线程对象

      • 获取当前线程对象
        • Thread.currentThread();
      • 获取当前线程对象名称
        • Thread.currentThread().getName();
    • Demo代码示例:

       public class TestThread extends Thread{
      
           @Override
           public void run() {
      
              // 获取start()方法创建出来的线程对象
              System.out.println("start() 创建的线程对象:"+Thread.currentThread());
      
              // 获取start()方法创建出来的线程对象名称
              System.out.println("start() 创建的线程对象名称:"+Thread.currentThread().getName());
          }
      
           public static void main(String[] args) {
              TestThread thread = new TestThread();
              thread.setName("姚青新创建的线程");
              thread.start();
      
              //获取 主线程对象
              System.out.println("主线程对象:"+Thread.currentThread());
      
              // 获取main()主线程对象名称
              System.out.println("主线程对象名称"+Thread.currentThread().getName());
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24

    运行结果:
    在这里插入图片描述

             在使用这个方法的时候需要注意一点,该方法固定的写法就是 Thread.currentThread(); 放在那个线程中执行这个方法就是指定的那个线程。
             这个写法主要作用就是获取当前线程的线程对象,获取线程对象之后还可以继续对该线程对象的一些状态进行操作。



    3、线程的休眠状态和中断状态控制


    1. public static void sleep(long millis); 线程休眠
      • 休眠以毫秒为单位:millis
    2. public final void stop(); 中断线程
      • 使用 stop() 后该线程就停止了,不再继续执行,并且线程无法恢复,可能会造成线程不安全问题,不建议使用
    3. public void interrupt(); 中断线程
      • 使用 interrupt() 会终止线程的状态,还会继续执行run方法里面的代码,且线程可以恢复,较为安全,推荐使用
    4. public boolean isInterrupted(); 用来判断当前线程是否为中断状态
    5. public static boolean interrupted(); 用来恢复线程的中断状态
      • 检验当前线程是否是中断状态,如果是中断状态,返回 true ,并将线程重新恢复成流通状态。如果不是中断状态,返回 false,直接退出方法。

    Demo 代码示例:

     public class TestThread extends Thread{
    
        @Override
        public void run() {
            System.out.println("这里是 start() 创建的新线程");
    
            try {
                // 将线程休眠五秒
                Thread.sleep(5000);
                System.out.println("将线程休眠五秒");
            } catch (InterruptedException e) {
                // 如果线程休眠出现异常,就将线程中断
                Thread.currentThread().interrupt();
            }
        }
    
        public static void main(String[] args) {
            TestThread thread = new TestThread();
            thread.start();
    
            System.out.println("这里是主线程");
    
            Thread.currentThread().interrupt();
            System.out.println("将主线程中断");
    
            //判断运行当前线程是否是中断状态
            System.out.println("检验当前线程是否是中断状态:"+Thread.currentThread().isInterrupted());
    
            // 将主线程重新连接
            System.out.println("当前线程是否需要恢复中断状态:"+Thread.interrupted());
            System.out.println("当前线程是否需要恢复中断状态:"+Thread.interrupted());
        }
    }
    
    • 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
    • 30
    • 31
    • 32
    • 33

    运行结果:
    在这里插入图片描述

             通过运行结果可以看出几点:
    1、将主线程中断之后,其他线程依然正常执行,也就是说每条线程都是独立的。
    2、将当前线程休眠时,当前的线程中的内容同时休眠,休眠倒计时结束线程正常运行。



    4、线程优先执行(线程加入):join()


    • public final void join(); 当通过线程对象调用该方法时,线程就会呈现堵塞状态,只有调用该方法的线程可以流通。直到线程对象的 run() 执行完毕,线程的堵塞状态结束。

      • Demo 代码示例:
      public class ThreadJoin extends Thread {
          @Override
          public void run() {
              for (int x = 0; x < 5; x++) {
                  System.out.println(getName() + ":" + x);
              }
          }
      
          public static void main(String[] args) {
              ThreadJoin tj1 = new ThreadJoin();
              ThreadJoin tj2 = new ThreadJoin();
              ThreadJoin tj3 = new ThreadJoin();
      
              tj1.setName("皮卡丘");
              tj2.setName("可达鸭");
              tj3.setName("呱呱");
      
              tj1.start();
      
              try {
              	// 将线程切换成堵塞状态,只执行 tj1 线程,run() 执行完成之后,堵塞结束
                  tj1.join();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              tj2.start();
              tj3.start();
         }
      }
      
      • 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

    运行结果:可以看出,皮卡丘是先执行的,当这个线程执行完成之后,其他两个线程同步执行。
    在这里插入图片描述




    5、线程延后执行(线程礼让): yield()


    • public final void yield(); 线程礼让
      • yield() 的作用就是让步,当多个线程同时执行时,调用该方法的线程会将当前的线程执行状态让出来,和其他线程处于同一个起跑线上。但是线程的执行顺序无法控制,可能是其他线程,也可能是调用 yield() 的线程。
      • 使用该方法,当前线程会放弃现有的CPU资源,和其他线程一起去抢夺现有的CPU资源,但是这样做会让程序运行花费更多的时间。

    Demo 代码示例:不调用 yield() 方法看运行的时间差

    	public class ThreadYield extends Thread{
    	
    	    int count = 0;
    	
    	    @Override
    	    public void run() {
    	        //获得的是自1970-1-01 00:00:00.000 到当前时刻的时间距离,类型为long。
    	        long beginTime =System.currentTimeMillis();
    	        for (int i = 0 ;i<= 50000000; i++) {
    	            //Thread.yield();
    	            count = count +(i+1);
    	        }
    	        long endTime = System.currentTimeMillis();
    	
    	        // 用第二次获取到的时间距离减第一次获取到的时间距离
    	        System.out.println("Time spent by the program is :" +(endTime-beginTime)+"ms");
    	    }
    	
    	    public static void main(String[] args) {
    	        ThreadYield thread = new ThreadYield();
    	
    	        thread.start();
    	    }
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    运行结果:

    在这里插入图片描述



    Demo 代码示例:调用 yield() 方法看运行的时间差

    	public class ThreadYield extends Thread{
    	
    	    int count = 0;
    	
    	    @Override
    	    public void run() {
    	        //获得的是自1970-1-01 00:00:00.000 到当前时刻的时间距离,类型为long。
    	        long beginTime =System.currentTimeMillis();
    	        for (int i = 0 ;i<= 50000000; i++) {
    	            Thread.yield();
    	            count = count +(i+1);
    	        }
    	        long endTime = System.currentTimeMillis();
    	
    	        // 用第二次获取到的时间距离减第一次获取到的时间距离
    	        System.out.println("Time spent by the program is :" +(endTime-beginTime)+"ms");
    	    }
    	
    	    public static void main(String[] args) {
    	        ThreadYield thread = new ThreadYield();
    	
    	        thread.start();
    	    }
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    运行结果:

    在这里插入图片描述

             通过两次运行结果可以看出程序运行的时间差别还是很大的。调用 yield() 运行的时间明显延长。


    6、守护线程和用户线程的设置:setDaemon()


    • public final void setDaemon(boolean on); 设置守护线程和用户线程
      • java 中线程分为两种类型:用户线程和守护线程。通过 Thread.setDaemon(false); 设置为用户线程;通过 Thread.setDaemon(true); 设置为守护线程。如果不设置次属性,默认为用户线程。

      • 该方法必须在用户线程执行之前执行

        • 用户线程和守护线程的区别:

          1. 主线程结束后,用户线程还会继续运行,JVM是存活状态;
          2. 主线程结束后,如果没有用户线程运行,守护线程和JVM便都结束运行。
          3. 主线程结束后,如果有用户线程运行,守护线程和JVM便继续为用户线程服务。
          4. 垃圾回收机制便是典型的守护线程,用户线程存在时,便会回收用户线程制造出来的垃圾,用户线程结束后,垃圾回收机制也结束。

    Demo 代码示例:

    public class ThreadsetDaemonDemo extends Thread {
        @Override
        public void run() {
            for (int x = 0; x < 10; x++) {
                System.out.println(getName() + ":" + x);
            }
        }
    
        public static void main(String[] args) {
            ThreadDaemon td1 = new ThreadDaemon();
            ThreadDaemon td2 = new ThreadDaemon();
    
            td1.setName("皮卡丘");
            td2.setName("喷火龙");
    
            // 设置守护线程
            td1.setDaemon(true);
            td2.setDaemon(true);
    
            td1.start();
            td2.start();
    
            Thread.currentThread().setName("小智");
            for (int x = 0; x < 5; x++) {
                System.out.println(Thread.currentThread().getName() + ":" + x);
            }
            
        }
    }
    
    
    • 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
    • 30

             运行结果:通过结果可以看出,用户线程一共执行了五次,守护线程也是执行了五次。但是其实守护线程是设置成10次的,因为用户线程执行结束了,所有守护线程自行离开,所有接下来的程序自然也就不继续执行了。

    在这里插入图片描述



    二、Runnable 接口

             在创建多线程时候推荐使用实现 Runnable 接口的方式,该接口中只有一个 run() 的抽象方法,我们在使用时候只需要实现该抽线方法即可。然后通过实例化 Thread 类,调用 Thread 类的构造方法,然后将我们创建的线程类对象传入该构造方法,并且可以给该线程设置名称,然后调用 Thread 类对象调用 start() 创建和启动线程,从而实现多线程的过程。这是一种代理模式,可以很好的降低程序的耦合。
            并且因为接口可以多实现,这就避免了单继承的局限性,灵活便捷,方便同一个对象被多线程使用。

    Demo 代码的复用示例:

    public class TestRunnable implements Runnable{
    
        //run()方法线程
        @Override
        public void run() {
    
            // 获取新创建的线程名称
            String str = Thread.currentThread().getName();
            System.out.println("姚青创建的线程:"+str);
    
    
            try {
                // 将线程休眠4秒
                Thread.sleep(4000);
                System.out.println(Thread.currentThread().getName()+"休眠4秒");
            } catch (InterruptedException e) {
                // 如果线程休眠时出现线程异常,就将线程中断
                Thread.currentThread().interrupt();
            }
        }
    
        //程序主线程   main线程
        public static void main(String[] args) {
    
            // 创建 Runnable 接口实现类对象
            TestRunnable tr = new TestRunnable();
    
            // 创建线程对象,设置线程名称,并通过线程对象来开启线程,这种启动线程的方式是代理的方式
            Thread thread = new Thread(tr, "姚青创建的线程");
            thread .start()// 获取当前线程的线程名称
            String str = Thread.currentThread().getName();
            System.out.println("主线程:" + str);
    
            // 将当前线程中断
            Thread.currentThread().interrupt();
            System.out.println(Thread.currentThread().getName()+"线程中断了!");
    
            // 判断当前线程是否是中断状态
            if (Thread.currentThread().isInterrupted() == true) {
                // 如果是中断状态就重新链接
                Thread.interrupted();
                System.out.println(Thread.currentThread().getName()+"线程重新链接了!");
            }
    
        }
    }
    
    
    • 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
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    运行结果:

    在这里插入图片描述

  • 相关阅读:
    三分钟搭建一个自己的 ChatGPT (从开发到上线)
    基于ElasticSearch存储海量AIS数据:时空立方体索引篇
    全国双非院校考研信息汇总整理 Part.7
    懒人系列--文件上传之OSS使用案例
    浅谈用匈牙利算法寻找二分图的最大匹配
    matlab实现3个y轴绘图
    2023-09-11工作错题集:绝了!BigDecimal计算百分数保留2位小数
    SpringCloud - Nacos 结合 K8s 优雅关闭服务(平滑升级)
    【STM32】定时器与PWM的LED控制
    Ubantu18.04系统安装Hexagon SDK教程
  • 原文地址:https://blog.csdn.net/shiyu_951/article/details/126226538