什么是Java中Thread构造方法

发布时间:2021-10-11 21:56:49 作者:iii
来源:亿速云 阅读:138

这篇文章主要讲解了“什么是Java中Thread构造方法”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“什么是Java中Thread构造方法”吧!

线程生命周期

五个阶段

线程生命周期可以分为五个阶段:

NEW

new创建一个Thread对象时,但是并没有使用start()启动线程,此时线程处于NEW状态。准确地说,只是Thread对象的状态,这就是一个普通的Java对象。此时可以通过start()方法进入RUNNABLE状态。

RUNNABLE

进入RUNNABLE状态必须调用start()方法,这样就在JVM中创建了一个线程。但是,线程一经创建,并不能马上被执行,线程执行与否需要听令于CPU调度,也就是说,此时是处于可执行状态,具备执行的资格,但是并没有真正执行起来,而是在等待被调度。

RUNNABLE状态只能意外终止或进入RUNNING状态。

RUNNING

一旦CPU通过轮询或其他方式从任务可执行队列中选中了线程,此时线程才能被执行,也就是处于RUNNING状态,在该状态中,可能发生的状态转换如下:

BLOCKED

也就是阻塞状态,进入阻塞状态的原因很多,常见的如下:

处于BLOCKED状态时,可能发生的状态转换如下:

TERMINATED

TERMINATED是线程的最终状态,进入该状态后,意味着线程的生命周期结束,比如在下列情况下会进入该状态:

Thread构造方法

构造方法

Thread的构造方法一共有八个,这里根据命名方式分类,使用默认命名的构造方法如下:

命名线程的构造方法如下:

但实际上所有的构造方法最终都是调用如下私有构造方法:

private Thread(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals);

在默认命名构造方法中,在源码中可以看到,默认命名其实就是Thread-X的命令(X为数字):

public Thread() {
    this((ThreadGroup)null, (Runnable)null, "Thread-" + nextThreadNum(), 0L);
}

public Thread(Runnable target) {
    this((ThreadGroup)null, target, "Thread-" + nextThreadNum(), 0L);
}

private static synchronized int nextThreadNum() {
    return threadInitNumber++;
}

而在命名构造方法就是自定义的名字。

另外,如果想修改线程的名字,可以调用setName()方法,但是需要注意,处于NEW状态的线程才能修改。

线程的父子关系

Thread的所有构造方法都会调用如下方法:

private Thread(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals);

其中的一段源码截取如下:

if (name == null) {
    throw new NullPointerException("name cannot be null");
} else {
    this.name = name;
    Thread parent = currentThread();
    SecurityManager security = System.getSecurityManager();
    if (g == null) {
        if (security != null) {
            g = security.getThreadGroup();
        }

        if (g == null) {
            g = parent.getThreadGroup();
        }
    }
}

可以看到当前这里有一个局部变量叫parent,并且赋值为currentThread()currentThread()是一个native方法。因为一个线程被创建时的最初状态为NEW,因此currentThread()代表是创建自身线程的那个线程,也就是说,结论如下:

也就是自己创建的线程,父线程为main线程,而main线程由JVM创建。

另外,Thread的构造方法中有几个具有ThreadGroup参数,该参数指定了线程位于哪一个ThreadGroup,如果一个线程创建的时候没有指定ThreadGroup,那么将会和父线程同一个ThreadGroupmain线程所在的ThreadGroup称为main

关于stackSize

Thread构造方法中有一个stackSize参数,该参数指定了JVM分配线程栈的地址空间的字节数,对平台依赖性较高,在一些平台上:

但是,在一些平台上该参数不会起任何作用。另外,如果设置为0也不会起到任何作用。

Thread API

sleep()

sleep()有两个重载方法:

但是在JDK1.5后,引入了TimeUnit,其中对sleep()方法提供了很好的封装,建议使用TimeUnit.XXXX.sleep()去代替Thread.sleep()

TimeUnit.SECONDS.sleep(1);
TimeUnit.MINUTES.sleep(3);

yield()

yield()属于一种启发式方法,提醒CPU调度器当前线程会自愿放弃资源,如果CPU资源不紧张,会忽略这种提醒。调用yield()方法会使当前线程从RUNNING变为RUNNABLE状态。

关于yield()sleep()的区别,区别如下:

setPriority()

优先级介绍

