进程有进程的优先级,线程也有优先级,但是优先级这个东西并不是越高的会优先执行,只是在CPU比较忙的时候,优先级较高的线程会有机会获取更多CPU的执行时间,但是闲时并不会出现这种情况。因此在程序设计中,最好不要将业务与线程的优先级进行强关联。
/**
* autor:liman
* createtime:2020/6/7
* comment:线程id和线程优先级
*/
@Slf4j
public class ThreadIDAndPriority {
public static void main(String[] args) {
Thread t1 = new Thread(()->{
while(true){
log.info("this is t1 thread");
}
});
t1.setPriority(3);
Thread t2 = new Thread(()->{
while(true){
log.info("this is t2 thread");
}
});
t2.setPriority(8);
t1.start();
t2.start();
}
}
上述代码多运行几次,发现有时候t2线程并没有真正的占用大部分运行时间。
翻看Thread中关于setPriority的源码
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
setPriority0(priority = newPriority);
}
}
发现其实很简单,新的线程的优先级不能设置小于1或者大于10,否则会抛出异常,同时线程的优先级不能高于线程组所在的优先级。线程的默认优先级与其父线程的优先级保持一致。
顺便说说线程id 通过线程本身的getId方法即可获取到线程id,线程id对于我们通过jstack排查问题很有帮助。顺便提一下,线程id的产生也是一个自增的id
private static synchronized long nextThreadID() {
return ++threadSeqNumber;
}
yield并不常用,只是调用yield方法,会将当前线程从RUNNING状态切换到RUNNABLE状态,线程不会释放锁,yield也是一种启发式的方法,提醒CPU本线程自愿放弃CPU的执行权,如果CPU资源不紧张,则会忽略这种提醒。而sleep方法则是会让当前线程进入阻塞状态,但是依旧不释放锁。
join某个线程A,会使得当前线程B 进入等待,直到线程A结束生命周期,或者到达指定时间,这个时候线程B是处于BLOCKED状态的,而不是A线程。
可以参考如下示例:
/**
* autor:liman
* createtime:2020/6/7
* comment:join的简单实例
*/
@Slf4j
public class JoinDemo {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
IntStream.rangeClosed(1,999).forEach(i->log.info("this is t1 thread,i:{}",i));
},"t1");
t1.start();
t1.join();
IntStream.rangeClosed(1,999).forEach(i->log.info("this is main thread,i:{}",i));
}
}
上述代码会出现主线程和t1线程交替运行的情况,如果加入了t1.join的方法,则主线程会等到t1线程运行完成之后才开始运行
线程interrupt是一个比较复杂的内容,关于线程中断的api主要有以下三个方法
public void interrupt():
public static boolean interrupted();
public boolean isInterrupted();
在正式熟悉这些api方法之前,我们先梳理一下,目前为止,让一个线程进入阻塞状态的方法有如下几个
1、wait方法
2、sleep方法
3、join方法
同时终止一个线程,并不是直接暴力的方式终结一个线程(stop方法除外)而是将一个线程内部的中断标志位置为true,让后线程判断该标志位是否置为true,如果置为true则本身开始进行一些终止线程的操作,后面会详细总结如何优雅的关闭一个线程的几种方式
先直接看实例
@Slf4j
public class ThreadInterruptDemo {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
while(true) {
log.info("this is t1 thread");
}
},"t1");
t1.start();
Thread.sleep(10l);//主线程休眠10ms
t1.interrupt();//调用t1线程的interrupt方法
}
}
这个时候线程并不会终止,因为虽然我们调用了t1.interrupt()方法,将t1线程的中断标志位置为true,但是并没有对这个中断作出处理,因此我们线程依旧会运行,如果将上述代码改成如下语句,则t1线程会正常退出
@Slf4j
public class ThreadInterruptDemo {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
while(true) {
log.info("this is t1 thread");
if(Thread.currentThread().isInterrupted()){//判断当前线程是否被终止
log.info("interrupted");
break;
}
}
},"t1");
t1.start();
Thread.sleep(10l);
t1.interrupt();
}
}
之前我们梳理了那些方法可以让线程进入阻塞状态,这些方法在《Java 高并发编程详解》一书中被称为可中断方法,如果目标线程在执行这些方法的时候,被外部中断,则会抛出一个InterruptedException的异常,同时目标线程当前的阻塞状态被打断,这个时候我们可以根据抛出的异常来终止目标线程。但是需要说明的是,如果目标线程在执行可中断方法时被中断,则中断标志位被清除。
/**
* autor:liman
* createtime:2020/6/7
* comment:join的简单实例
*/
@Slf4j
public class JoinDemo {
Thread t2 = new Thread(){
@Override
public void run() {
try {
TimeUnit.MINUTES.sleep(1);
} catch (InterruptedException e) {
//这一行输出为false,sleep方法清空了t2线程的中断标志位
log.info("t2 is interrupted ? {}", this.isInterrupted());
log.error("interrupted");
}
}
};
t2.start();
TimeUnit.SECONDS.sleep(2);
log.info("t1 is interrupt : {}", t2.isInterrupted());
t2.interrupt();
//预留3毫秒给t2调用sleep清空标志位,不然影响判断结果
TimeUnit.MILLISECONDS.sleep(3);
log.info("t1 is interrupt : {}", t2.isInterrupted());
}
最终运行结果如下,三个都为false
如果改为执行wait方法,则会有一样的效果
@Slf4j
public class ThreadInterruptWaitDemo {
private static Object MONITOR = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
log.info("this is t1 thread");
while(true){
synchronized (MONITOR) {
try {
MONITOR.wait();
} catch (InterruptedException e) {
log.info("t1 is interrupted ? {}", Thread.currentThread().isInterrupted());
}
}
}
});
t1.start();
TimeUnit.SECONDS.sleep(10);
log.info("t1 is interrupted ? {}",t1.isInterrupted());
t1.interrupt();
TimeUnit.MILLISECONDS.sleep(3);
log.info("t1 is interrupted ? {}",t1.isInterrupted());
}
}
上述代码运行效果一样。
interrupted方法是一个静态方法,isInterrupted方法是一个实例方法,两者还是有些区别的,调用interrupted方法会直接清除掉线程的中断标志位。而isInterrupted则不会。
总结完interrupt之后,我们关闭线程其实有一个相对优雅的方式了,就是通过中断信号关闭线程
@Slf4j
public class StopThreadGraceful {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
while(true){
log.info("t1 will start work");
while(!Thread.currentThread().isInterrupted()){
// doing work;
}
log.info("t1 will exit");
break;
}
});
t1.start();
TimeUnit.SECONDS.sleep(1);
log.info("main will stop t1");
t1.interrupt();
}
}
只是有个不好的地方,如果在上述的doing work中我们调用了可中断方法,会导致线程的正常中断
@Slf4j
public class StopThreadGraceful {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
while(true){
log.info("t1 will start work");
while(!Thread.currentThread().isInterrupted()){
TimeUnit.SECONDS.sleep(1);//这个会导致程序的正常退出,有可能会影响到正常业务。
}
log.info("t1 will exit");
}
});
t1.start();
TimeUnit.SECONDS.sleep(1);
log.info("main will stop t1");
t1.interrupt();
}
}
由于Thread本的中断标志位很有可能会被擦除,因此我们通过自己设置标志位来中断线程
public static class MyTask extends Thread{
//设置自己的中断标志位
private volatile boolean isClosed = false;
@Override
public void run() {
while(true){
log.info("t1 will start work");
while(!isClosed&&isInterrupted()){//两者都判断
// doing work;
}
log.info("t1 will exit");
break;
}
}
//供外部调用的close方法,用于执行标记中断。
public void close(){
this.isInterrupted();
this.isClosed=true;
}
}