Thread类的属性及常见方法

Thread是JVM用于管理线程的类,换句话说,每个线程都有一个Thread对象与之关联,一个Thread对象有ID、名称、优先级、状态等属性,JVM会将这些Thread对象组织起来,用于线程调度,线程管理。

1. Thread的常见构造方法

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

给Thread命名的意义:上一篇文章中我们使用jconsole查看过自定义的线程,由于没有命名,它的默认名称为Thread-0,我们可以在构造函数中给线程命名(与内核中的线程一一对应),以便后续调试。

public class Demo7 {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            while (true) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "myThread");

        thread.start();
        while (true) {
            System.out.println("hello world");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

打开jconsole再次查看:

image.png

2. Thread的常见属性

属性获取方法
ID:线程在JVM中的唯一标识符getId()
名称getName()
状态getState()
优先级getPriority()
是否为守护线程isDaemon()
是否存活isAlive()
是否被中断isInterrupted()

守护线程的解释:创建出来的线程默认为非守护线程,守护线程不会阻止进程结束,非守护线程会阻止进程结束。也就是说所有非守护线程必须执行完了进程才会结束,如果想设置线程为守护线程可以使用setDaemon(true)方法来设置线程为守护线程。

使用一下上面的api,查看Thread的属性:

public class Demo8 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "myThread");

        t.start();
        System.out.println(t.getId());
        System.out.println(t.getName());
        System.out.println(t.getState());
        System.out.println(t.getPriority());
        System.out.println(t.isDaemon());
        System.out.println(t.isAlive());
        System.out.println(t.isInterrupted());
     }
}

运行结果:下面都是线程一瞬间的属性

11
myThread
TIMED_WAITING
5
false
true
false

3. 启动一个线程——start()

创建出Thread实例,只是在JVM的堆区中创建出了一个Thread对象,并没有在操作系统中创建出一个线程,调用了start()方法才是在操作系统中创建出一个线程并完成指定的入口方法(重写的run()方法或者Runnable对象),并且当线程的入口方法完成后,该线程就随之结束了。

4. 获取线程引用——currentThread()

Thread类有一个静态方法currentThread(),用于在入口方法(线程需要做的事)内部获取到线程的引用。

  • 如果是继承Thread类,重写run方法,可以直接在run方法内使用this获取到线程引用
  • 但如果是Runnable内重写run方法,this就不管用了,需要使用Thread.currentThread()方法获取引用

5. 中断线程——interrupt()

所谓的中断线程,就是让线程尽快把入口方法执行结束,Java中把中断的决定权交给被中断的线程本身,接下来我将以两种方式举例如何中断线程。

  1. 直接使用标识为来区分线程是否需要中断
public class Demo9 {
    private static boolean isQuit = false;
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            while (!isQuit) {
                System.out.println("thread is running..");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("thread is finished! ");
        });
        thread.start();

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        //主线程休眠五秒后将标志位设为true
        isQuit = true;
    }
}
thread is running..
thread is running..
thread is running..
thread is running..
thread is running..
thread is finished! 

Process finished with exit code 0

这种方案是可行的,Thread其实内置了标志位,不需要我们手动创建标志位

  1. 使用Thread自带的标志位来判断是否要中断

设置标志位的方法如下:

//调用后标志位就设为true了
thread.interrupt();

判断标志位的方法如下:

//普通成员方法,中断后不清除中断标志(还是true)
Thread.currentThread().isInterrupted()
//静态成员方法,中断后清除中断标志(变为false)
Thread.interrupted()

使用这两个方法来中断线程:

public class Demo10 {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("thread is running..");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        thread.start();

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        //主线程休眠五秒后中断线程
        System.out.println("控制线程退出");
        thread.interrupt();
    }
}

此时会发生一个问题,运行结果如下:

thread is running..
thread is running..
thread is running..
thread is running..
thread is running..
控制线程退出
Exception in thread "Thread-0" java.lang.RuntimeException: java.lang.InterruptedException: sleep interrupted
	at thread.Demo10.lambda$main$0(Demo10.java:18)
	at java.lang.Thread.run(Thread.java:750)