线程与进程类似,也有自己的优先级,理论上来说,优先级越高的线程会有优先被调度的机会,但实际上并不是如此,设置优先级与yield()类似,也是一个提醒性质的操作:

所以,设置优先级只是很大程度上让某个线程尽可能获得比较多的执行机会,也就是让线程自己尽可能被操作系统调度,而不是设置了高优先级就一定优先运行,或者说优先级高的线程比优先级低的线程就一定优先运行。

优先级源码分析

设置优先级直接调用setPriority()即可,OpenJDK 11源码如下:

public final void setPriority(int newPriority) {
    this.checkAccess();
    if (newPriority <= 10 && newPriority >= 1) {
        ThreadGroup g;
        if ((g = this.getThreadGroup()) != null) {
            if (newPriority > g.getMaxPriority()) {
                newPriority = g.getMaxPriority();
            }

            this.setPriority0(this.priority = newPriority);
        }

    } else {
        throw new IllegalArgumentException();
    }
}

可以看到优先级处于[1,10]之间,而且不能设置为大于当前ThreadGroup的优先级,最后通过native方法setPriority0设置优先级。

一般情况下,不会对线程的优先级设置级别,默认情况下,线程的优先级为5,因为main线程的优先级为5,而且main为所有线程的父进程,因此默认情况下线程的优先级也是5。

interrupt()

interrupt()是一个重要的API,线程中断的API有如下三个:

下面对其逐一进行分析。

interrupt()

一些方法调用会使得当前线程进入阻塞状态,比如:

而调用interrupt()可以打断阻塞,打断阻塞并不等于线程的生命周期结束,仅仅是打断了当前线程的阻塞状态。一旦在阻塞状态下被打断,就会抛出一个InterruptedException的异常,这个异常就像一个信号一样通知当前线程被打断了,例子如下:

public static void main(String[] args) throws InterruptedException{
    Thread thread = new Thread(()->{
        try{
            TimeUnit.SECONDS.sleep(10);
        }catch (InterruptedException e){
            System.out.println("Thread is interrupted.");
        }
    });
    thread.start();
    TimeUnit.SECONDS.sleep(1);
    thread.interrupt();
}

会输出线程被中断的信息。

isInterrupted()

isInterrupted()可以判断当前线程是否被中断,仅仅是对interrupt()标识的一个判断,并不会影响标识发生任何改变(因为调用interrupt()的时候会设置内部的一个叫interrupt flag的标识),例子如下:

public static void main(String[] args) throws InterruptedException{
    Thread thread = new Thread(()->{
        while (true){}
    });
    thread.start();
    TimeUnit.SECONDS.sleep(1);
    System.out.println("Thread is interrupted :"+thread.isInterrupted());
    thread.interrupt();
    System.out.println("Thread is interrupted :"+thread.isInterrupted());
}

输出结果为:

Thread is interrupted :false
Thread is interrupted :true

另一个例子如下:

public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread() {
        @Override
        public void run() {
            while (true) {
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    System.out.println("In catch block thread is interrupted :" + isInterrupted());
                }
            }
        }
    };
    thread.start();
    TimeUnit.SECONDS.sleep(1);
    System.out.println("Thread is interrupted :" + thread.isInterrupted());
    thread.interrupt();
    TimeUnit.SECONDS.sleep(1);
    System.out.println("Thread is interrupted :" + thread.isInterrupted());
}

输出结果:

Thread is interrupted :false
In catch block thread is interrupted :false
Thread is interrupted :false

一开始线程未被中断,结果为false,调用中断方法后,在循环体内捕获到了异常(信号),此时会Thread自身会擦除interrupt标识,将标识复位,因此捕获到异常后输出结果也为false

interrupted()

这是一个静态方法,调用该方法会擦除掉线程的interrupt标识,需要注意的是如果当前线程被打断了:

例子如下:

public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread() {
        @Override
        public void run() {
            while (true) {
                System.out.println(Thread.interrupted());
            }
        }
    };
    thread.setDaemon(true);
    thread.start();
    TimeUnit.MILLISECONDS.sleep(2);
    thread.interrupt();
}

输出(截取一部分):

false
false
false
true
false
false
false

可以看到其中带有一个true,也就是interrupted()判断到了其被中断,此时会立即擦除中断标识,并且只有该次返回true,后面都是false

关于interrupted()isInterrupted()的区别,可以从源码(OpenJDK 11)知道:

public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}

public boolean isInterrupted() {
    return this.isInterrupted(false);
}

