Java 从多线程到并发编程(二)——Runnable Callable

前言 ´・ᴗ・`

  • 继上一次我们初识了线程 进程 多线程等概念,也学会使用继承Thread类的方式来实现多线程

  • 本节将会帮助你了解…

    • 实现Runnable接口的方式 实现多线程效果
    • 实现Callable接口的方式 实现多线程效果

Runnable

为什么要用Runnable,我们知道Java单继承的特点,意味着如果必须通过继承Thread的方式来实现多线程,那就不能继承其他的类了,使用起来变得非常受限,没法继承,因此我们要换成 实现implement的方式,实现Runnable接口,间接实现“多继承”,这样我们的程序结构不再受限,可以使用设计模式。

具体操作上,说白了 也就是从原来的 extend Thread变成implement Runnable
demo我简单修改了一下

package com.base.threadlearning.download_pic;

public class Runnable_download_pic implements Runnable {
    private String url;
    private String name;
    public Runnable_download_pic(String url){
        this.url = url;
        this.name = "default";
    }
    @Override
    public void run(){
        Downloader downloader = new Downloader();
        downloader.download(url,name);
        System.out.println("download done");

    }

    public static void main(String[] args) throws InterruptedException {
        String _url = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1604058853499&di=65a5ee6cea043252fd2385db8658e11f&imgtype=0&src=http%3A%2F%2Fcdn.duitang.com%2Fuploads%2Fitem%2F201410%2F17%2F20141017162807_4JuXA.thumb.700_0.jpeg";
        Runnable_download_pic runnable_download_pic = new Runnable_download_pic(_url);

        long start_run = System.currentTimeMillis();
        for (int i = 0; i < 3; i++) {
            new Thread(runnable_download_pic,String.valueOf(i)).run();
        }

        System.out.println("run耗时 " +(System.currentTimeMillis() - start_run) + "ms");

        long start_start = System.currentTimeMillis();
        
        for (int i = 0; i < 3; i++) {
            Thread temp = new Thread(runnable_download_pic,String.valueOf(i));
            temp.start();
            temp.join();
        }

        System.out.println("start-join耗时 " +(System.currentTimeMillis() - start_start) + "ms");

    }


}

将两者的耗时都打印出来,我们可以发现多线程确实在时间上有优势:
在这里插入图片描述

Thread与Runnable的对比讨论

那么观察代码,相对于Thread实现 Runnable的优势还有什么?

观察代码,我们发现,这种方式只需要一个Runnable_download_pic类,然后我们可以复用之,类似于根据一个模板(模板思维),设置多个线程机器人,这样代码上看起来非常简洁。而Thread必须造多个类,一个机器人就必须一个类,造成类的泛滥,而且这些类实际上只会被使用一次。。。 这效率太低,代码维护性太差。

有人可能会问了,难道Runnable不需要造多个类嘛?我觉得确实不需要,因为他可以继承,可以实现接口,可以组合,你可以有别的各种方法保证类不会泛滥。

另外,其实,真要说的话Thread也并非无可救药,如果抛开单继承这个问题上,我们确实可以活学活用一下设计模式,比如我们可以弄个工厂来批量生产,避免类的泛滥,提升代码的可维护性,亦或是我们可以借助Android里边的思想,对只是用一次的类,我们其实可以使用匿名内部类,上一节末尾我们讨论过这个问题。

然而不幸的是,正是因为单继承,导致这种方式不够灵活,而且实际使用中,仅仅实现Runnable接口的这种方式也不能完全满足需求,更别说利用设计模式苟且出来的继承Thread这种方式了。

Why?

Callable

因为,随着需求的不断增加 我们发现Runnable也有个问题 就是不能返回结果,不能抛出异常 因为Runnable只是个普通接口,处理错误就只能在run()里面处理了,而Callable不同,它是一个泛型接口,具有返回值(返回值就是泛型V),并且能够抛出异常

不过Callable功能强大的同时也让人难以理解

它一般与线程池接口(ExecutorService)以及Future(表示异步运算结果的对象)配合使用

所以一般是先声明一个线程池 然后提交(submit),之后用一个Future对象来接收线程运行结果

最后通过get方法获取Future对象存储的结果信息

package com.base.threadlearning.download_pic;

import java.util.concurrent.*;

public class Callable_download_pic implements Callable<Boolean> {
    private String url;
    private String name;
    public Callable_download_pic(String url, String name){
        this.name = name;
        this.url = url;
    }
    @Override
    public Boolean call(){
        Downloader downloader = new Downloader();
        downloader.download(url,name);
        System.out.println("download"+name+"done");
        return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        String _url = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1604058853499&di=65a5ee6cea043252fd2385db8658e11f&imgtype=0&src=http%3A%2F%2Fcdn.duitang.com%2Fuploads%2Fitem%2F201410%2F17%2F20141017162807_4JuXA.thumb.700_0.jpeg";
        String _name = "D:\\Users\\Lenovo\\IdeaProjects\\jvm_base_knowledge\\src\\main\\java\\com\\base\\threadlearning\\download_pic\\saber\\";
        Callable_download_pic callable_download_pic_1 = new Callable_download_pic(_url,_name+"1.jpg");
        Callable_download_pic callable_download_pic_2 = new Callable_download_pic(_url,_name+"2.jpg");
        Callable_download_pic callable_download_pic_3 = new Callable_download_pic(_url,_name+"3.jpg");

        ExecutorService service = Executors.newFixedThreadPool(3);

        Future<Boolean> result_1 = service.submit(callable_download_pic_1);
        Future<Boolean> result_2 = service.submit(callable_download_pic_2);
        Future<Boolean> result_3 = service.submit(callable_download_pic_3);

        System.out.println(result_1.get());
        System.out.println(result_2.get());
        System.out.println(result_3.get());

        service.shutdown();
    }

}


Runnable —— 可以执行的任务

我们上一篇文章其实讲了 继承Thread的子类,与真正的线程,互相的关系,那你说Runnable实现的类又是什么?这里千万注意,实现Runnable接口的类实例,应该认为是 可以执行(Runnable)的任务,而不是与线程一对一的外壳,他与继承Thread的类实例不一样

正因为如此,任务相对于线程的外壳 更具有灵活性,抽象程度也更高。类似于工厂模式,我们将一个Runnable任务实体,配置好以后当参数传入Thread,就可以轻松地灵活地造出很多线程外壳,最终实现更多的线程。

当然了 那你说实现了Callable的是什么?其实是功能更加强大的任务

总结 ´◡`

下一节 Java 从多线程到并发编程(三)——线程停止的方式

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值