Thread 类及常见方法

Posted 一朵花花

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Thread 类及常见方法相关的知识,希望对你有一定的参考价值。

Thread 类及常见方法

Thread 的常见构造方法:

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

举例:

// Thread(String name) 
public class ThreadDemo5 
    public static void main(String[] args) 
        Thread t = new Thread("这是一个线程的名字,可以起的很长~")
            @Override
            public void run() 
               while (true)

               
            
        ;
        t.start();
    

执行,在 jconsole 中查看:

Thread 的几个常见属性

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

演示举例:

public class ThreadDemo6 
    public static void main(String[] args) throws InterruptedException 
        Thread t = new Thread("HUAHua线程")
            @Override
            public void run() 
                for (int i = 0; i < 10; i++) 
                    //Thread.currentThread() 获取到当前线程的实例,当前代码中,相当于 this.
                    System.out.println(Thread.currentThread().getName());
                    //  效果和上行代码一样
//                    System.out.println(this.getName());
                    try 
                        Thread.sleep(1000);
                     catch (InterruptedException e) 
                        e.printStackTrace();
                    
                
                // run 方法的执行过程,就代表着系统内线程的生命周期
                // run 方法执行中,内核的线程就存在
                // run 方法执行完毕,内核中的线程就随之销毁
                System.out.println("线程要退出了");
            
        ;
//        t.start();

        // 只要线程创建完毕,下面这些属性就不变了,除非显式修改
        System.out.println(t.getName());
        System.out.println(t.getPriority());
        System.out.println(t.isDaemon());
        System.out.println(t.getId());

        // 这些属性,会随着线程的运行过程而发生改变
        System.out.println(t.isAlive());
        System.out.println(t.isInterrupted());
        System.out.println(t.getState());

        t.start();

        while (t.isAlive())
            System.out.println("HUAHua线程正在运行.....");
            System.out.println(t.getState());
            System.out.println(t.isInterrupted());
            Thread.sleep(300);
        
    

补充:
Thread.currentThread( ),即: 获取到当前线程的实例
在当前代码中,相当于 this.
但,不是所有情况都可以使用this
注意:
若使用继承 Thread 的方式来创建线程,这个操作就和 this 是一样的
若使用 Runnable 的方式或者 lambda 的方式,此时就不能使用 this

此时,运行程序,输出结果:
取前小部分输出结果:

启动一个线程 — start( )

线程对象被创建出来并不意味着线程就开始运行了
调用 start 方法,才真的在操作系统的底层创建出一个线程
创建实例,和重写 run 方法,是告诉线程要做什么,而调用 start 方法,才是真正开始执行

中断一个线程

中断,就是让一个线程结束 — 结束,可能有两种情况:
①已经把任务执行完了;即:让线程 run 执行完(比较温和)
②任务执行了一半,被强制结束,即:调用线程的 interrupt 方法(比较激烈)

常见的线程中断有以下两种方式:

  1. 通过共享的标记来进行沟通
private static boolean isQuit = false;

public static void main(String[] args) throws InterruptedException 
    // 创建一个线程
    Thread t = new Thread()
        @Override
        public void run()
            while (!isQuit)
                System.out.println("交易继续...");
                try 
                    Thread.sleep(500);
                
                catch (InterruptedException e) 
                    e.printStackTrace();
                
            
            System.out.println("交易被终止!");
        
    ;
    t.start();

    Thread.sleep(5000);
    System.out.println("发现内鬼,终止交易!");
    isQuit = true;

输出结果:


上述方式的结束方式比较温和
当标记位被设置之后,等到当前这次循环执行完了之后,再结束线程
例如: 当线程执行到 sleep 的时候,已经 sleep 100ms 了,此时 isQuit 被设置为 true,当前线程不会立刻退出,而是会继续 sleep,把剩下的 400ms sleep 完,才会结束线程

  1. 调用 interrupt( ) 方法来通知
public static void main(String[] args) throws InterruptedException 
    // 创建一个线程
    Thread t = new Thread()
        @Override
        public void run()
            // 此处直接使用线程内部的标记为来判定
            while (!Thread.currentThread().isInterrupted())
                System.out.println("交易继续...");
                try 
                    Thread.sleep(500);
                 catch (InterruptedException e) 
                    e.printStackTrace();
                
            
            System.out.println("交易被终止!");
        
    ;
    t.start();
    Thread.sleep(1000);
    System.out.println("发现内鬼,终止交易!");
    t.interrupt();

输出结果:


interrupt 本质上是给该线程触发一个异常 InterruptedException,此时,线程内部就会收到这个异常,具体针对这个异常如何处理,这是 catch 内部的事情
例如,上边代码中,catch 中,只是打印了调用栈,并没有真正的结束循环,故应该再加一个 break 结束循环

如果 catch 中没有 break,相当于忽略了异常
如果有 break,则触发异常就会导致循环结束,从而线程也结束