Caused by: java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at thread.Demo10.lambda$main$0(Demo10.java:16)
	... 1 more

thread.interrupt()在线程非阻塞状态和阻塞状态下会有两种行为:

  1. 非阻塞状态:thread.interrupt()会修改内置的标志位为true
  2. 阻塞状态:thread.interrupt()方法会使如sleep()这样阻塞线程的方法出现异常,又由于我们在catch代码块中捕获到异常的处理方式是直接throw抛出异常,因此进程就会因为异常而意外中断。

由于我们写的该线程大部分时间都是处于阻塞状态,因此我们需要对catch代码块中的代码进行特殊处理:

Thread thread = new Thread(() -> {
    while (!Thread.currentThread().isInterrupted()) {
        System.out.println("thread is running..");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            break;
        }
    }
});

此时就可以正常地中断线程了:

thread is running..
thread is running..
thread is running..
thread is running..
thread is running..
控制线程退出

Process finished with exit code 0

6. 等待一个线程——join()

join()方法的行为:

  1. 如果被等待的线程还没执行完,就阻塞等待
  2. 如果等待的线程已经执行完,就直接返回

由于调度器的策略是我们无法干涉的,但是使用join()可以控制一些必要的执行顺序,这里我将举两个案例来帮助理解join()方法

上一篇文章中我们使用了下面的代码来控制main函数阻塞,等待t1和t2执行完了,main才能解除阻塞,继续往下执行:

public class Demo6 {
    private static final long COUNT = 10_0000_0000;
    private static void serial() {
        long begin = System.currentTimeMillis();

        int a = 0;
        for (int i = 0; i < COUNT; i++) {
            a++;
        }
        a = 0;
        for (int i = 0; i < COUNT; i++) {
            a++;
        }
        long end = System.currentTimeMillis();
        System.out.println("共花费了" + (end - begin) + "ms");
    }

    public static void main(String[] args) {
        concurrency();
    }

    private static void concurrency() {
        long begin = System.currentTimeMillis();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                int a = 0;
                a++;
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                int a = 0;
                a++;
            }
        });
        t1.start();
        t2.start();

        try {
            //等待t1,t2线程结束才接触阻塞并执行后续代码
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        long end = System.currentTimeMillis();
        System.out.println("共花费了" + (end - begin) + "ms");
    }
}

第二个场景:t1,t2,main并发运行,但是他们的结束顺序必须是t1、t2、main,此时我们同样可以使用join()控制线程结束的顺序:

public class Demo11 {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("main begin");

        Thread t1 = new Thread(() -> {
            System.out.println("t1 begin");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("t1 end");
        });
        t1.start();

        Thread t2 = new Thread(() -> {
            System.out.println("t2 begin");
            //等待t1结束
            try {
                t1.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("t2 end");
        });
        t2.start();

        //等待t2结束
        t2.join();
        System.out.println("main end");
    }
}

上面那种写法是一直阻塞到线程结束,但是在实际开发中,更推荐使用最多阻塞多久就不阻塞了的方式(第2、3种方法):

image.png

7. 休眠线程——sleep()

sleep()方法已经在前面的演示代码中使用了很多遍了,这里我们具体谈谈操作系统中做了什么。

第一章我们在引入线程之前讲了进程的五种状态:运行态的进程在cpu上运行,就绪态的线程在就绪队列中等待调度,阻塞态的进程在阻塞队列中,不参与调度。

引入线程后调度的基本单位变成了线程,一个线程对应一个TCB,在就绪队列中的TCB参与调度器的随机调度,在阻塞队列中的TCB不参与调度器的随机调度。

  1. 在调用sleep()方法前,它们是这样的:

image.png

  1. 当调用到sleep()后,该线程立刻就从运行态变为了阻塞态,并且将TCB放入阻塞队列中,并且CPU立马从就绪队列中调度一个新的线程运行:

image.png

  1. 当sleep的时间结束时,会唤醒该线程,该线程才从阻塞态变为就绪态,并且被放入就绪队列中等待调度器随机调度:

image.png

  • 19
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

干脆面la

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值