Thread类是Java中用于创建和管理线程的基类(父类),它提供了一些方法来创建,启动,控制和管理线程,以及获取线程的属性和状态,每个线程都有对应于一个Thread对象,可以通过创建Thread对象,启动一个新的线程,并且在该线程中执行指定的代码.
Thread类的构造方法
方法 | 说明 |
---|---|
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用 Runnable 对象创建线程对象 |
Thread(String name) | 创建线程对象,并命名 |
Thread(Runnable target, String name) | 使用 Runnable 对象创建线程对象,并命名 |
Thread(ThreadGroup group, | |
Runnable target) | 线程可以被用来分组管理,分好的组即为线程组 |
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("My Thread");
Thread t4 = new Thread(new MyRunnable(), "My Thread");
public static void main(String[] args) {
Thread t = new Thread(() -> {
while (true) {
System.out.println("hello");
}
}, "myThread");
t.start();
}
线程是通过start创建的,一个线程的入口方法执行完毕(对于主线程,是main;对于其他的线程,run/lambda),执行完线程的入口函数,线程就会销毁.
Thread类的属性
属性 | 获取方法 |
---|---|
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
- ID:线程的唯一标识,不同的线程不会重复
- 状态:线程当前所处的情况
- 优先级:优先级的高的会被先调度到,由系统内核来负责
- 是否后台线程:后台进程不影响进程结束,前台线程,会影响进程结束,如果前台进程没有执行完毕,进程是不会结束的,前台线程结束,那么后台线程就会随之结束,创建的线程默认是前台线程,通过setDaemon显式的设置成后台.
public static void main(String[] args) {
Thread t = new Thread(()-> {
while (true) {
System.out.println("hello Thread");
}
},"Mythread1");
t.setDaemon(true);//转化为后台线程
//前台线程就只剩下main,前台线程结束,那么后台线程就会随之结束
t.start();
}
- 是否存活:Thread对象,对应的线程(系统内核中)是否存活,Thread对象生命周期,并不是和系统中的线程完全一致,一般是Thread对象先创建好,再手动调用start,内核才真正创建出线程.简单的理解,就是run方法是否运行结束了.
Thread方法
启动一个线程
start方法就是调用系统的api完成线程创建工作,start方法本身的执行是一瞬间就完成的,只是告诉系统,你要创建一个线程,调用完start完毕之后,代码就会立即继续执行start之后的逻辑.
终止一个线程
一个线程的run方法执行完毕,就算终止了,此处的终止线程,就是想办法,让run能够尽快的执行完毕.
目前常见的两种快速终止线程的方式:
- 通过共享的标记来进行沟通
- 调用interrupt()方法来通知
//使用自定义的变量作为标志位
public class demo6 {
//如果是成员变量就不触发变量捕获的逻辑了,而是"内部类访问外部类的成员"
public static boolean isQuit = false;
public static void main(String[] args) throws InterruptedException {
//boolean isQuit = false;//局部变量
//lambda表达式可以捕获到外面的变量
Thread t = new Thread(()->{
while (!isQuit) {
System.out.println("hello");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
//主线程执行其他的逻辑后,要让t线程结束
Thread.sleep(3000);
//修改前面设定的标志位
isQuit = true;
System.out.println("t 线程终止");
}
}
如果把isQuit设置为局部变量编译器就会报错,isQuit是跟随main方法销毁,lambda表达式执行时机是更靠后的,这就导致,后续真正执行lambda的时候,局部变量isQuit就可能已经被销毁了,为了解决这一问题,lambda就引入"变量捕获"这样的机制,lambda内部看起来是直接访问外部的变量,其实本质上是把外部的变量复制了一份到lambda里面.就可以解决生命周期的问题了,但是变量捕获这里有个限制,要求捕获的变量得是final(至少看起来是final,代码中没有修改变量,这个变量也可以视为final),如果这个变量想要进行修改,就不能进行变量捕获.java通过复制的方式来实现"变脸捕获"的原因是因为如果外面的代码要对这个变量进行修改,就会出现一个情况:外面的变量变了,里面的没变,代码更容易出现歧义.
//线程终止,使用Thread自带的标志位
public class demo7 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
//Thread提供了静态方法,在哪个线程调用这个方法,就能获取哪个线程的引用
//Thread.currentThread()获取当前线程的对象(相当于t),
//但是lambda表达式是在构造t之前就定义好的,编译器看到的lambda里的t会认为这是一个还没初始化的对象
//isInterrupted()对象内部提供了一个标志位(boolean),true:线程应该要结束 false:线程先不必结束
while (!Thread.currentThread().isInterrupted()) {
System.out.println("hello");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
Thread.sleep(3000);
//把上述的标志位设置成true
t.interrupt();
}
}
t线程正在sleep,其他线程调用interrupt方法,就会强制使sleep抛出一个异常,sleep就被立即唤醒了,sleep在被唤醒的同时,会自动清除前面设置的标志位,如果想继续让线程结束,那么就在catch中加个break即可.
当sleep被唤醒后,可以有以下几种操作方式:
- 立即停止循环,立即结束线程
- 在catch中执行别的逻辑,之星完毕再结束线程break
- 忽略终止的请求,继续循环.
等待一个线程
多个线程是并发执行的,具体的执行过程,都是由操作系统负责调度的,操作系统调度线程的过程,是"随机"的.无法确定,线程执行的先后顺序.等待线程,是一种规划线程结束顺序的手段.
举一个例子:此时有A B两个线程,我们希望B先结束,A后结束,此时就可以让A线程中调用B.join的方法,此时,B线程还没执行完,A线程就会进入"阻塞"(代码不继续往下执行)状态,就相当于给B留下了执行的时间,B执行完毕之后,A再从阻塞状态中回复回来,并且继续往后执行.如果A执行到B.join(等待结束,run方法执行完毕)的时候,B已经之星完了,A就不必阻塞了,直接往下执行就可以了.
public class demo1 {
public static void main(String[] args) {
Thread b = new Thread(()->{
for (int i = 0; i < 5; i++) {
System.out.println("hello b");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("b 线程结束");
});
Thread a = new Thread(()->{
for (int i = 0; i < 3; i++) {
System.out.println("hello a");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
//如果b还没执行完毕,b.join就会产生阻塞的情况
b.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("a 线程结束");
});
a.start();
b.start();
}
}
阻塞:让代码暂时不继续执行(该线程暂时不去cpu上参与调度)与sleep不同的是,sleep的阻塞时有时间限制的,而join的阻塞是"死等",为了不让线程死等,我们设定了一个最大的等待时间.
方法 | 说明 |
---|---|
public void join() | 等待线程结束 |
public void join(long millis) | 等待线程结束,最多等 millis 毫秒 |
获取当前线程引用
方法 | 说明 |
---|---|
public static Thread currentThread(); | 返回当前线程对象的引用 |
public class ThreadDemo {
public static void main(String[] args) {
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
}
}
休眠当前线程
因为线程的调度是不可控的,所以,这个方法只能保证实
际休眠时间是大于等于参数设置的休眠时间的.比如说sleep(1000)的意思是该线程在1000ms之后,就恢复成"就绪状态",此时就可以随时去cpu上执行了,但不一定是立即马上就去制定.
sleep(0):让当前线程放弃cpu,准备下一轮的调度.
方法 | 说明 |
---|---|
public static void sleep(long millis) throws InterruptedException | 休眠当前线程 millis毫秒 |
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println(System.currentTimeMillis());
Thread.sleep(3 * 1000);
System.out.println(System.currentTimeMillis());
}
}