Thread类及常见方法 - JavaEE

目录

 一、Thread类的常见构造方法

二、Thread的几个常见属性

名称getName()

状态getState()

优先级getPriority()

是否后台线程isDaemon()

是否存活isAlive()

三、线程终止

四、等待一个线程 - join()

五、获取当前线程引用

六、休眠当前线程


一、Thread类的常见构造方法

更详细的参考Java标准库文档Java Platform SE 8

方法

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

 注:第三第四个方法的name只是给线程起了个名字,目的是为了方便调试。线程默认的名字为thread-0之类的……


我们执行如下代码:

public class ThreadDemo6 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    System.out.println("hello");
                }
            }
        },"mythread");
        t.start();
    }
}

我们运行之后使用jconsole进行查看,我们可以看到,在线程中就有了一个mythread的线程: 


二、Thread的几个常见属性

属性获取方法
IDgetId()
名称getName()
状态getState()
优先级getPriority()
是否后台线程isDaemon()
是否存活isAlive()
是否被中断isInterrupted()

解释一下其中几个属性:

名称getName()

构造方法里起的名字;

状态getState()

线程状态(Java里线程的状态操作比操作系统原生的状态要更丰富一些);

优先级getPriority()

这个可以获取,也可以设置,但是实际上设置了也没啥用,多线程同时进行很难做出先后顺序;

是否后台线程isDaemon()

与是否是“守护线程”一个意思;

线程有分前台后台:前台线程,会阻止线程结束。前台线程的工作没做完,进程是结束不了的;后台线程,不会阻止进程结束。后台线程工作没做完,进程也是可以结束的。

注:代码里手动创建的线程,默认都是前台的,包括main默认也是前台的。其他的jvm自带的线程都是后台的。我们也可以手动的使用setDaemon设置成后台线程。注意线程是前台还是后台,取决于调用的setDaemon方法做了什么。

我们使用上述代码,添加调用setDaemon方法并设置为true,可以看到运行结果中什么都没有打印就结束了:

 很明显这个t线程是没有执行完的,但是我们的进程仍然结束了,因为当我们把这个线程设置为一个守护线程的时候,那我们的前台线程就剩下main了,main什么时候执行完,我们整个进程就什么时候执行完,所以我们的t线程执行与否就和t无关了。

是否存活isAlive()

判定的是系统里面的一个线程是是否存在。

如图所示,比如说我们有一个应用程序,应用程序里面创建了一个Thread对象t,当我们去调用t.start()方法的时候,就会在我们内核里创建出一个PCB,此时这个PCB才表示一个真正的线程。然后这个线程才开始工作了,在系统里进行参与调度。

所以在真正调用start之前,调用t.isAlive就是false;调用start之后,isAlive就是true。

另外,如果内核里的线程把run干完了,此时线程销毁,PCB随之释放,但是Thread t 这个对象还不一定被释放,此时isAlive也是false。

注:“不一定被释放”:

public class ThreadDemo6 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            //run执行完PCB释放,操作系统里的线程就没了
            @Override
            public void run() {
                System.out.println("hello");
            }
        }, "mythread");
        //同时,t对象仍然存在
        t.start();
        while (true) {
            try {
                Thread.sleep(1000);
                System.out.println(t.isAlive());//false
                //t什么时候不指向当前这个对象,被GC回收时就不存在了
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

t.isAlive为true,使用for循环延长线程执行时间:

 总结isAlive:如果t的run还没跑,isAlive就是false;如果t的run正在,isAlive就是true;如果t的run跑完了,isAlive就是false。

三、线程终止

中断的意思是,不是让线程立即就停止,而是通知线程,你应该要停止了。是否真的停止取决于线程这里具体的代码写法。

使用方式:

(1)使用标志位来控制线程是否要停止;

public class ThreadDemo8 {
    private static boolean flag = true;

    public static void main(String[] args) throws InterruptedException{
        Thread t = new Thread(()->{
            while (flag){
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        Thread.sleep(3000);
        //在主线程里就可以随时通过 flag 变量的取值,来操作 t 线程是否结束
        flag = false;
    }
}

注:这个代码之所以设置flag能够让线程结束,完全取决于t线程内部的代码写法,代码里通过flag控制循环。因此,flag = false这里只是告诉让这个线程结束。这个线程是否要结束,啥时候结束,都是线程内部自己的代码来决定的。

(2)使用Thread自带的标志位,来进行判定。

上一个自定义变量这种方式,是不能够及时相应的,尤其是在sleep休眠时间比较久的时候。所以我们就可以借助Thread自带的标志位来进行判定,这个东西是可以唤醒上面这个sleep这样的方法的。

我们使用currentThread()这个方法,这是Thread类的静态方法。通过这个方法可以获取到当前线程。换句话说就是哪个线程调用的这个方法,就是得到哪个线程的对象引用。(很类似于this)

public class ThreadDemo9 {
    public static void main(String[] args) throws InterruptedException{
        Thread t = new Thread(()->{
            while (!Thread.currentThread().isInterrupted()){
                //Thread.currentThread()是在t.run中被调用的,此处获取的线程就是t线程。
                //isInterrupted()为true表示被终止,为false表示未被终止(应该要继续走);
                //               这个方法背后相当于在判定应该boolean变量
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        Thread.sleep(3000);
        t.interrupt();//使用该方法终止线程,这里就是终止t线程;
                      //设置上面isInterrupted()的boolean变量
    }
}

执行以上代码,我们会看到执行结果中前3行表示t正常执行,然后t调用interrupt出发了异常,之后t还在继续执行。

 这里interrupt会做两件事:1)把线程内部的标志位(boolean)给设置成true;2)如果线程在进行sleep,就会触发异常,把sleep唤醒。但是sleep唤醒的时候,还会做一件事,把刚才设置的这个标志位,再设置会false(清空了标志位)。这就导致,当sleep的异常被catch完了之后,如果什么也不做,循环还要继续执行。(线程t忽略了我们的终止请求)

当我们加了break之后,就可以让线程立即结束了。(线程t立即相应我们的终止请求)

加个稍后进行终止。 

为什么sleep要清除标志位?可以让程序员自由控制在 唤醒之后,线程是否要终止以及是否立即终止或者稍后,选择权就在于程序员怎么写代码。


四、等待一个线程 - join()

线程是一个随机调度的过程,所以完全无法判定两个线程谁先先执行什么代码谁后执行什么代码,这个顺序是不可预期的,但是实际上我们写代码的时候不希望有这种不可预期的情况,我们更希望我们的结果是可预期的,所以很多时候我们就要对这里的情况进行调整。那么等待线程做的事情就是在控制两个线程的结束顺序,这里涉及的方法就是join()。

public class ThreadDemo10 {
    public static void main(String[] args) {
        Thread t = new Thread(() ->{
            for (int i = 0; i < 3; i++) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        System.out.println("join之前");
        //此处的join就是让当前的main线程等待t线程的结束(等待t的run方法执行完)
        try {
            t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("join之后");
    }
}

如上述代码,本身执行完start之后,t线程和main线程就并发执行,分头行动。main继续往下执行,t也会继续往下执行。但是main遇到join之后,就会发生阻塞,让main“暂停”执行,一直阻塞到,t线程执行结束,main线程才会从join中恢复回来,才会继续往下执行。(t线程肯定是比main线程先结束的)

TestDemo10运行结果:

 接下来我们假设开始执行join的时候,t已经结束了,此时join会怎么样?

 我们让t线程等待5秒,可以知道5秒之后t线程已经结束了,结束了之后主线程再去join就可以看到这次的join没有等待立即就返回了。

所以对于我们的join来说是不是一定要等待也得看情况。如果执行join的时候t已经结束了,join就不会阻塞,就会立即返回。

附录:

方法说明
public void join()等待线程结束
public void join(long millis)等待线程结束,最多等millis毫秒
public void join(long millis, int nanos)同理,但可以更高精度

注:

public void join():无参版本,“死等”不见不散;

public void join(long millis):指定一个超时时间(最大等待时间,这种操作方式更常见,死等很容易有问题)


五、获取当前线程引用

方法说明
public static Thread currentThread();返回当前线程对象的引用
public class ThreadDemo {
    public static void main(String[] args) {
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName());
   }
}

注:理解:这个方法在哪个线程中调用,就能获取到哪个线程的实例


六、休眠当前线程

方法说明
public static void sleep(long millis) throws InterruptedException
休眠当前线程 millis 毫秒
public static void sleep(long millis, int nanos) throws
InterruptedException
可以更高精度的休眠

这个方法可以让线程休眠,本质上就是让这个线程不参与调度了(也就是不去CPU上执行了)

举个例子解释:

 操作系统内核中有这样一些PCB,这些PCB构成这样一个链表。当前线程A调用sleep,此时A就会进入休眠的状态。这个时候就相当于把A从上述链表中拎出来,放进另一个链表中,这个链表的PCB都是“阻塞状态”,暂时不参与CPU的调度执行。

注:PCB是使用链表来组织的 这种说法并不具体,实际的情况并不是一个简单的链表,其实这是一系列以链表为核心的数据结构。那么从我们理解角度来讲,操作系统每次只需要调度一个线程去执行,就直接从就绪队列中选一个就好了。一旦线程进入阻塞状态,对应的PCB就进入阻塞队列了,此时就暂时无法参与调度了。

而且此时需要注意:当线程阻塞之后(处于阻塞队列),回到就绪队列时,考虑调度的开销,实际上线程是无法在唤醒之后立即执行的,实际上的时间间隔大概率会比理论间隔要大。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值