Warum ein Unterklassenobjekt der Runnable-Schnittstelle an den Thread-Konstruktor übergeben?
巴扎黑
巴扎黑 2017-06-12 09:19:22
0
3
1578

Wie spiegelt sich außerdem im Vergleich zu Threads, abgesehen von der Vererbung, die Unabhängigkeit von Code und Daten in Runnable wider? Wie in einigen Blogs geschrieben, kann Thread keine Ressourcen gemeinsam nutzen, aber Runnable kann Ressourcen gemeinsam nutzen. Reicht es nicht aus, die Variablen im Thread in statisch zu ändern? Genau wie im folgenden Artikel: http://blog.csdn.net/uudou/ar...

巴扎黑
巴扎黑

Antworte allen(3)
漂亮男人

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

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

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

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


代言

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

Peter_Zhu

回答:

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

之所以将 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、我在例子中给出的代码确实比较简单,但希望能说明白问题;

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

Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage