【并发编程系列1】Thread生命周期及interrupted()作用分析

什么是线程

线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,一个进程中可以并发多个线程,每条线程并行执行不同的任务。线程的出现是为了更加合理的利用CPU资源

如何创建线程

创建线程有2种方法,一种是继承Thread类,另一种就是实现Runable

继承Thread类

新建一个类继承Thread类并重写run()方法:

package com.zwx.thread;

public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("111");
    }
}
package com.zwx.thread;

public class CreateThreadDemo{
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}

实现Runnable接口

新建一个类实现Runnable接口并重写run()方法:

package com.zwx.thread;

public class MyThread1 implements Runnable {
    @Override
    public void run() {
        System.out.println("111");
    }
}
package com.zwx.thread;

public class CreateThreadDemo{
    public static void main(String[] args) {
        MyThread1 myThread1 = new MyThread1();
        Thread thread = new Thread(myThread1);
        thread.start();
    }
}

创建线程简写形式

上面是线程的两种基本形式,我们有时候创建线程也可以用下面的简写形式进行直接创建:

package com.zwx.thread;

public class CreateThreadDemo{
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("我是t1");

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

        new Thread(()->{
            System.out.println("我是t2");
        },"t2").start();
    }
}

线程的生命周期

线程中总共有六种状态。

NEW

NEW表示线程尚未启动的状态,即当我们new 一个线程时,线程就是NEW状态。

package com.zwx.thread;
 
public class ThreadState {
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            System.out.println("我是t1线程");
        });
        System.out.println(t1.getState());
    }
}

上面输出的状态就是:NEW

RUNNABLE

可运行状态或者说就绪状态。当我们调用thread.start()之后,线程就进入RUNNABLE状态,注意,调用start()方法之后线程并不一定会马上执行,还要看操作系统的调度才能执行。

package com.zwx.thread;

public class ThreadState {
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            System.out.println("我是t1线程");
        });
        System.out.println(t1.getState());//NEW
        t1.start();
        System.out.println(t1.getState());//RUNNABLE
    }
}

上面的第二个输出语句中线程t1的状态就是RUNNABLE状态

TERMINATED

package com.zwx.thread;

public class ThreadState {
   public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            System.out.println("我是t1线程");
        });
        System.out.println(t1.getState());//NEW
        t1.start();
        Thread.sleep(1000);
        System.out.println(t1.getState());//TERMINATED
    }
}

t1线程启动之后,我们把主线程休眠1s,t1执行完毕之后再看t1的状态就是TERMINATED

WAITING

等待状态,当调用wait(),join()或者park()方法时,线程就会处于WAITING等待状态

package com.zwx.thread;

public class ThreadState {
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            while (true){
                synchronized (ThreadState.class){
                    System.out.println("我是t1线程");
                    try {
                        ThreadState.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"t1").start();

        new Thread(()->{
            while (true){
                System.out.println("我是t2线程");
                try {
                    Thread.sleep(20000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"t2").start();
    }
}

运行之后通过jps和jstack命令可以看到t2线程就是TIMED_WAITING状态,而t1就是WAITING状态
在这里插入图片描述

TIMED_WAITING

和上面WAITING状态一样,只是有一个等待时间,一般调用如下方法时:sleep(),wait(),join(),LockSupport.parkNanos(),LockSupport.parkUntil(),而且方法带上时间则线程会出现这个状态。
示例同上面的WAITING中的示例

BLOCKED

线程等待锁时被阻塞状态。一个线程正在等待进入一个同步代码块时线程会被阻塞或者被调用之后重入一个同步代码块时被阻塞。

package com.zwx.thread;

public class ThreadState {
    static class BlockDemo extends Thread{
        @Override
        public void run() {
            synchronized (BlockDemo.class){
                while (true){

                }
            }
        }
    }
    public static void main(String[] args) {
        new Thread(new BlockDemo(),"t3").start();
        new Thread(new BlockDemo(),"t4").start();
    }
}

上面示例中t3和t4争抢同一把锁进入同步代码块,那么一定会有一个线程处于BLOCKED状态:
在这里插入图片描述

线程生命周期图

经过上面的分析,我们可以得到一张线程的生命周期简图:
在这里插入图片描述

interrupted()作用

线程当中有三个关于中断的方法:

  • interrupt():打一个中断标记,并没有实际去中断一个线程。
  • isInterrupted():如果调用了interrupt()方法,则这里会输出true,表示当前线程被中断过。
  • interrupted():静态方法。如果调用了interrupt()方法,则这里会输出true,表示当前线程被中断过,但是这个方法比上面的isInterrupted()方法多做了一件事,那就是线程复位,也就是说,连续两次调用interrupted()方法后,第一次为true,第二次就会变回false。
    我们来看下面一个例子:
package com.zwx.thread.baseapi;

public class ThreadInterruptDemo {

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            int i=0;
            while(Thread.interrupted()){
                System.out.println("我是线程" + Thread.currentThread().getName() + ":" + Thread.interrupted());//false
            }
        },"t1");
        t1.start();
        t1.interrupt();
    }
}

这里的输出结果为false,这是因为判断条件里面调用了一次为true,然后对线程进行了复位,所以run()方法内输出了false。

实际上: isInterrupted()和 interrupted()方法最终都调用的是下面这个本地方法,只不过isInterrupted()方法默认传了false(不复位),而 interrupted()默认传了true(复位)
在这里插入图片描述
线程中断标记有什么用呢?

这是因为我们并不建议直接去中断一个线程,假如一个线程正在执行一系列操作,那么强行中断,可能会引起其他问题(这也是stop()方法之所以标记为过期的原因),所以中断只是打一个标记,然后由线程自己去获取中断标记,再决定要不要中断。

就比如我们上面的while循环中,我们获取到中断标记,然后内部可以决定是继续执行循环体还是直接返回终止线程。

为什么需要对线程复位?
我们设想这么一种场景,假如我们规定收到3次线程中断要求的时候,即使线程没有执行完毕这时候也需要直接返回,那么线程如果不复位,我们就没办法知道当前线程进行过多少次中断(不考虑同时多次中断的情况),因为中断过一次,一直是true;而有了线程复位,我们就只需要判断3次都是true就说明至少被中断过3次。

当interrupt()遇到sleep()

我们都知道,每次调用sleep()的时候都需要抛出一个InterruptedException异常
老规矩,还是来看一个例子:

package com.zwx.thread.baseapi;

public class InterruptSleepThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().isInterrupted());//false
            }
        },"t1");
        t1.start();
        Thread.sleep(1000);
        t1.interrupt();
    }
}

这里输出结果为false,这就说明当我们一个休眠的线程被中断后,抛出异常的同时也会对线程的中断标记进行复位,这一点是需要注意的

currentThread()和this的区别

我们还是先来看下面的一个例子:

package com.zwx.thread.baseapi;

public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());//t1
        System.out.println(this.getName());//Thread-0
    }
}
package com.zwx.thread.baseapi;

public class CurrentThreadAndThis {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        Thread t1 = new Thread(myThread,"t1");
        t1.start();
    }
}

输出结果如下图:
在这里插入图片描述
为什么this.getName()不会输出t1,很多人的理解中这两种应该是等价的。其实不然,this指的是当前对象,也就是MyThread对象,而currentThread()才是指的当前线程t1。

首先我们看第一行代码:

MyThread myThread = new MyThread();

因为MyThread继承了Thread,所以会调用默认的无参构造器:
在这里插入图片描述
这时候会产生一个name为:Thread-0的线程。

然后第二行代码我们再把这个线程传进去重新new一个线程又会发生什么?这时候调用的是另一个有参构造器:
在这里插入图片描述
我们发现,myThread作为target被传入进去,断点后发现我们最开始创建的线程Thread-0被赋在t1对象的traget中:
在这里插入图片描述

实际上这产生了一个现象,最后的结果是t1线程中的target线程才是我们的myThread线程,也就是最后执行run()方法的是target中的对象(myThread)去执行。所以this获取到的名字是target中线程的名字,而currentThread()获取到的是当前线程t1的名字。

那如果我们不创建t1线程,直接调用myThread.start()呢,这时候的结果两个都会输出Thread-0,因为这时候this对象就是myThread,当前线程是myThread。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章