Runnable引发的血案

618 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

面试

面试java开发的时候作为面试官,我提了一个问题:

用多线程实现一下生产者和消费者模式

面试者回答的很标准就是通过队列实现生产者和消费者 以下是java伪代码实现

MQ  //可以是消息队列也可以是数据库
#创建生产者
ThreadPoolExecutor producter  //生产者线程池
for(int i=0;i<m;i++){
    Producter worker = new Thread(()->{
        //TODO:
        // 生产数据到队列
        MQ.put(data)
    });
    producter.execute(worker);
}

#创建消费者
ThreadPoolExecutor consumer  //消费者线程池
for(int i=0;i<n;i++){
    Consumer worker = new Consumer(()->{
        //TODO:
        //从队列消费数据
        MQ.get()
    });
    consumer.execute(worker);
}

本来到这里就可以结束了,但是由于面试的是高级开发,我也觉得自己提问有些low于是我又问了一句:

生产者和消费者都是实现了Runnable接口的?如果这个系统出现了问题,你如何排查是哪个生产者线程或者消费者线程发生了问题?

问完这个问题,气氛就开始有些微妙了,面试者回答的就开始扯的有些远,开始分析java内存,或者使用共享变量存储线程状态等 由于这个问题,面试者最后被leader刷掉了。

问题的根本

其实面试者回答的还可以,但是他没看到问题的根本,就是Runnable接口

Runnable 接口是 JDK1.0 的核心产物

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

曾经的它是多线程编程的标杆线程(Thread)的执行入口也都是Runnable的,但是时代在变,现在都2202年了,最新的java都到17了。 Runnable它无参数无返回无异常,这样的三无产品在做复杂的核心业务时你真的还要用么?

Callable更好的选择

早在 Java 1.5 这个神奇的版本中 大神Doug Lea 就创造了Callable这个接口,有参数,有返回,还有异常,这不比Runnable香?

/*
 * @see Executor
 * @since 1.5
 * @author Doug Lea
 * @param <V> the result type of method {@code call}
*/
@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

看一下Runnable 和Callable的对比图

callable.jpeg 除了不能在Thread类中单独执行,它完全可以取代Runnable接口

Future给你一个承诺

多线程,也就是异步的,我从哪里获得执行结果呢?

嗯,回想一下js 的Promise,这个大概就是程序员的浪漫吧,js中时个你一个Promise保证,保证在执行完成时通过then告诉你结果

还是大神Doug Lea :

java则是给你一个Future未来 在未来通知你执行结果。

actions following the corresponding {@code Future.get()} in another thread.

/*
 * <p>Memory consistency effects: Actions taken by the asynchronous computation
 * <a href="package-summary.html#MemoryVisibility"> <i>happen-before</i></a>
 * actions following the corresponding {@code Future.get()} in another thread.
 *
 * @see FutureTask
 * @see Executor
 * @since 1.5
 * @author Doug Lea
 * @param <V> The result type returned by this Future's {@code get} method
 */
public interface Future<V> {
*/

实验演示

看看future是如何在未来通知你执行结果的

import java.io.IOException;
import java.util.concurrent.*;

public class FutureImpl {
    static ThreadPoolExecutor pool = new ThreadPoolExecutor(10,10,1, TimeUnit.MINUTES,new ArrayBlockingQueue<>(10));
    public static void main(String[] args) throws IOException {
        Callable<String> goodday = ()->{
            System.out.println("start good day!");
            Thread.sleep(5000);
            return "have a good day!";
        };
        Callable<String> badday = ()->{
            System.out.println("start bad day!");
            Thread.sleep(2000);
            int a = 1;
            int b = 0;
            return "have a bad day!"+(a/b);
        };
        Future<String> future1 = pool.submit(goodday);
        Future<String> future2 = pool.submit(badday);

        pool.execute(()->{
            try {
                System.out.println("future1 "+future1.get());
            } catch (Exception e) {
                System.err.println(e.getMessage());
            }
        });
        pool.execute(()->{
            try {
                System.out.println("future2 "+future2.get());
            } catch (Exception e) {
                System.err.println(e.getMessage());
            }
        });
        System.in.read();
    }
}

执行结果

future1.jpg

今天就到这里吧,have a good day!