Start和run的区别
public class StartAndRun {
public static void main(String[] args) {
Runnable runnable = ()->{
System.out.println(Thread.currentThread().getName());
};
runnable.run();//main
new Thread(runnable).start();//thread-0
}
}
通过上面的代码打印分析得出:启动线程的方式是start方法,注意start的调用顺序不一定代表线程的启动顺序。start是被主线程调用的。所以首先会创建main。run只是作为一个普通方法被main线程调用。另外注意在jvm中找那个Thread run 方法执行完会notify
//当在join或者wait时并没有代码唤醒。而是在run执行完毕后jvm唤醒 public class JoinPrinciple { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("子线程执行完毕"); } }); thread.start(); System.out.println("等待子线程执行完毕"); // thread.join();//这一行等价于下面三行 synchronized (thread){ thread.wait(); } System.out.println("所有子线程执行完毕"); } }
interrupt 通知正确停止线程
使用interrupt()来通知,而不是强制。
boolean isInterrupted() 这个方法不会清除中断标记。
static boolean interrupted() 这个方法会清除中断标记。它的目标对象是执行这个线程的对象。这行代码谁执行就返回谁的状态。如果这个方法被连续调用两次第二个调用将返回false
停止线程的情况
- run方法内没有sleep和wait时,通常我们使用isInterrupted()方法配合中断默认是false
package com.study.thread;
//当run方法中没有sleep和wait
public class RightWayStopWithOutSleep implements Runnable{
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new RightWayStopWithOutSleep());
thread.start();
thread.sleep(1);
thread.interrupt();
}
@Override
public void run() {
//模拟一个小程序
int num = 0;
while (! Thread.currentThread().isInterrupted() && num < Integer.MAX_VALUE/2){
if(num %10000 == 0 ){
System.out.println(num+"是10000的倍数");
}
num++;
}
System.out.println("运行结束");
}
}
- run方法内有sleep方法时,响应中断的方式就是抛出InterruptedException
package com.study.thread;
public class RightWayStopWithSleep {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = ()->{
int num = 0;
while(! Thread.currentThread().isInterrupted() && num <= 300){
if(num % 100 == 0){
System.out.println(num+"是100的倍数");
}
num++;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
Thread thread = new Thread(runnable);
thread.start();
Thread.sleep(500);//让主线程睡500毫秒
thread.interrupt();
}
}
- 每次迭代都会阻塞,不需要再判断是否已经中断。但是要注意catch异常的范围,在while内进行trycatch是无法进行中断的。因为sleep响应中断后会清除interruppt标记位置
package com.study.thread;
public class RightWayStopWithSleepEveryLoop {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = ()->{
int num = 0;
try{
while(/**! Thread.currentThread().isInterrupted() &&*/ num <= 10000){
if(num % 100 == 0){
System.out.println(num+"是100的倍数");
}
num++;
Thread.sleep(10);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
};
Thread thread = new Thread(runnable);
thread.start();
Thread.sleep(5000);//让主线程睡5000毫秒
thread.interrupt();
}
}
中断的最佳实践
- 优先选择:传递中断。方法如果有中断异常那就网上抛出直到run方法。然后在run方法中进行catch处理
package com.study.thread;
public class RightWayStopInProd {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = ()->{
int num = 0;
while(true){
System.out.println("go");
try {
throwExceptionMethod();
} catch (InterruptedException e) {
//进行保存日志的等处理
e.printStackTrace();
}
}
};
Thread thread = new Thread(runnable);
thread.start();
Thread.sleep(500);//让主线程睡500毫秒
thread.interrupt();
}
private static void throwExceptionMethod() throws InterruptedException {
Thread.sleep(1000);
}
}
- 不想或者无法传递:恢复中断。在catch中调用Thread.currentThread().interrupt()方法
package com.study.thread;
public class RightWayStopInProd2 {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = ()->{
while(true){
if(Thread.currentThread().isInterrupted()){
System.out.println("go");
break;
}
throwExceptionMethod();
}
};
Thread thread = new Thread(runnable);
thread.start();
Thread.sleep(500);//让主线程睡500毫秒
thread.interrupt();
}
private static void throwExceptionMethod() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
}
响应中断的方法列表
- sleep
- wait
- join
- take/put
- lockInterruptibly
- countDownLatch.await
- CyclicBarrier.await
- Exchange.exchange(V)
- nio.channels.Selector/InterruptibleChannel
常见错误停止线程的方法
- stop:导致某个最小单元的数据没有执行完造成脏数据会释放锁
- suspend/resume:不释放锁容易造成死锁
- 用volatile修饰标boolean标记,无法唤醒阻塞线程。下面具体示例
package com.study.thread.volitiledemo;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class WrongWayStopBlockWithVolatile {
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue<Integer> storages = new ArrayBlockingQueue<>(10);
Producer producer = new Producer(storages);
Thread producerThread = new Thread(producer);
producerThread.start();
Thread.sleep(1000);//模拟生产者生产慢阻塞时
Consumer consumer = new Consumer(storages);
while(consumer.needMordNum()){
System.out.println("消费者消费"+storages.take());
Thread.sleep(100);
}
System.out.println("消费者不需要再消费了");
producer.canceld = true;
System.out.println(producer.canceld);
}
}
class Producer implements Runnable{
private BlockingQueue<Integer> storage;
public volatile boolean canceld = false;
public Producer(BlockingQueue<Integer> storage) {
this.storage = storage;
}
@Override
public void run() {
int num = 0;
try {
while ( num < 10000 && !canceld){
if( num % 10 == 0){
System.out.println(num+"是10的倍数,放入storage");
storage.put(num);
}
num++;
}
}catch (InterruptedException e){
e.printStackTrace();
}finally {
System.out.println("生产者结束运行");
}
}
}
class Consumer{
private BlockingQueue<Integer> storage;
public Consumer(BlockingQueue<Integer> storage) {
this.storage = storage;
}
//模拟业务需要消费的情况
public boolean needMordNum(){
if(Math.random()>0.95){
return false;
}
return true;
}
}