目录

Life in Flow

Embrace your dreams and unlock your full potential at every stage of life.

X

线程的创建、启动、停止

线程的创建方式:继承 Thread

 run()整个都被重写

  • 任务与线程类高度耦合。
  • 每次新建任务都需要创建独立的线程,如果使用 Runnable 则可以利用线程池,大大减少创建线程和销毁线程的开销。
  • Java 只允许单继承,影响扩展性。
public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.setName("线程demo");
        myThread.start();
    }
}

线程的创建方式:实现 Runnable

 最终调用 target.run()

  • 任务与线程类解耦
  • 可扩展
  • 解决资源开销(利用线程池)
public class MyRunable implements Runnable,Serializable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
        try {
            System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunable());
        thread.setName("soulboy");
        thread.start();  // 开启新的线程执行  soulboy
//        thread.run();   // 在当前线程下执行 main
    }
}

同时用两种方法会怎样?

 由于继承 Thread 类并且重写 run() 方法, 会覆盖掉 Thread 类原有的 run()方法。
 Thread 类原有 run() 方法用于调用 target.run() 来执行传入 Runable 对象的 run() ,由于继承 Thread 时被覆盖了,所以 Runable 对象的 run() 得不到调用。
 最终输出结果: 我来自 Thread

@Override
    public void run() {
        if (target != null) {
            target.run();
        }

 基于 Runnable 接口的匿名内部类,再重写了 run()方法。

/**
 * 描述:同时使用Runnable和Thread两种实现线程的方式
 */
public class BothRunnableThread extends Thread implements Runnable{

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("我来自Runnable");
            }
        }) {
            @Override
            public void run() {
                System.out.println("我来自Thread");
            }
        }.start();

/*        Thread thread1 = new Thread(new BothRunnableThread());
        thread1.start();

        Thread thread2 = new BothRunnableThread();
        thread2.start();*/

    }

/*    @Override
    public void run() {
        System.out.println("继承Thread");
    }*/
}

创建线程的方式有几种

 创建线程的方式只有一种方式:就是构造 Thread 的类,Thread 本身实现了 Runable 接口。
 实现线程的执行单元有两种方式。

* extends Thread
* implements Runnable

实现 Runable:匿名内部类

/**
 * 匿名内部类的方式
 */
public class MyThread {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        });
        thread.start();
    }
}

实现 Runable:Lambda 表达式

public class Lambda {
    public static void main(String[] args) {
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName());
        }).start();
    }
}

实现 Runable:线程池

public class ThreadPool {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        executorService.execute(()->{
            System.out.println(Thread.currentThread().getName());//pool-1-thread-1
        });
    }
}

start() 和 run() 的比较

 调用 start() 方法之后,运行中的 main 线程会通知 JVM 在空闲时候启动新线程。线程什么时候执行由线程调度器所决定,无法保证立刻启动新线程并执行。(线程饥饿的情况) new Thread(runnable).start() 指令本身是由主线程执行的。
 新线程的准备工作

  • 就绪状态:已获取除 CPU 之外的其他资源:上下文、栈、线程状态、寄存器等…
  • 执行状态:等待获取 CPU 资源。
  • 运行状态:执行 run() 方法中代码逻辑。
* run() 不会开启新的执行路径,只是普通的方法,用于添加执行线程逻辑,但其本身并不会开启新的线程,所以依然是在主线程下按顺序执行
* start() 会开启新的执行路径(新线程)
package com.xdclass.couponapp.test.start;

/**
 * 对比 start & run
 */
public class StartAndRunMethod {
    public static void main(String[] args) {
        Runnable runnable = () -> {
            System.out.println(Thread.currentThread().getName());//main
        };
        runnable.run();
        new Thread(runnable).start();//Thread-0
        new Thread(runnable).run();//main
    }
}

连续两次调用 start() 方法

Exception in thread "main" java.lang.IllegalThreadStateException
 线程生命周期无法逆转,无法从 TERMINATED 状态变为 NEW 状态。

源码分析

  • 启动新线程检查线程状态,0 代表初始化尚未启动的状态。
