Thread 类及常见方法

x33g5p2x  于2021-11-24 转载在 其他  
字(5.7k)|赞(0)|评价(0)|浏览(528)

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());
}

休眠当前线程

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

关于线程休眠:

相关文章