并发基础之实现多线线程的正确姿势
实现多线程的方式到底是几种?
针对于java多线程的实现方式,不论是网上还是各种书籍都有同的观点,有的说的两种,有的说有四种,随便百度一下我们就能看到很多种答案:
那究竟是几种呢?下面我们来查找一下java的api文档:https://docs.oracle.com/javase/9/docs/api/java/lang/thread.html
从文档中我们可以清晰的看到,实现多线程有两种方式,一种是将一个类声明为的子类thread
,还有一种是声明一个实现runnable
接口的类。
两种实现方式对比
实现runnable接口:
/** * @author chen * @description 使用runnable接口实现多线程 * @create 2019-11-04 21:38 */ public class runnablestyle implements runnable{ public static void main(string[] args) { thread thread = new thread(new runnablestyle()); thread.start(); } @override public void run() { system.out.println("使用runnable接口实现多线程"); } }
继承thread类:
/** * @author chen * @description 使用thread方式实现多线程 * @create 2019-11-04 21:41 */ public class threadstyle extends thread{ public static void main(string[] args) { thread thread = new threadstyle(); thread.start(); } @override public void run() { system.out.println("使用thread类实现多线程"); } }
使用runable还是thread?
既然都可以实现多线程,那实际我们应该使用选择一种方式创建还是随便都可以呢?
答案是实现runable
方式更好。
原因主要有以下几点:
1.从解耦的角度来说,多线程执行的任务(也就是run方法的内容)应该与thread
类是解耦的,
而不是都写在一起。
2.如果使用继承thread
类来实现多线程,我们每次想新创建一个任务只能新建一个独立的线程,而这样的损耗是比较大的,需要去创建,销毁等。而使用runnable
接口的话后续我们可以使用线程池,这样可以减少新建线程带来的损耗。
3.因为java是单继承,一旦继承了thread
就不能在去继承其他的类,限制了程序的可扩展性。
runable和thread创建线程区别?
使用runable
的方式创建,我们是使用了一个thread
类一个带参的构造方法,使用thread
方法创建,是使用了thread
类的一个无参的构造方法,这两种方法都重写了run
方法,接下来我们来看一看thread
类中的run方法是如何实现的:
/* what will be run. */ private runnable target; /** * if this thread was constructed using a separate * <code>runnable</code> run object, then that * <code>runnable</code> object's <code>run</code> method is called; * otherwise, this method does nothing and returns. * <p> * subclasses of <code>thread</code> should override this method. * * @see #start() * @see #stop() * @see #thread(threadgroup, runnable, string) */ @override public void run() { if (target != null) { target.run(); } }
以上是一部分thread
类的源码,我们可以看到执行run
方法的时候,会先判断是否传入runable
接口的实现类,如果传入了,就执行接口实现类中的run
方法,如果没传入,则会直接执行子类重写的run
方法,所以本质上来说,这两种方法本质上都是执行了run
方法,只不过run
方法的来源不同。
错误观点
线程池创建线程也算是一种新建线程的方式
/** * @author chen * @description 使用线程池创建线程 * @create 2019-11-05 9:40 */ public class threadpoolstyle { public static void main(string[] args) { executorservice executorservice = executors.newcachedthreadpool(); for (int i = 0; i <1000 ; i++) { executorservice.submit(new task()); } } static class task implements runnable{ @override public void run() { try { thread.sleep(500); } catch (interruptedexception e) { e.printstacktrace(); } system.out.println(thread.currentthread().getname()); } } }
下面简单看一下excutors
内部是如何创建线程的:
可以看到在下面的代码中线程池的内部新建线程也是通过传入一个runnable
,然后在new thread
来创建的,所以虽然外表看起来创建的方式不太一样,但是原理都是通过runnable
接口来实现的多线程。
/** * the default thread factory */ static class defaultthreadfactory implements threadfactory { private static final atomicinteger poolnumber = new atomicinteger(1); private final threadgroup group; private final atomicinteger threadnumber = new atomicinteger(1); private final string nameprefix; defaultthreadfactory() { securitymanager s = system.getsecuritymanager(); group = (s != null) ? s.getthreadgroup() : thread.currentthread().getthreadgroup(); nameprefix = "pool-" + poolnumber.getandincrement() + "-thread-"; } public thread newthread(runnable r) { thread t = new thread(group, r, nameprefix + threadnumber.getandincrement(), 0); if (t.isdaemon()) t.setdaemon(false); if (t.getpriority() != thread.norm_priority) t.setpriority(thread.norm_priority); return t; } }
通过callable和futuretask创建线程也算是一种新建线程的方式
通过callable
和future
:
/** * @author chen * @description 使用callable、future以及futuretask * @create 2019-11-05 10:03 */ public class futuretaskstyle { public static void main(string[] args) throws executionexception, interruptedexception { executorservice executorservice = executors.newcachedthreadpool(); future<string> future = executorservice.submit(new callable<string>() { @override public string call() throws exception { thread.sleep(500); return "future result"; } }); system.out.println(system.currenttimemillis()); system.out.println(future.get()); system.out.println(system.currenttimemillis()); } }
通过callable
和futuretask
:
/** * @author chen * @description 使用callable、future以及futuretask * @create 2019-11-05 10:03 */ public class futuretaskstyle { public static void main(string[] args) throws executionexception, interruptedexception { executorservice executorservice = executors.newcachedthreadpool(); futuretask<string> futuretask = new futuretask<string>(new callable<string>() { @override public string call() throws exception { thread.sleep(500); return "future result"; } }); new thread(futuretask).start(); system.out.println(system.currenttimemillis()); system.out.println(futuretask.get()); system.out.println(system.currenttimemillis()); } }
使用ctrl+alt+u
查看futuretask
的继承关系图:可以看到runablefuture
继承了future
和runnable
接口,而futuretask
又实现了runablefuture
,所以futuretask
也间接的实现了runable
接口。所以说此种方式和使用runnable
实现的方式原理是相同的。
通过定时器
/** * @author chen * @description 使用定时器创建新的线程 * @create 2019-11-05 10:26 */ public class timmerstyle { public static void main(string[] args) { timer timer = new timer(); timer.scheduleatfixedrate(new timertask() { @override public void run() { system.out.println(thread.currentthread().getname()); } },1000,1000); } }
看看timertask
也是实现了runnable
接口,本质上也是使用runnable
接口方式创建。
通过匿名内部类或lambda表达式
/** * @author chen * @description 使用匿名内部类 和lamada表达式实现多线程 * @create 2019-11-05 10:36 */ public class anonymousinnerclassstyle { public static void main(string[] args) { //匿名内部类 new thread(new runnable() { @override public void run() { system.out.println(thread.currentthread().getname()); } }).start(); //lambda 表达式 new thread(()-> system.out.println(thread.currentthread().getname())).start(); } }
这种形式只是语法改变了一下,其实匿名内部类和lambda 表达式实现的效果都是一样的,只是更换了一种写法。
总结
实现多线程到底有几种方法?从不同角度看,会有不同的答案,如果从实现多线程的本质上看,一般有两种,一种是实现runnable
接口,一种是继承thread
类;而从代码实现层面看,会有很多种,如线程池,futuretask
匿名内部类和lambda
表达式等。
其实看原理,本质上的两种实现方式也是一样的,都是调用了thread
类的run
方法,而run
方法中有一个判断,如果传入的runnable
不为空,就执行run
方法本身的方法内的代码。
一般情况下我们会使用runable
接口的方式新建线程,主要出于以下原因:
从程序耦合的角度来看,把要提交的任务代码和thread
类中的代码分离开,实现低耦合;
从可拓展角度来看,因为java是单继承的,使用继承thead
类的方式就无法在继承其他的基类,降低了程序的课拓展性;
从资源消耗的角度看,使用runnable
的方式我们可以使用线程池来管理线程,可以减少新建线程销毁线程等带来的资源损耗。
下一篇: 奇亚籽怎么吃,营养价值又有哪些
推荐阅读