Thread 收到通知的方式有两种:

  • 如果线程因为调用 wait / join / sleep 等方法而阻塞,则以 InterruptedException 异常的形式通知,清除中断标志,当出现 InterruptedException 的时候,要不要结束线程取决于 catch 中代码的写法. 可以选择忽略这个异常, 也可以跳出循环结束线程.
  • 否则,只是内部的一个中断标志被设置,thread 可以通过
    Thread.interrupted( ) 判断当前线程的中断标志被设置,清除中断标志(此方法为借助类里的静态方法判断)
    Thread.currentThread( ).isInterrupted( ) 判断指定线程的中断标志被设置,不清除中断标志(此方法为借助实例,再拿实例里的方法进行判断),这种方式通知收到的更及时,即使线程正在 sleep 也可以马上收到

Thread.interrupted( )方式:

public static void main(String[] args) 
   Thread t = new Thread()
       @Override
       public void run() 
           for (int i = 0; i < 10; i++) 
               System.out.println(Thread.interrupted());
           
       
   ;
   t.start();
   t.interrupt();

输出结果:
.

Thread.currentThread( ).isInterrupted( )方式:

public static void main(String[] args) 
   Thread t = new Thread()
       @Override
       public void run() 
           for (int i = 0; i < 10; i++) 
               System.out.println(Thread.currentThread().isInterrupted());
           
       
   ;
   t.start();
   t.interrupt();

输出结果:
.

思考: 如果没有 sleep,新线程能否继续输出?
这个不确定,多线程之间是抢占式执行的,如果主线程没有 sleep,此时接下来 CPU 是执行主线程的 isQuit = true,还是新线程的 while 循环,这个不确定(都有可能)

对于新线程来说,run 方法执行完,线程就结束了
对于主线程来说,main 方法执行完,主线程就结束了

等待一个线程 — join( )

线程和线程之间是并发执行的关系,多个线程之间,谁先执行谁后执行,谁执行到哪里让出 CPU,作为程序员是完全无法感知的,是全权由系统内核负责
例如: 创建一个新线程的时候,此时接下来是主线程继续执行,还是新线程继续执行,这是不好保证的 (这也是 "抢占式执行"的重要特点 )

可以通过下面的代码验证:

public static void main(String[] args) throws InterruptedException 
    Thread t = new Thread()
        @Override
        public void run() 
            while (true)
                System.out.println("我是新线程!");
                try 
                    Thread.sleep(100);
                 
                catch (InterruptedException e) 
                    e.printStackTrace();
                
            
        
    ;
    t.start();
    while (true)
        System.out.println("我是主线程!");
        Thread.sleep(100);
    

截取部分输出结果:

虽然我们没办法控制哪个线程先走,哪个线程后走,但是我们可以控制,让哪个线程先结束,哪个线程后结束 — 借助线程等待

join 方法,执行 join 方法的线程就会阻塞,一直阻塞到对应线程执行结束之后,才会继续执行
存在的意义:为了控制线程结束的先后顺序
多线程的一个场景:
例如要进行一个复杂运算,主线程把任务分成几份,每个线程计算自己的一份任务
当所有任务都被分别计算完毕后,主线程再来进行汇总(就必须保证主线程是最后执行完的线程)

举例:

public static void main(String[] args) throws InterruptedException 
    Thread t1 = new Thread()
        @Override
        public void run() 
            for (int i = 0; i < 10; i++) 
                System.out.println("我是线程1");
                try 
                    Thread.sleep(1000);
                
                catch (InterruptedException e) 
                    e.printStackTrace();
                
            
        
    ;
    Thread t2 = new Thread()
        @Override
        public void run() 
            for (int i = 0; i < 10; i++) 
                System.out.println("我是线程2");
                try 
                    Thread.sleep(1000);
                
                catch (InterruptedException e) 
                    e.printStackTrace();
                
            
        
    ;
    t1.start();
    t1.join();
    t2.start();
    t2.join();

输出结果:


由于 t1 的 join 放在了 t2 的 strat 之前,意味着此时还没有执行线程2,t1 这里就已经阻塞等待了,一直到 t1 结束,线程2才会继续往下,开始执行

若交换 t1.join( ) 和 t2.start( ); 的位置,输出结果如下:


如果线程已经结束了,才调用 join,此时 join 也会立刻返回

获取当前线程的引用

public static Thread currentThread( ) — 返回当前线程对象的引用

代码示例:

public static void main(String[] args) 
	Thread thread = Thread.currentThread();
	System.out.println(thread.getName());

休眠当前线程

休眠是让当前线程进入阻塞

关于线程休眠:


以上是关于Thread 类及常见方法的主要内容,如果未能解决你的问题,请参考以下文章

Java中Thread类及常见方法

Java中Thread类及常见方法

多线程(二):Thread 类及常见方法

多线程的Thread 类及方法

Java多线程——Thread 类及常见方法和线程的基本操作

多线程多线程基础知识