方法 | 描述 |
---|---|
静态方法 | 作用在当前代码所在的线程 |
static void sleep(long millis ) | 当前线程休眠 给定的时间,时间单位是毫秒 |
static Thread currentThred | 获取代码行所在的当前线程 |
static void yield() | 暂停当前正在执行的线程对象,并执行其他线程 当前线程让步,从运行态转变为就绪态 |
static int activeCount | 获取当前线程组中,还存活的线程数量 |
static boolean interrupted | 获取当前线程的中断标志位,并重置 |
方法 | 描述 |
---|---|
实例方法 | 作用在调用的线程对象 |
void run() | 定义线程的任务,线程真正运行时的方法 |
void start() | 启动线程 申请系统调度运行线程 |
String getName() | 获取线程名称 |
void interrupt() | 中断线程 |
boolean isInterrupted() | 获取线程中断标志位 不重置标志位 |
void join() | 等待该线程终止-无条件等待,有参数就是限时等待 |
void setDaemon(boolean on) | 设置守护线程 |
boolean isAlive() | 测试线程是否处于活动状态 |
int getPriority | 获取线程优先级 返回的是0-10的数值 |
void setPriority(int newPriority) | 更改线程优先级 |
Thread.State getState | 获取线程状态 |
当前线程让步,从运行态转变为就绪态,让当前正在执行的线程暂停,但不阻塞。礼让不一定成功,看cpu心情。
public static void main(String[] args) throws InterruptedException {
Thread [] threads = new Thread[20];
for(int i = 0; i <20 ;i++) {
final int n = i; // 内部类使用外部的变量该变量必须是final 修饰 和JVM 有关
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(n);
}
});
}
for(Thread t : threads) {
t.start();//同时将20个线程启动
}
//但实际中不会这么用
//静态方法:作用是在当前线程 -- 当前代码所在的线程是main -- 只能使用debug
while (Thread.activeCount() > 1) {
//获取当前线程的活跃数量 如果数量大于1 就让当前线程--即main 线程让步
Thread.yield(); // 让当前线程mian让步变为就绪态 : 确保其他线程先执行
}
System.out.println("ok");
}
注意:小tips
Thread.activeCount() > 1 采用这样的写法的时候,只能用debug的方法启动,这样才能确保当其他线程运行完毕后,打印main 线程的ok。
因为idea 会启动其他线程(这里是指出来main 和 20个线程的其他线程),如果直接run 运行代码,会在这里的进入死循环,后面的ok 不会打印。如果想要用run 运行,将条件改成 Thread.activeCount() > 2 即可。
礼让不一定成功
public class TestYield {
public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield,"a").start();
new Thread(myYield,"b").start();
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始执行");
Thread.yield();//让步
System.out.println(Thread.currentThread().getName()+"线程停止执行");
}
}
运行结果1 – 让步不成功:
b线程开始执行
b线程停止执行
a线程开始执行
a线程停止执行
运行结果2 – 让步成功:
a线程开始执行
b线程开始执行
b线程停止执行
a线程停止执行
让步一次成功 :a 线程开始执行的时候,执行到 run 方法里面的 Thread.yield(); 表示当前代码行所在的线程让步即a让步,b执行。a 给b让步,b在执行让步的时候,没让步成功。
运行结果3 – 让步成功:
b线程开始执行
a线程开始执行
b线程停止执行
a线程停止执行
也是让步执行成功了,只是和上面的区别是 让步两次都成功了,b 让了 a ,结果a 又让了b。
上面的yield 方法表示当前线程让步,但实际中使用的不多而且还不可靠,还是主要使用实例 join 方法。t.join 对 t 线程没有什么影响,影响的是当前线程。join 方法会让当前线程处于waiting 状态(也就可以理解为阻塞)。
比如当我有main 线程和 t 线程的时候,我在main 线程里面调用t.join () 表示等待t 线程执行完毕之后,main线程再执行。
无参数
下面两个代码的区别
for(Thread t : threads) {
t.start();
t.join();
//这个表示先启动一个线程,启动后main 等待它执行完
//在启动下一个 main 再等待,这样的线程执行是顺序的
//打印的结果是顺序的
}
把启动和join 分开写
for(Thread t : threads) {
t.start();
}
for(Thread t: threads) {
// 同时执行20个线程,让main 等待20个线程执行完
t.join();
// 这个表示的是同时启动之后,
//如果一个线程还没有执行就让main 等待
// 如果一个线程在这之前已经执行完了,就告诉main 否则我会一直等待
}
这样分开启动,分别启动join 方法,可能会涉及到一个线程已经运行完了。当一个线程已经运行完了以后,再去调用它的 join 方法,没有什么影响。
有参数
当前线程main 是限时等待,直到t线程执行完毕,或者给定的时间已经到了
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
System.out.println("t");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
t.join(); //无条件等待
t.join(1000); // 当前线程main 是限时等待,直到t线程执行完毕,或者给定的时间已经到了
// 具体的时间看两个时间哪一个先到,到了之后先往后在
// 先等 1 s 打印main 然后等3 s 打印t
t.join(4000); // 先等待3 s 打印t 然后执行main
//系统调度t由就绪态转变为运行态 加上 t 的运行时间
System.out.println("main");
}
归纳总结
无参数的join :当前线程无条件等待,直到调用线程执行完
有参数的join :当前线程有条件等待,直到调用线程执行完或者时间片(参数里面的时间)用完 其实等待时间就是线程执行时间和参数等待时间这两个时间里面最小的那个,就会往下执行。
中断线程会用到三个API :
其实我们可以做到自己写一个静态变量flag 然后控制静态变量的值,使得线程终止,但这样做会产生一个问题就是,如果我告诉线程你需要终止的时候,线程处于阻塞状态,那么这个线程是终止不了的。但是采用官方的API 可以解决当线程处于阻塞状态无法终止的问题。
为什么自己写标志位无法中断线程的代码说明:
public class StopThreadTest {
private static volatile boolean STOP = false; // volatile关键字后续会讲解
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
// ..执行任务,执行时间可能比较长
try {
for(int i = 0 ; i <10000 && !STOP;i++) {
System.out.println(i);
//模拟终端线程
Thread.sleep(1000); // 阻塞时间比较短可以解决
// 通过标志位自行实现其实就是无法解决因为线程处于阻塞状态而终止线程
//这里只是用sleep 来模拟,由于sleep 有时间限制
Thread.sleep(10000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
System.out.println("t start");
Thread.sleep(500);
//让t 线程中断掉 停止
STOP = true;
System.out.println("t stop");
}
}
其实你让线程休眠时间长一点,只是时间长,最后还是会结束,这里只是想用休眠模拟线程阻塞,但真实的线程阻塞有很多。 如果我今天是等待输入的io阻塞的话,那我就一直阻塞,线程一直不终止,那么通过自己设置中断标志位就无法实现。
上面说自己写中断标志位,无法解决线程处于阻塞态的时候让线程终止,而interrupt即使线程处于阻塞态,也可以终止线程–是以抛出异常的方式终止。但线程是否停止运行,取决于代码是怎么写的。
isInterrupted() 是获取线程的中断标志位。
注意观察 try catch 的位置
代码说明
try-catch 包裹整个for循环
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
//中断以后,停止程序代码演示
try {
for(int i = 0 ; i <10000 && !Thread.currentThread().isInterrupted() ;i++) {
System.out.println(i);
//模拟终端线程
Thread.sleep(100000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start(); // 线程启动中断标志位=false
System.out.println("t start");
//模拟t执行五秒,还没有停下来,要中断t
Thread.sleep(5000);
//让t 线程中断掉 停止
// (设置t 线程的中断标志位为true),告诉了以后由t 的代码自行决定是否中断
//如果t线程处于阻塞状态会抛出InterruptedException , 并且重置中断标志(这就是继续运行的原因)
t.interrupt();
System.out.println("t stop");
}
try catch 只包裹 sleep
更改run 方法try catch 的位置
//中断以后,继续执行--try catch 的位置和上面的不一样
for (int i = 0; i < 10000 && !Thread.currentThread().isInterrupted(); i++) {
System.out.println(i);
//模拟终端线程
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
看到这里,有没有些许疑惑?的确,interrupt 是可以将处于阻塞态的代码通过抛出异常的方式终止,但是这还要看我的代码是怎么写的。
异常抛出,我会继续for 循环,一个是因为try catch 只包裹 sleep,还有一个原因是会重置中断标志。(刚开始告诉你中断标志位设置位true 之后,interrupt 会重置它为false的!)
t.interrupt() 的作用就是告诉t 线程要中断了(设置t 线程的中断标志位为true),如果t线程处于阻塞状态会抛出InterruptedException , 并且重置中断标志,告诉了以后由 t 的代码自行决定是否中断。(上面演示的就是try -catch位置不同,线程会有不马上中断的可能,当然任务执行完就中断了,只是没有在我通知它的时候及时中断 )
线程的状态是一个枚举类型Thread.State
for(Thread.State state : Thread.State.values()){
System.out.println(state);
}
六大状态
NEW (新建)
RUNNABLE(包含就绪ready 和 运行中 Running 两种) 可运行状态
BLOCKED(阻塞)
WAITING(等待)
TIMED_WAITING(限时等待)–有时间限制
TERMINATED(终止)
阻塞、等待、限时等待这三个都是对应着进程的阻塞。
线程中断或者结束后,一旦进入死亡状态就不能再次启动,也就是说一个线程只能启动一次。
Thread.getState 方法
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("//");
});
//观察状态
Thread.State state = thread.getState();//alt+enter 可以创建变量
System.out.println(state);//new
//观察启动后
thread.start();
System.out.println(thread.getState());//RUNNABLE
while (thread.getState() != Thread.State.TERMINATED) {
// 只要线程不终止就一直输出下去
Thread.sleep(100);
state = thread.getState(); // 更新线程状态
System.out.println(state); //TIMED_WAITING
}
//线程中断或者结束后,一旦进入死亡状态就不能再次启动
// 一个线程不能启动该两次,死亡以后就不能再启动了
thread.start();
}
priority 线程的优先级用数字表示,范围从1~10
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;(默认优先级)
public final static int MAX_PRIORITY = 10;
使用getPriority()方法来获取优先级
setPriority(int xxx)来改变优先级。
要先设置优先级再启动 (启动之后在设置没有意义啦)
优先级高不一定会先执行,优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调度了。
既然优先级高的不一定会先执行,那我设置优先级意义是什么?
这个只是一个给系统的建议。但这个优先级不可靠,系统不一定能满足。
就好像你给别人建议,别人听不听那你可管不着。它采纳建议是最好的,不采纳你也没办法。但你还是会一直建议。
线程分为用户线程和守护线程
虚拟机必须确保用户线程执行完毕(main),但不用等待守护线程执行完毕(gc)。守护线程例如:后台记录操作日志、监控内存、垃圾回收(gc)等等。
设置守护线程的方法是 setDaemon
public final void setDaemon(boolean on)
设置布尔值为 true,标志着这个线程为守护线程 ,默认false 用户线程
用户线程执行完毕后,守护线程就会被结束(但不是马上就被结束,需要时间)
虚拟机不用等待守护线程运行完毕