@HotSpotIntrinsicCandidate
private native boolean isInterrupted(boolean var1);

实际上两者都是调用同一个native方法,其中的布尔变量表示是否擦除线程的interrupt标识:

join()

join()简介

join()sleep()一样,都是属于可以中断的方法,如果其他线程执行了对当前线程的interrupt操作,也会捕获到中断信号,并且擦除线程的interrupt标识,join()提供了三个API,分别如下:

 例子

一个简单的例子如下:

public class Main {
    public static void main(String[] args) throws InterruptedException {
        List<Thread> threads = IntStream.range(1,3).mapToObj(Main::create).collect(Collectors.toList());
        threads.forEach(Thread::start);
        for (Thread thread:threads){
            thread.join();
        }
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+" # "+i);
            shortSleep();
        }
    }

    private static Thread create(int seq){
        return new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName()+" # "+i);
                shortSleep();
            }
        },String.valueOf(seq));
    }

    private static void shortSleep(){
        try{
            TimeUnit.MILLISECONDS.sleep(2);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

输出截取如下:

2 # 8
1 # 8
2 # 9
1 # 9
main # 0
main # 1
main # 2
main # 3
main # 4

线程1和线程2交替执行,而main线程会等到线程1和线程2执行完毕后再执行。

线程关闭

Thread中有一个过时的方法stop,可以用于关闭线程,但是存在的问题是有可能不会释放monitor的锁,因此不建议使用该方法关闭线程。线程的关闭可以分为三类:

正常关闭

正常结束

线程运行结束后,就会正常退出,这是最普通的一种情况。

捕获信号关闭线程

通过捕获中断信号去关闭线程,例子如下:

public static void main(String[] args) throws InterruptedException {
    Thread t = new Thread(){
        @Override
        public void run() {
            System.out.println("work...");
            while(!isInterrupted()){

            }
            System.out.println("exit...");
        }
    };
    t.start();
    TimeUnit.SECONDS.sleep(5);
    System.out.println("System will be shutdown.");
    t.interrupt();
}

一直检查interrupt标识是否设置为true,设置为true则跳出循环。另一种方式是使用sleep()

public static void main(String[] args) throws InterruptedException {
    Thread t = new Thread(){
        @Override
        public void run() {
            System.out.println("work...");
            while(true){
                try{
                    TimeUnit.MILLISECONDS.sleep(1);
                }catch (InterruptedException e){
                    break;
                }
            }
            System.out.println("exit...");
        }
    };
    t.start();
    TimeUnit.SECONDS.sleep(5);
    System.out.println("System will be shutdown.");
    t.interrupt();
}

volatile

由于interrupt标识很有可能被擦除,或者不会调用interrupt()方法,因此另一种方法是使用volatile修饰一个布尔变量,并不断循环判断:

public class Main {
    static class MyTask extends Thread{
        private volatile boolean closed = false;

        @Override
        public void run() {
            System.out.println("work...");
            while (!closed && !isInterrupted()){

            }
            System.out.println("exit...");
        }

        public void close(){
            this.closed = true;
            this.interrupt();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        MyTask t = new MyTask();
        t.start();
        TimeUnit.SECONDS.sleep(5);
        System.out.println("System will be shutdown.");
        t.close();
    }
}

异常退出

线程执行单元中是不允许抛出checked异常的,如果在线程运行过程中需要捕获checked异常并且判断是否还有运行下去的必要,可以将checked异常封装为unchecked异常,比如RuntimeException,抛出从而结束线程的生命周期。

假死

所谓假死就是虽然线程存在,但是却没有任何的外在表现,比如:

等等,虽然此时线程是存在的,但看起来跟死了一样,事实上是没有死的,出现这种情况,很大可能是因为线程出现了阻塞,或者两个线程争夺资源出现了死锁。

这种情况需要借助一些外部工具去判断,比如VisualVMjconsole等等,找出存在问题的线程以及当前的状态,并判断是哪个方法造成了阻塞。

感谢各位的阅读,以上就是“什么是Java中Thread构造方法”的内容了,经过本文的学习后,相信大家对什么是Java中Thread构造方法这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!

推荐阅读:
  1. 如何使用Java Thread中Sleep()
  2. Java中Runnable与Thread有什么不同

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

thread java

上一篇:PG存储过程是什么

下一篇:如何用SpringSecurity查看登录

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》