if (threadStatus != 0)
            throw new IllegalThreadStateException();
  • 加入线程组
  • 调用 start0() native 方法
package com.xdclass.couponapp.test.start;

public class CantStartTwice {
    public static void main(String[] args) {
        Thread thread = new Thread(
                () -> System.out.println(Thread.currentThread().getName())
        );
        thread.start();
        thread.start();
    }
}

如何正确停止线程

  • 原理:用 interrupt 来请求的优点
  • 想终止线程,需要请求方、被停止方、子方法被调用方相互配置。
  • 错误方法:stop/suspend 已废弃、volatile 的 boolean 无法处理长时间阻塞的情况。

应该使用 interrupt 来通知,而不是强制使用 stop()
 正确的停止线程:如何正确的使用 interrupt 来通知线程,以及被停止的线程如何配合 interrupt。
 想让线程快速、安全、可靠的停止下来并不容易,Java 没有一种机制安全正确的停止线程。
 Java 提供了 interrupt ,一种合作机制(使用一个线程通知另外一个线程,让它停止工作)。
 被通知要 interrupt 的线程自身拥有最终决定权:被 interrupt 线程本身更加了解自己的业务状态,而不是通知 interrupt 线程。

使用 interrupt 的优点

  • 被中断的线程自身拥有如何响应中断的权利(有些线程的某些代码必须具备原子性,必须要等待这些写成处理完成之后,或是准备好之后),再由它们自己主动终止。
  • 被中断线程可以完全不理会中断(如果有需要的话),不应该鲁莽的使用 stop()方法,而是 interrupt()
  • 需要遵循良好编码规范:让被告知中断的线程自身可以响应中断。

响应中断的方法列表
 以下方法可以响应 interrupt,响应的方式为抛出 InterruptedException

* Object.wait()  
* Thread.sleep()  
* Thread.join  
* java.util.concurrent.BlockingQueue.take() /put()  
* java.util.concurrent.locks.Lock.lockInterruptibly()  
* java.util.concurrent.CountDownLatch.await()  
* java.util.concurrent.CyclicBarrier.await()  
* java.util.concurrent.Exchanger.exchange(V)  
* java.nio.channels.InterruptibleChannel相关方法  
* java.nio.channels.Selector的相关方法

最佳实践:普通情况下如何停止线程

package com.xdclass.couponapp.test.stopThreads;

import com.sun.org.apache.xerces.internal.dom.PSVIAttrNSImpl;

/**
 * run() 内没有 sleep() 或 wait() 时,停止线程
 */
public class RightWayStopThreadWithoutSleep implements Runnable {

    @Override
    public void run() {
        int num = 0;
        //添加线程可以响应中断的判断逻辑,否则线程不会理会interrupt,直到循环结束。
        while ( !Thread.currentThread().isInterrupted() && num <= Integer.MAX_VALUE /2){
            if(num % 10000 == 0){
                System.out.println(num + "是10000的倍数.");
            }
            num++;
        }
        System.out.println("任务运行结束了.");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new RightWayStopThreadWithoutSleep());
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}

613930000是10000的倍数.  
613940000是10000的倍数.  
任务运行结束了.

最佳实践:线程被阻塞情况下停止线程
 当线程处于休眠的过程中,如果接收到 interrupt 信号,响应 interrupt 的方式是抛出 java.lang.InterruptedException: sleep interrupted 异常。
 中断线程的写法:catch() 捕获异常,响应 interrupt 信号。

package com.xdclass.couponapp.test.stopThreads;

/**
 * 带有 sleep() 的中断线程的写法
 */
public class RightWayStopThreadWithSleep {
    public static void main(String[] args) throws InterruptedException {
        //匿名内部类(基于接口)
        Runnable runnable = () -> {
            int num = 0;
            try {
                while (num <= 300 && !Thread.currentThread().isInterrupted()) {
                    if (num % 100 == 0) {
                        System.out.println(num + "是100的倍数");
                    }
                    num++;
                }
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        //启动线程
        Thread thread = new Thread(runnable);
        thread.start();
        //在线程处于休眠状态时:发起 interrupt
        Thread.sleep(500);
        thread.interrupt();

    }

}
0是100的倍数  
100是100的倍数  
200是100的倍数  
300是100的倍数  
java.lang.InterruptedException: sleep interrupted  
 at java.lang.Thread.sleep(Native Method)  
 at com.xdclass.couponapp.test.stopThreads.RightWayStopThreadWithSleep.lambda$main$0(RightWayStopThreadWithSleep.java:18)  
 at java.lang.Thread.run(Thread.java:748)

最佳实践:如果线程在每次迭代后都阻塞

package com.xdclass.couponapp.test.stopThreads;

/**
 * 如果在执行过程中,每次循环都会 sleep() 或 wait() ...
 * 那么不需要每次都迭代都检查 Thread.currentThread().isInterrupted()
 * 因为 sleep() 过程中的线程接收到 interrupt 消息会抛出异常,因此无需检查
 * 只需捕获处理即可
 */
public class RightWayStopThreadWithSleepEveryLoop {

    public static void main(String[] args) throws InterruptedException {
        //匿名内部类(基于接口)
        Runnable runnable = () -> {
            int num = 0;
            try {
                while (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();
        //在线程处于休眠状态时:发起 interrupt
        Thread.sleep(5000);
        thread.interrupt();
    }
}

0是100的倍数
100是100的倍数
200是100的倍数
300是100的倍数
400是100的倍数
java.lang.InterruptedException: sleep interrupted

try/catch 在 while 中会导致中断失效
 不要在 while 循环中 使用 try/catch 处理 sleep() 对 interrupt 的响应,中断的效果会失效。

* 问什么会一直循环?
   答:interrupt 被捕获在循环内部,并没有出错,因此循环会继续。
* 为什么 Thread.currentThread().isInterrupted() 判断不生效?
   答:sleep() 响应 interrupt 时候除了抛出异常,还会一并清除线程的 isInterrupted 标记位。
package com.xdclass.couponapp.test.stopThreads;

/**
 * 如果 while 循环中使用 try/catch 响应中断,则会导致中断失效。
 */
public class CantInterrupt {
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            int num = 0;
            while (num <= 10000 && !Thread.currentThread().isInterrupted()) {
                if (num % 100 == 0) {
                    System.out.println(num + "是100的倍数.");
                }
                num++;
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        //启动线程
        Thread thread = new Thread(runnable);
        thread.start();
        //在线程处于休眠状态时:发起 interrupt
        Thread.sleep(5000);
        thread.interrupt();

    }
}

0是100的倍数.
100是100的倍数.
200是100的倍数.
300是100的倍数.
400是100的倍数.
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.xdclass.couponapp.test.stopThreads.CantInterrupt.lambda$main$0(CantInterrupt.java:16)
	at java.lang.Thread.run(Thread.java:748)
500是100的倍数.
600是100的倍数.

实际开发中的两种最佳实践

优先选择:传递中断 (方法的签名上抛出异常)

package com.xdclass.couponapp.test.stopThreads;

/**
 * 最佳实践 : catch 了 InterruptedException 之后优先选择:在方法签名中抛出异常。
 * 那么在 run() 就会强制 try/catch
 */
public class RightWayStopThreadInProd implements Runnable{

    @Override
    public void run() {
        //try嵌套在while循环外面的意义是:中断while循环(否则只会抛出异常,while会继续循环)
        try {
            while (true) {
                System.out.println("go");
                throwInMethod();
            }
        } catch (InterruptedException e) {
            //保存日志 ....
            e.printStackTrace();
        }
    }

    /**
     * 如果此方法是为其他线程做调用,一定要选择抛出异常,而不是在内部做异常处理
     * 这样可以防止本方法吞噬 interrupt 
     * 应该把处理 interrupt 的方式选择器 交给上层的调用者
     * @throws InterruptedException
     */
    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();
    }
}

不想或无法传递:恢复中断

package com.xdclass.couponapp.test.stopThreads;

/**
 * 最佳实践2 : 在 catch 子句中调用Thread.currentThread().interrupt() 来回复设置中断状态
 * 以便在后续的执行中,让调用者能够检查到刚才发生的中断。
 */
public class RightWayStopThreadInProd2 implements Runnable {
    @Override
    public void run() {
        while (true) {
            System.out.println("go");
            reInterrupt();
            if (Thread.currentThread().isInterrupted()){
                //记录日志
                System.out.println("reInterrupt 方法运行期间发生中断!运行结束");
                break;
            }
            System.out.println("go 2");
        }
    }

    /**
     *  虽然 catch 住了 interrrupt 引发的异常
     *  但是没有独吞,而且是重新恢复了 interrupt
     * @throws InterruptedException
     */
    private void reInterrupt() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            //重新设置 interrupt
            Thread.currentThread().interrupt();
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new RightWayStopThreadInProd2());
        thread.start();
        Thread.sleep(2000);
        thread.interrupt();
    }
}
go
go 2
go
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.xdclass.couponapp.test.stopThreads.RightWayStopThreadInProd2.reInterrupt(RightWayStopThreadInProd2.java:29)
	at com.xdclass.couponapp.test.stopThreads.RightWayStopThreadInProd2.run(RightWayStopThreadInProd2.java:12)
	at java.lang.Thread.run(Thread.java:748)
reInterrupt 方法运行期间发生中断!运行结束

不应该屏蔽中断
 独吞中断,并且不恢复中断。

停止线程相关重要函数解析

interrupt() 方法为什么可以中断处于 wait 状态的线程?
 追踪源码直至 private native void interrupt0(); (本地方法)
 进入 github(也可以进 OpenJDK 网站)

* 设置 interrupted 状态为 true
* _SleepEvent 对应 Thread.sleep()
…… 看不懂

判断是否已经被中断的相关方法

# 用于判断当前线程中断状态的同时,清除线程中断状态
static boolean interrupted():返回当前线程是否已经被中断状态,如果是则返回 true,之后会把线程的中断状态设置为 false。
# 用于判断线程中断状态
boolean isInterrupted():返回当前线程是否已经被中断。
package com.xdclass.couponapp.test.volatiledemo;

/**
 * 描述:注意Thread.interrupted()方法的目标对象是“当前线程”,而不管本方法来自于哪个对象
 */
public class RightWayInterrupted {

    public static void main(String[] args) throws InterruptedException {

        Thread threadOne = new Thread(new Runnable() {
            @Override
            public void run() {
                for (; ; ) {
                }
            }
        });

        // 启动线程
        threadOne.start();
        //设置中断标志 (threadOne线程被设置为中断)
        threadOne.interrupt();
        //获取中断标志
        System.out.println("isInterrupted: " + threadOne.isInterrupted()); //true (threadOne线程)
        //获取中断标志并重置
        System.out.println("isInterrupted: " + threadOne.interrupted()); //false  静态方法(main线程)
        //获取中断标志并重直
        System.out.println("isInterrupted: " + Thread.interrupted()); //false 静态方法(main线程)
        //获取中断标志
        System.out.println("isInterrupted: " + threadOne.isInterrupted());//true (threadOne线程)
        threadOne.join();
        System.out.println("Main thread is over.");
    }
}

如何处理不可中断阻塞

 没有银弹!!!根据特定情况使用特定的方法。

常见错误观点

线程池创建线程也算一种新建线程的方式

* 并不是构建线程的本质
* 查看源码发现线程池也是通过 new Thread(Runnable r)  和 new Thread() 。
package com.xdclass.couponapp.test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThradPool5 {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 1000; i++) {
            executorService.submit(new Task());
        }
    }

}

class Task implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName());
    }
}
pool-1-thread-977
pool-1-thread-980
pool-1-thread-1000

通过 Callable 和 FutureTask 创建线程,也算是一种新的创建线程的方式

* 并不是构建线程的本质
* 查看源码发现 FutureTask 实现了 RunnableFutrue,  RunnableFutrue 同时继承了 Runnable 、Future 两个接口。
* 所以本质是离不开 Runnable 接口的。

无返回值是实现 Runnable 接口,有返回值是实现 Callable 接口,所以 Callable 是新的实现线程的方式

* 并不是构建线程的本质
* 同上

定时器也是一种创建线程的方式

package com.xdclass.couponapp.test;

import java.util.Timer;
import java.util.TimerTask;

public class DemoTimerTask {
    public static void main(String[] args) {
        Timer timer = new Timer();
        //每个一秒钟打印一次
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        },1000,1000);
    }
}
Timer-0
Timer-0

匿名你内部类、Lambda 表达式也是一种创建线程的方式

* 很明显本质还是通过构建Thread,只是语法层面的蜜糖。
package com.xdclass.couponapp.test;

public class AnonymousInnerClassDemo {
    public static void main(String[] args) {
        //匿名内部类
        new Thread() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());//Thread-0
            }
        }.start();

        //匿名内部类(基于接口)
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());//Thread-1
            }
        }).start();

        //Lambda
        new Thread(
                () -> System.out.println(Thread.currentThread().getName())
        ).start();//Thread-2
    }
}

如何正确的停止线程?

  • stop、suspend、resume 方法
* stop 野蛮、它本质上不是安全的。停止线程会导它解锁已所以定的所有监视器。
* suspend、resueme已经废弃

stop 示例

package com.xdclass.couponapp.test.stopThreads;

/**
 * 错误的停止线程的方法: 用 stop() 来停止线程,会导致线程运行一半突然停止
 * 没办法完成一个基本单元的操作(一个连队),会造成脏数据(有的连队多领、有的连队少领)
 * 领取弹药
 * 其中有一个连队  居然有些人没有领取到(连队本身丢失了原子性)
 * 1连队0号士兵
 * 1连队1号士兵
 * 1连队2号士兵
 * 1连队3号士兵
 * 1连队4号士兵
 * 1连队5号士兵
 * 1连队6号士兵
 */
public class StopThread implements Runnable{
    @Override
    public void run() {
        //一共5个连队,每队10人,以连队为单位,方法武器弹药,叫到号的士兵前去领取。
        for (int i = 0; i < 5; i++) {
            System.out.println("连队" + i + "开始领取武器");
            for (int j = 0; j < 10; j++) {
                System.out.println(i + "连队" + j + "号士兵");
                try {
                    Thread.sleep(60);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(i + "连队完成领取工作");
        }
    }
    public static void main(String[] args) {
        Thread thread = new Thread(new StopThread());
        thread.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.stop();
    }
}
  • 用 volatile 设置 boolean 标记位

 volatile 示例(看上去似乎是可行的)

package com.xdclass.couponapp.test.volatiledemo;

import sun.tools.jconsole.Worker;

/**
 * 演示用 volatile 的局限。
 *
 * 0是100的倍数
 * 100是100的倍数
 * 200是100的倍数
 * 300是100的倍数
 * 400是100的倍数
 */
public class WrongWayVolatile implements Runnable{
    //让变量具有可见性
    private volatile boolean canceled = false;

    @Override
    public void run() {
        int num = 0;
        try {
            while (num <= 100000 && !canceled) {
                if (num % 100 == 0) {
                    System.out.println(num + "是100的倍数");
                }
                num++;
                Thread.sleep(10);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        WrongWayVolatile r = new WrongWayVolatile();
        Thread thread = new Thread(r);
        thread.start();
        Thread.sleep(5000);
        r.canceled = true;
    }
}

 volatile 示例(事实证明有局限性,无法在想要停止的时候停止线程)
 当陷入阻塞的时候,volatile 这种方式是无法停止线程的。
 以下示例发现无法终止生产者,生产者一直在运行,阻塞在队列上。

package com.xdclass.couponapp.test.volatiledemo;


import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.BlockingQueue;

/**
 * 当陷入阻塞的时候,volatile 这种方式是无法停止线程的。
 * 生产者的生产速度快,消费者消费速度慢
 * 阻塞队列满以后,生产者会阻塞,等待消费者进一步消费
 */
public 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;
        System.out.println(producer.canceled);
    }
}

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) {
                    //这里如果队列已经满,会导致线程一直处于阻塞状态
                    //队列被消费(少于10个)才会唤醒生产者线程
                    //如果队列一直没有被消费,那么永远不会执行到while循环的判断条件 !canceled
                    //所以就算是canceled已经被设置为 true,被阻塞在队列的生产者线程也无法被唤醒
                    //将一直处于阻塞状态
                    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;
    }
}

 用 interrupt() 代替 volatile 示例(还是 interrupt() 靠谱,修复版)

package com.xdclass.couponapp.test.volatiledemo;

import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

/**
 * 描述:     用中断来修复刚才的无尽等待问题
 * 想要实例化内部类,需要先要实例化外部类,这里仅仅是复习知识而已,仅此而已。
 * 为什么要在抛出InterruptedException的时候清除掉中断状态呢?
 * 这个问题没有找到官方的解释,估计只有Java设计者们才能回答了。
 * 但这里的解释似乎比较合理:一个中断应该只被处理一次(你catch了这个InterruptedException,
 * 说明你能处理这个异常,你不希望上层调用者看到这个中断)。
 */
public class WrongWayVolatileFixed {

    public static void main(String[] args) throws InterruptedException {
        WrongWayVolatileFixed body = new WrongWayVolatileFixed();
        ArrayBlockingQueue storage = new ArrayBlockingQueue(10);

        Producer producer = body.new Producer(storage);
        Thread producerThread = new Thread(producer);
        producerThread.start();
        Thread.sleep(1000);

        Consumer consumer = body.new Consumer(storage);
        while (consumer.needMoreNums()) {
            System.out.println(consumer.storage.take() + "被消费了");
            Thread.sleep(100);
        }
        System.out.println("消费者不需要更多数据了。");

        producerThread.interrupt();
        System.out.println(producerThread.isInterrupted()); //true
    }


    class Producer implements Runnable {

        BlockingQueue storage;

        public Producer(BlockingQueue storage) {
            this.storage = storage;
        }

        /**
         * 事实证明:除了 sleep()  其他被阻塞线程在响应 interrupt 的时候 也会清空 isInterrupted 标志位
         * 所以这里 使用 isInterrupted() 作为判断条件是毫无意义的
         * 需要借助 try.catch  套在 while 外面来中断 while 循环
         */
        @Override
        public void run() {
            int num = 0;
            try {
                //以下写法毫无意义:因为阻塞线程响应中断的时候会重置 isInterrupted 的值
                //while (num <= 100000 && !Thread.currentThread().isInterrupted()) {
                while (num <= 100000) {
                    if (num % 100 == 0) {
                        storage.put(num);
                        System.out.println(num + "是100的倍数,被放到仓库中了。");
                        System.out.println(Thread.currentThread().isInterrupted());
                    }
                    num++;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println(Thread.currentThread().isInterrupted());
            } finally {
                System.out.println("生产者结束运行");
                System.out.println(Thread.currentThread().isInterrupted());
            }
        }
    }

    class Consumer {

        BlockingQueue storage;

        public Consumer(BlockingQueue storage) {
            this.storage = storage;
        }

        public boolean needMoreNums() {
            if (Math.random() > 0.95) {
                return false;
            }
            return true;
        }
    }
}

4900被消费了
5900是100的倍数,被放到仓库中了。
false
5000被消费了
6000是100的倍数,被放到仓库中了。
false
消费者不需要更多数据了。
true
false
生产者结束运行
false
java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.reportInterruptAfterWait(AbstractQueuedSynchronizer.java:2014)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2048)
	at java.util.concurrent.ArrayBlockingQueue.put(ArrayBlockingQueue.java:353)
	at com.xdclass.couponapp.test.volatiledemo.WrongWayVolatileFixed$Producer.run(WrongWayVolatileFixed.java:56)
	at java.lang.Thread.run(Thread.java:748)

作者:Soulboy