Thread类 及常见方法
1、常见构造方法
方法 | 说明 |
---|---|
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用 Runnable 对象创建线程对象 |
Thread(String name) | 创建线程对象,并命名 |
Thread(Runnable target, String name) | 使用 Runnable 对象创建线程对象,并命名 |
Thread(ThreadGroup group, | 线程可以被用来分组管理,分好的组即为线程组(了解即可,不详细讲) |
上篇文章基本上都讲解过了,我们这里只讲解两个Thread(String name)以及Thread(Runnable target, String name) 来讲解
这两个方法都可以在创建线程的时候,给线程起给名字(这个名字运行重复),目的就是为了方便程序员调试。一旦出问题,就更方便找到对应的代码。
如果不自定义名字,JVM 就会按照thread-0 ,thread-1…来命名
1.1Thread(Runnable target, String name)
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {
try {
Thread.sleep(3000);
System.out.println("hello");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "Thread线程自定义名字"
);
thread.start();
}
}
通过jconsole来查看验证:
2、Thread 的几个常见属性
属性 | 获取方法 |
---|---|
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否是后台线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
ID:
Java中给Thread对象安排的身份标识;和操作系统内核中的PCB的pid以及和操作系统提供的线程线程API中的线程id都不是一回事。
名称:
就是前面构造方法中我们自己自定义的名称
状态:
下面单独讲。
优先级:
获取线程的优先级
后台线程:
默认创建的是前台线程,后台线程指的是当所有(main线程)前台线程执行完毕以后,不管后台线程有没有执行完,直接退出进程。前台进程就不会。
是否存活:
判定内核的线程在不在!
Thread对象虽然和内核中的线程是一一对应的,但是生命周期并非完全相同。
Thread t = new Thread();
这样虽然Thread对象出来了,内核里的线程还不一定有;调用start方法,内核线程才会有,当内核里的线程执行完了(run方法运行完了),内核的线程就销毁了,但是Thread对象还在。
[经典的面试题]
调用 start 才会真正创建线程!! 不调用 start 没创建线程 (在内核里创建 PCB)
注意理解, start 和 run 的区别!! **
直接调用 run 并没有创建线程,只是在原来的线程中运行的代码
调用 start,则是创建了线程**,在新线程中执行代码(和原来的线程是并发的)
线程的中断:
run方法执行完了,线程就结束了.有没有办法,让线程,提前一点结束呢??
通过线程中断的方式来进行;
本质:让run方法尽快结束,而不是run执行一半强制结束。
目前常见的有以下两种方式:
通过共享的标记来进行沟通
调用 interrupt() 方法来通知
1、通过共享的标记来进行沟通:
2、使用标准库里面自带的一个标志位
使用 Thread.interrupted()
或者 Thread.currentThread().isInterrupted()
代替自定义标志位
方法 | 说明 |
---|---|
public static boolean interrupted() | 判断当前线程的中断标志位是否设置,调用后清除标志位 |
public boolean isInterrupted() | 判断对象关联的线程的标志位是否设置,调用后不清除标志位 |
使用 thread 对象的 interrupt() 方法通知线程结束.
在主线程,通过thread.interrupt()设置标志位为true!
2.1使用Thread.currentThread().isInterrupted()
判断当前线程的中断标志被设置
启动时我们发现:
报错了,并且线程也没有中断,为什么?
2.1.1、interrupt方法的行为,有两种情况:
1、t线程在运行状态:
调用t.interrupt()会设置标志位为true。结果**!**后正常中断。
。
2、线程在阻塞状态( wait/join/sleep 等方法而阻塞挂起)
不会设置标志位,而是触发一个InterruptedException 异常的形式通
知,清除中断标志,并且提前将阻塞状态唤醒!
前面有讲到线程中断的本质是:本质:让run方法尽快结束,而不是run执行一半强制结束。
下面就可以体现出来,当遇到InterruptedException 异常的形式通知时候。会触发这个异常:
具体的如何处理,就在于补抓到异常以后用户的代码是如何写
①立即结束进程:
②不理会:
③稍后处理:
2.2使用Thread.interrupted()
判断当前线程的中断标志被设置
当这个使用这个作为标志位时候调用thread.interrupt()时候问题同上;
他们两个方法的不同之处就在于:
什么叫做清楚标志位,什么叫做不清除?
清除标志位:
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()-> {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().interrupted());
}
});
thread.start();
thread.interrupt();
}
}
打印结果:
true // 只有一开始是 true,后边都是 false,因为标志位被清
false
false
false
false
false
false
false
false
false
不清除标志位:
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()-> {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.interrupted());
}
});
thread.start();
thread.interrupt();
}
}
true // 全部是 true,因为标志位没有被清
true
true
true
true
true
true
true
true
true
3、等待一个线程-join()
join()可以控制线程之间的结束顺序
线程之间的调度顺序,是不确定的!!可以通过一些特殊操作,来对线程的执行顺序,做出干预其中 join 就是一个办法,控制线程之间的结束顺序!!
Tips:Java 中的多线程方法,只要是这个方法会阻塞,都可能会抛出InterrupttedException 异常!!
效果:在main中调用t.join效果就是让main线程阻塞等待,等到t执行完了,main才继续执行!!
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()-> {
for(int i = 0; i < 10; i++) {
}
});
t.start();
t.join();
}
}
当调用t.join()时候,main线程进入阻塞,不参与CPU的调度。
方法 | 说明 |
---|---|
public void join() | 等待线程结束 |
public void join(long millis) | 等待线程结束,最多等 millis 毫秒 |
public void join(long millis, int nanos) | 等待线程结束,最多等 millis 毫秒,但可以更高精度 |
实际应用:
服务器开发经常要处理客户端的请求,根据请求计算生成响应 就需要用到“等待时间”称为“超时时间“客户端发了请求过来,等待响应,这个等待就不能无限的等 可以根据需要,约定 1000ms 500ms如果时间之内响应没有回来,客户端直接提示“等待超时
4、获取当前线程引用
public static Thread currentThread() :返回当前线程对象的引用
5、休眠当前线程
注意:由于线程的调度是不可控制的,所以,这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间的
方法 | 说明 |
---|---|
public static void sleep(long millis) throws InterruptedException | 休眠当前线程 millis毫秒 |
public static void sleep(long millis, int nanos) throws | 可以更高精度的休眠 |
sleep:指定休眠的之间(阻塞一会会)
如何更加好的理解sleep?
操作系统管理这么多个PCB的时候是有多个链表的,调用了sleep,则当前调用sleep的PCB就会被移动到另外的“阻塞队列”中。如图:
通过上诉案例我们也能够清楚的认识到,使用sleep(1000)的话,实际阻塞时间是大于等于1s的,这完全取决于调度了。
状态-Thread常见属性
这里特指Java中的对Thread的状态规则
NEW:Thread对象创建出来了,但是内核的PCB还没创建。
TERMINATED:内核的PCB销毁了,但是Thread对象还在。
RUNNABLE:就绪状态(正在CPU上运行 + 在就绪队列中排队)
TIMED_WAITING:按照一定的时间,进行阻塞。
WAITING:特殊的阻塞状态(调用wait)
BLOCKED:等待锁的时候竟然的阻塞状态
一个线程状态之间的变化如下图: