首页  >  问答  >  正文

java - 为什么要将Runnable接口的子类对象传递给Thread的构造函数?

此外,runnable相比thread除了继承方面,代码和数据独立体现在哪?像有些博客上写的thread不能共享资源,runnable能共享资源,将thread中的变量改成static不就行了吧?就像下面这篇说的http://blog.csdn.net/uudou/ar...

巴扎黑巴扎黑2708 天前1548

全部回复(3)我来回复

  • 漂亮男人

    漂亮男人2017-06-12 09:21:22

    跟数据似乎关系不大,我觉得Runnable有两个好处:

    1. 实现Runnable以后,即可以开个线程跑(一般是用executorService.exec(command),挫一点也可以用new Thread(command).start()),也可以不开线程阻塞式的跑(直接调用command.run());

    2. Java 1.8以后可以用Lambda来跑,例如:

    new Thread(() -> {
        // Do something
    }).start();
    
    
    

    回复
    0
  • 代言

    代言2017-06-12 09:21:22

    Runnable的好处是各种场景都可以用,比如你可以让任何一个Class implements Runnable,但是extends Thread就有一些限制,因为Java单继承的原因,在有些场景下没法用。

    回复
    0
  • 天蓬老师

    天蓬老师2017-06-12 09:21:22

    回答:

    这个问题算是一个设计问题。

    之所以将 Thread 和 Runnable 分开,是希望把线程的 "创建过程" 与线程的 "执行逻辑" 彻底分开。

    也就是说:
    线程的创建过程是“代码”;
    线程的执行逻辑是“数据”;

    这听起来有点叫人晕呼,不都是 JAVA 代码么?怎么代码又变成数据了呢?

    我们不在这些概念上纠缠,我觉得可以倒转过来思考这个问题,举个例子来说明问题。

    讨论过程:

    例如我要设计一个单线程程序,这个单线程需要完成两个任务:

    1、打印一句 hello world;
    2、计算一下 int a 与 int b 两个数的和并输出;

    注意:到底是执行 1? 还是 2?是由参数 n 来决定的,n 是一个随机数……

    为了让这两个任务在同一个线程里执行,我们可以写这样的代码:

    float n = (float)Math.random();
    
    int a = 1;
    int b = 1;
    
    Thread t = new Thread() {
        @Override
        public void start() {
            if (n >= 0.5f) {
                System.out.println("hello world");
            } else {
                System.out.println(a + b);
            }
        }
    };
    
    t.start();
    

    上面的代码确实是可以完成任务的,但问题是我们把线程的 "创建过程" 和 "业务逻辑" 混淆在一起了……

    这样不太妙。顺便说一句,从操作系统层面来看,线程的创建过程其实是非常复杂的!

    Java 语言把这种复杂性都封装得看不见了,虽然代码上就是一个 Thread 类,调用起来似乎也没什么门槛,但 Thread 的创建过程还是很复杂、很消耗资源的。

    言归正传,现在我再次加入一个小小的需求,除了前面的 1、2,我再加入一个 3,显示一下系统当前时间戳。

    于是任务变成了:
    1、打印一句 hello world;
    2、计算一下 int a 与 int b 两个数的和并输出;
    3、显示一下系统当前时间戳;

    注意,这时候我们需要修改 Thread 的创建过程,也就是修改 start 函数:

    float n = (float)Math.random();
    
    int a = 1;
    int b = 1;
    
    Thread t = new Thread() {
        @Override
        public void start() {
            if (n >= 0.33f) {
                System.out.println("hello world");
            } else if (n >= 0.66f) {
                System.out.println(a + b);
            } else {
                System.out.println(System.currentTimeMillis()); // 这里是新增的语句
            }
        }
    };
    
    t.start();
    

    讨论至此,让我们仔细观察观察……其实:

    Thread t = new Thread() {
        @Override
        public void start() {
            // ...
        }
    }
    

    这部分代码是不变的,只有 start 函数里面的代码是随着需求变化而修改的。

    那么我们可不可以把这部分变化的内容包装成一个接口??

    这应该是一个不错的主意!

    Thread t = new Thread() {
        private Runnable runnable; // 这里定义一个 Runnable 类型的成员
    
        @Override
        public void start() {
            if (null != this.runnable) {
                runnable.run(); // 在这里用接口来把频繁变化的业务逻辑从线程代码里给拿出去,只调用 run 函数
            }
        }
    }
    

    到这里不知道你是否已经完全明白了? :D

    哈哈,Java 的 Thread 类不是刚好提供了一个带有 Runnable 参数的构造器么?

    我们将业务代码被放到 Runnable 接口的实现类里:

    class BizLogicRun implements Runnable {
        @Override
        public void run() {
            float n = (float)Math.rand();
    
            int a = 1;
            int b = 1;
    
            if (n >= 0.33f) {
                System.out.println("hello world");
            } else if (n >= 0.66f) {
                System.out.println(a + b);
            } else {
                System.out.println(System.currentTimeMillis()); // 这里是新增的语句
            }
        }
    }
    

    那么最后,我们可以这么调用:

    Thread t = new Thread(new BizLogicRun());
    t.start();
    

    这样就完成了线程的 "创建过程" 和 "业务逻辑" 彻底拆分!这种 "拆分" 也为 Java 线程池(Thread Pool)技术做好了铺垫。

    说实话,示例代码中的 Thread t = new Thread() { ... } 这个还是够简单的,但在线程池中创建 Thread 可就没这么简单了。

    所以 "拆分" 是非常有必要的!

    另外,我们是否可以想象:

    class PoolRun implements Runnable {
        List<Runnable> runnableList;
    }
    

    如果 Runable 实现类里面,夹带的还是一个 Runnable 列表会怎么样呢?

    总结:

    1、使用 Runnable 接口的目的是把线程的 "创建过程" 与线程的 "执行逻辑" 彻底分开;
    2、Thread 不能共享资源,Runnable 能共享资源,这个说法是不正确的;
    3、在讨论过程中我们是从具体到抽象;
    4、我在例子中给出的代码确实比较简单,但希望能说明白问题;

    好了,以上就是我对这个问题的回答,希望对你有所帮助。

    回复
    0
  • 取消回复