Thread类是JVM用来管理线程的一个类,每个线程都有一个唯一的Thread对象与之关联。
多一个线程,就多一条执行流,每个执行流也要一个对象来描述,而Thread类的对象就是用来描述一个线程的执行流,JVM 会将这些 Thread 对象组织起来,⽤于线程调度,线程管理。
Thread常见的构造方法:
1.Thread t1=new Thread();
2.Thread t2=new Thread(()->{});
3.Thread t3=new Thread("这是我的名字t3");
4.Thread t4=new Thread(()->{},"这是我的名字t4");
Thread的常见属性:
ID:
可以通getId()方法来获取,就相当于是线程的身份标识符,线程的唯一标识,不同线程不会重复。在java中,不同线程的id是由java虚拟机来分配的
public class test1 {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{});
Thread t2=new Thread(()->{});
Thread t3=new Thread(()->{});
t1.start();
t2.start();
t3.start();
Thread.sleep(1000);
System.out.println("t1的ID"+t1.getId()+" t2的ID"+t2.getId()+" t3的ID"+t3.getId());
System.out.println("main线程结束....");
}
}
运行结果:
可以看出不同的线程,ID不一样。
名称:
各种调试工具会用到。(名称可以随便取,只是方便用于调试)
public class test1 {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{});
Thread t2=new Thread(()->{});
Thread t3=new Thread(()->{});
t1.start();
t2.start();
t3.start();
Thread.sleep(1000);
System.out.println("t1的name:"+t1.getName()+" t2的name:"+t2.getName()+" t3的name:"+t3.getName());
System.out.println("main线程结束....");
}
}
运行结果:
由此可知:当用户没有去手动命名,JVM会自动命名,且命名格式如上:Thread-0;Thread-1.....
线程的状态:
表示当时线程处于一个的情况。(下面会详细说明)
优先级:
在java中,线程优先级是用来帮助操作系统决定哪个线程先获取CPU资源的一个机制。决定那个线程先获取CPU资源的一个机制。
线程的优先级是一个整数:范围从1-10(Thread.MIN_PRIORITY-Thread.MAX_PRIORITY)
默认优先级是5(Thread.NORM_PRIORITY).
我们可以通过setPriority()方法来设置线程的优先级:thread.setPriority(7)
线程优先级只是一种提示,不能保证优先级越高就越先执行,因为实际的线程调度依赖于底层的操作系统的策略和当前的负载情况。
优先级更高的线程理论上更容易被调用到。
isDaemon:
JVM会在一个进程中的所有非后台线程结束后,才会结束执行。
isDaemon是否是后台线程(守护线程)
public class test1 {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
while(true){
System.out.println("t1正在执行....");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2=new Thread(()->{
while(true){
System.out.println("t2正在执行....");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t3=new Thread(()->{
while(true){
System.out.println("t3正在执行....");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
t2.start();
t3.start();
Thread.sleep(1000);
System.out.println("main线程结束....");
}
}
在上面这个测试类中,有t1,t2,t3,main主线程(四个线程)
运行结果:
前台线程:
我们会发现,虽然main线程结束了,但是进程依然还在继续执行。因为t1,t2,t3,main,这四个线程都是前台线程(能影响到进程存在的线程叫“前台线程”),进程只会等所有的前台进程都结束了才结束进程。
自己代码创建的线程,包括main主线程,默认都是前台线程,可以通过setDaemon方法来修改。
后台线程:
JVM提供这些线程属于有特殊功能的线程,并且伴随的整个进程持续执行的,比如:垃圾回收线程(伴随着整个进程的后台线程)
当我们把上面的t1,t2,t3这三个线程修改为后台进程。再看看运行结果:
t1.setDaemon(true);
t2.setDaemon(true);
t3.setDaemon(true);
运行结果:
当唯一的前台线程main(主线程)一结束,整个进程都结束了。
是否存活isAlive():
前面提到,Thread类是JVM用来管理一个线程的,每一个线程都有一个Thread对象与之关联。isAlive方法就是返回线程的生命周期,而非是Thread对象的生命周期,当线程中的run()还在运行时返回true,若run()运行结束就返回false。
但是Thread对象的生命周期和系统中的线程的生命周期,是不同的。
Thread对象存在,但是系统中的线程已经销毁(线程的run方法结束了)的情况:比如像下面代码这种:
public class test1 {
public static void main(String[] args) throws InterruptedException {
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();
while(true){
System.out.println(t.isAlive());
Thread.sleep(1000);
}
}
}
运行结果:
这个代码理应三秒后就结束了,但是我们看运行结果:虽然存在了3秒,但是打印了四个true(这里的结果可能是不同的,因为操作系统的随机调度),解释:主线程的第四次打印isAlive和t线程结束(因为随机调度),谁先谁后,不一定,所以第四次打印true,说明t线程还剩“最后一口气”
中断一个线程Interrupt()方法:
在Java中,通过用Interrupt方法来中断线程(这里的中断,是直接让线程停止了,不会再恢复),这个方法会向线程发送一个中断信号,但是线程具体行为取决于代码的具体实现,常见的方法就是在run()方法中使用一个循环(isInterrupted()方法)来检测线程中断状态,并在接收到中断信号并退出循环,从而终止线程的执行。(在isinterrupted方法内部有一个标志位,若接收到来自interrupt的中断信号,这个标志位就会修改为true)
public class test1 {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
while(!Thread.currentThread().isInterrupted()){//判断线程是否终止
System.out.println("hello thread");
}
});
t.start();
t.interrupt();//主动线程中断
}
}
这里就是在run()方法里循环判断线程的状态,用来判断线程是否被终止。
Thread.currentThread()是一个静态方法,此时就是那个线程调用,获取到的就是那个线程的Thread引用。
为什么这里不能直接用t,因为lambda这里的定义是在new Thread之前,也是在Thread t声明之前。
主动的去进行终止,Java中的Thread类中有一个boolean类型的变量叫做interrupted flag(中断标志),它用来表示线程是否被中断。调用线程的interrupt()方法会将这个标志设置为true,表示线程被中断。上面这个代码就是修改了这个flag这个变量的值,除此之外还能唤醒sleep这样的阻塞。
变量捕获:
先看一段代码:
public class test1 {
public static void main(String[] args) throws InterruptedException {
boolean isFinished = false;//局部变量
Thread t = new Thread(() -> {
while (!isFinished) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("thread 结束");
});
t.start();
Thread.sleep(3000);
isFinished = true;//修改这个变量
}
}
这里会报红,为什么呢?
在Lambda表达式中希望使用外面的变量,就会触发“变量捕获”这样的语法。
Lambda是回调函数,执行的时候是很久之后(操作系统真正创建出线程之后,才会执行),后面很有可能,后续线程创建好了,当前main这里的方法都执行完了,对应的isFinished局部变量就销毁了。(也就是生命周期失效了)
为了解决上述问题:java的做法,把被捕获的变量会拷贝一份,拷贝给lambda里面
外面的变量是否销毁,都不会影响lambda里面的执行了。
拷贝意味着,这样的变量就不适合修改。
修改一方,另一方不会随之修改(因为本质上是两个变量),后续就会带来更多的困惑。
后面又想出一个办法:通过final限制,压根就不让你修改这个变量。
这样注释掉这个修该之后
就不会报红了。
再看下面一个代码:
public class test1 {
private static boolean isFinished = false;//成员变量
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
while (!isFinished) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("thread 结束");
});
t.start();
Thread.sleep(3000);
isFinished = true;
}
}
lambda本质上是一个函数式接口,相当于一个内部类,isFinished变量本身就是一个外部类(test1)的成员
内部类本来就可以访问外部类的成员(成员变量生命周期是由GC来管理的),所以在lambda里面不担心变量周期失效的问题,也不必通过“拷贝”,也不必限制final
interrupt唤醒sleep:
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("thread继续执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("sleep抛出异常");
break;//使用break结束循环
}
}
});
t.start();
Thread.sleep(3000);
System.out.println("main尝试尝试终止线程");
t.interrupt();
}
每次循环,绝大部分时间都是sleep。这时主线程调用interrupt,这个操作不仅能够唤醒sleep,还能让sleep抛出异常。
再看一个代码:
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("thread继续执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//不做处理
}
}
});
t.start();
Thread.sleep(3000);
System.out.println("main尝试终止线程");
t.interrupt();
}
运行结果:
正常来说,调用interrupt方法后,会将isterrupted中的标准位改为true,然后终止循环。但是结果发现,循环没有结束
因为上述代码中,将sleep唤醒了,然后sleep在被唤醒之后,把isterrupted标志位修改为false,这样进行循环条件判断,就会继续执行。
sleep这样设计的,就可以让程序员在catch语句中有更多的选择:线程是否立即结束,还是等会结束,还是不结束(忽略这个终止信号)
java中的线程终止,不是一个“强制性”的,选择权在程序员自己手上。
等待一个线程-join()方法:
多个线程之间,采用并发执行和随机调度。但是程序员并不喜欢随机的东西。
join作用:确定多个线程之间,结束的先后顺序。如果有一个线程t,然后在主线程中调用t.join(),意思就是让主线程等待t线程结束再执行。
两张图对应看。在main线程中,调用t.join()效果,让main线程等待t先结束(当执行到t.join时,此时main线程就会阻塞等待,一直阻塞到t线程执行完,main主线程才能继续执行)
但是这个方法不是很好,假如t线程发生了什么意外,难道主线程要一直等下去吗?这就不切实际了。谁愿意一直去等一个等不到的人呢?
所以join提供了带参数的版本,可以指定join等待的时候(超时时间-等待的最大时间)
t.join(3000);
带有超时时间的等待,才是更加科学的做法;计算机中,尤其是与通信相关的逻辑,一般都需要“超时时间”
t.join(3000,500);
join方法也提供了上面这个版本,3000的单位是毫秒,而500的单位是纳秒(一般不使用)
1s=1000ms
1ms=1000us
1us=1000ns
休眠当前线程:
在java中,可以使用Thread.sleep()方法来休眠当前线程,前面也提到当使用sleep方法时,可能抛出异常,需要进行相应的处理(异常捕获),代码如下:
try{
Thread.sleep(时间毫秒数);
}catch(InterruptedException e){
e.printStackTrace();
}
当代码调用sleep,相当于让当前线程,让出cpu资源,后续时间到了,需要操作系统内核了,把这个线程重新调到cpu上,才能继续执行。所以当调用sleep(1000)这个方法,真的是1000毫秒吗?实际上比1000毫秒要多一点,时间到了,并不意味着立即就执行了,而是可以被调度了。
sleep(0)是sleep方法的一种特殊写法,当调用了sleep(0),意味着让当前的线程立即放弃cpu资源(放弃cpu资源,把cpu让出来给更多的人执行机会),等待操作系统重新调度