1、启动线程:start和run的区别
启动线程时,要调用start方法。而start方法会去调用run方法,那么为什么不直接调用run方法?
public static void main(String[] args) {
Runnable runnable = () -> {
System.out.println(Thread.currentThread().getName());
};
runnable.run();
new Thread(runnable).start();
}
运行结果:
run方法只是一个普通的方法,执行时是由主线程去调用的,并没用使用子线程,start方法才是真正用子线程调用的。
start方法执行流程:
- 1、检查线程状态,只有new状态下的线程才能继续,否则会抛出异常
- 2、被加入线程组
- 3、调用start0()方法启动线程
那么,一个线程调用两次start会发生什么?
IllegalThreadStateException
在调用start方法时会进行状态检测,包括新建,就绪,阻塞,等待、计时等待、终止,第二次调用start() 方法的时候,已经被start的线程已经不再是(NEW)状态了,所以无论如何是不能再次启动的。
2、终止线程最优雅的方式——interrupt
网上基本上流传着三种终止线程的方式:常规方式:interrupt;有点逼格的方式:volatile;已经淘汰的方式:stop
其中,volatile方式最有迷惑性,在有些情况下会造成bug。
最优雅的方式:interrupt
interrupt方法并不是一种强制性终止线程的方式,它相当于是一个信号,interrupt方法触发之后,子线程是否停止,是子线程说的算。
public class RightWayStopThreadInProd implements Runnable{
@Override
public void run() {
while (true){
System.out.println("子线程执行任务");
try {
throwInMethod();
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("子线程收到interrupt中断信息");
}
}
}
private void throwInMethod() throws InterruptedException {
Thread.sleep(2000);
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new RightWayStopThreadInProd());
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
}
运行结果:
可以看到,interrupt之后,并没有终止线程,而实抛出了interrupt的异常。这正验证了,interrupt只是一个信号,在捕捉到interrupt信息之后,可以根据具体的业务,决定是否真正停止线程。
例如,若要终止线程,可以加一个break。
为什么不用volatile?
volatile的通常做法是声明一个Boolean类型的标志位,当需要终止线程时,改变这个标志位,从而跳出循环。
为什么不用volatile呢?
设想,假如,在while循环内发生了阻塞,那么这个线程就永远无法结束。。
//生产者、消费者
//生产者生产速度快,消费者消费速度慢,当产品过剩,生产者阻塞,消费者在特定情况下停止消费,此时发生阻塞
ublic class WrongWayVolatileCantStop {
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue storage = new ArrayBlockingQueue(10);
Producer producer = new Producer(storage);
Thread producerThread = new Thread(producer);
producerThread.start();
Thread.sleep(1000);
Consumer consumer = new Consumer(storage);
while (consumer.needMoreNums()){
System.out.println(consumer.storage.take()+"被消费");
Thread.sleep(100);
}
System.out.println("消费者不需要更多数据了");
//当消费者不需要更多数据,生产者也应该停下来
producer.canceled = true;
}
}
class Producer implements Runnable{
public volatile boolean canceled = false;
BlockingQueue storage;
public Producer(BlockingQueue storage) {
this.storage = storage;
}
@Override
public void run() {
int num = 0;
try {
while (num <= 100000 && !canceled) {
if (num % 100 == 0) {
//最后此处发生阻塞,导致无法结束
storage.put(num);
System.out.println(num + "是100的倍数。");
}
num++;
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("生产者结束运行");
}
}
}
class Consumer{
BlockingQueue storage;
public Consumer(BlockingQueue storage){
this.storage=storage;
}
public boolean needMoreNums(){
if (Math.random()>0.95){
return false;
}
return true;
}
}
运行结果:阻塞之后,程序无法结束
为什么不用stop?
Oracle官方解释:https://docs.oracle.com/javase/1.5.0/docs/guide/misc/threadPrimitiveDeprecation.html
因为它本质上是不安全的。停止线程会使它解锁它已锁定的所有监视器。(当ThreadDeath异常在堆栈中传播时,监视器将被解锁 。)如果以前由这些监视器保护的任何对象处于不一致状态,则其他线程现在可能会以不一致状态查看这些对象。据说这类物体已损坏。当线程对损坏的对象进行操作时,可能会导致任意行为。此行为可能是微妙的,难以检测,或者可能是明显的。与其他未经检查的异常不同,它会 ThreadDeath无声地杀死线程。因此,用户没有警告其程序可能已损坏。在实际损坏发生后的任何时间,甚至未来数小时或数天,腐败都会显现出来。
关于interrupt和isInterrupt
public static void main(String[] args) {
Runnable runnable = () -> {
System.out.println(Thread.currentThread().getName());
};
Thread thread = new Thread(runnable);
thread.start();
System.out.println(thread.isInterrupted());
thread.interrupt();
System.out.println(thread.isInterrupted());
System.out.println(Thread.interrupted());
System.out.println(thread.interrupted());
}
执行结果:
第一个和第二个比较明显,isInterrupted就是判断一下当前线程的状态
而第三和第四个返回的结果都是false。interrupted是一个静态方法,它不管是谁调用的,返回的都是执行这个方法的线程的状态,而执行它的线程是主线程,没有被中断,因此返回的都是false。