前言 ´・ᴗ・`
-
继上一次我们初识了线程 进程 多线程等概念,也学会使用继承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 从多线程到并发编程(三)——线程停止的方式
- 本文专栏
- 我的其他专栏 希望能够帮到你 ( •̀ ω •́ )✧
- 谢谢大佬支持! 萌新有礼了:)