Java多线程拾遗(三)——Thread的一些API

在这里插入图片描述# 线程优先级和线程Id

进程有进程的优先级,线程也有优先级,但是优先级这个东西并不是越高的会优先执行,只是在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;
}

sleep和yield

yield并不常用,只是调用yield方法,会将当前线程从RUNNING状态切换到RUNNABLE状态,线程不会释放锁,yield也是一种启发式的方法,提醒CPU本线程自愿放弃CPU的执行权,如果CPU资源不紧张,则会忽略这种提醒。而sleep方法则是会让当前线程进入阻塞状态,但是依旧不释放锁

join

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

线程interrupt是一个比较复杂的内容,关于线程中断的api主要有以下三个方法

public void interrupt():
public static boolean interrupted();
public boolean isInterrupted();

在正式熟悉这些api方法之前,我们先梳理一下,目前为止,让一个线程进入阻塞状态的方法有如下几个

1、wait方法

2、sleep方法

3、join方法

同时终止一个线程,并不是直接暴力的方式终结一个线程(stop方法除外)而是将一个线程内部的中断标志位置为true,让后线程判断该标志位是否置为true,如果置为true则本身开始进行一些终止线程的操作,后面会详细总结如何优雅的关闭一个线程的几种方式

interrupt方法

先直接看实例

@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方法是一个实例方法,两者还是有些区别的,调用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;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章