zoukankan      html  css  js  c++  java
  • 多线程实现方式

    多线程有几种实现方式?如果被问到这个问题一定很头疼,因为百度一下随便就能出现各种各样的答案。两种、三种、四种、五种、六种、七种。。。

    但本质上来讲,个人认为只有一种方式:实现Runnable接口。

    先放个图:

     1、实现Runnable接口

     1 public class DemoThreadTask implements Runnable{
     2     @Override
     3     public void run() {
     4         // TODO Auto-generated method stub
     5     }
     6     
     7     public static void main(String[] args) {
     8         DemoThreadTask task = new DemoThreadTask();
     9         Thread t = new Thread(task);
    10         t.start();
    11         ...
    12     }
    13 }

    实现Runnable接口,利用Runnable实例构造Thread,是较常用且最本质实现。

    此构造方法相当于对Runnable实例进行一层包装,在线程t启动时,调用Thread的run方法从而间接调用target.run()

     1 public class Thread implements Runnable {
     2     /* What will be run. */
     3     private Runnable target;
     4 
     5     public void run() {
     6         if (target != null) {
     7             target.run();
     8         }
     9    }
    10      ...
    11 }

    2、继承Thread类

     1 public class DemoThread extends Thread{
     2     @Override 
     3     //重写run方法
     4     public void run() {
     5         // TODO Auto-generated method stub
     6     }
     7 
     8     public static void main(String[] args) {
     9         DemoThread t = new DemoThread();
    10         t.start();
    11         ...
    12     }
    13 }

    这种实现方式是显示的继承了Thread,但从类图中我们可以看到,Thread类本身就继承自Runnable,所以继承Thread的本质依然是实现Runnable接口定义的run方法。

    需要注意的是继承Thread方式,target对象为null,重写了run方法,导致方式1中的Thread原生的run方法失效,因此并不会调用到target.run()的逻辑,而是直接调用子类重写的run方法。

    因为java是单根继承,此方式一般不常用。

    3、实现Callable接口并通过FutureTask包装

     1 public class DemoCallable implements Callable<String>{
     2     @Override
     3     public String call() throws Exception {
     4         // TODO Auto-generated method stub
     5         return null;
     6     }
     7     
     8     public static void main(String[] args) throws Exception {
     9         DemoCallable c = new DemoCallable();
    10         FutureTask<String> future = new FutureTask<>(c); 
    11         Thread t = new Thread(future);
    12         t.start();
    13         ...
    14         String result = future.get(); //同步获取返回结果
    15         System.out.println(result);
    16     }
    17 }

    实现Callable接口通过FutureTask包装,可以获取到线程的处理结果,future.get()方法获取返回值,如果线程还没执行完,则会阻塞

    这个方法里,明明没有看到run方法,没有看到Runnable,为什么说本质也是实现Runnable接口呢

    回看开篇的类图,FutureTask实现了RunnableFuture,RunnableFuture则实现了Runnable和Future两个接口。

    因此构造Thread时,FutureTask还是被转型为Runnable使用。因此其本质还是实现Runnable接口。

    至于FutureTask的工作原理,后续篇章继续分析。

    4、匿名内部类

    匿名内部类也有多种变体,上述三种方式都可以使用匿名内部类来隐式实例化。

     1 public class Demo{
     2     
     3     public static void main(String[] args) throws Exception {
     4         //方式一:Thread匿名内部类
     5         new Thread(){
     6             @Override
     7             public void run() {
     8                 // TODO Auto-generated method stub
     9             }
    10         }.start();
    11         
    12         //方式二:Runnable匿名内部类
    13         new Thread(new Runnable() {
    14             @Override
    15             public void run() {
    16                 // TODO Auto-generated method stub
    17             }
    18         }).start();
    19         
    20         ...
    21     }
    22 }

    匿名内部类的优点在于使用方便,不用额外定义类,缺点就是代码可读性差。

    5、Lambda表达式

    Lambda表达式是jdk8引入的,已不是什么新东西,现在都jdk10了。demo如下:

    1 public class Demo{
    2     public static void main(String[] args) throws Exception {
    3         new Thread(() -> System.out.println("running") ).start() ;
    4         ...
    5     }
    6 }

    如此简洁的Lambda表达式,有没有吸引到你呢?当然本质不多说,还是基于Runnable接口。

    6、线程池

     1 public class DemoThreadTask implements Runnable{
     2     @Override
     3     public void run() {
     4         // TODO Auto-generated method stub
     5         System.out.println("running");
     6     }
     7     
     8     public static void main(String[] args) {
     9         DemoThreadTask task = new DemoThreadTask();
    10         ExecutorService ex = Executors.newCachedThreadPool();
    11         ex.execute(task);
    12         ...
    13     }
    14 }

    线程池与前面所述其他方式的区别在于执行线程的时候由ExecutorService去执行,最终还是利用Thread创建线程。

    线程池的优势在于线程的复用,从而提高效率。

    7、定时器

     1 public class DemoTimmerTask {
     2 
     3     public static void main(String[] args) throws Exception {
     4         Timer timer = new Timer();
     5         timer.scheduleAtFixedRate((new TimerTask() {
     6             @Override
     7             public void run() {
     8                 System.out.println("定时任务1执行了....");
     9             }
    10         }), 2000, 1000);
    11     }
    12 }

    TimerTask的实现了Runnable接口,Timer内部有个TimerThread继承自Thread,因此绕回来还是Thread + Runnable。

    总结,多线程的实现方式,在代码中写法千变万化,但其本质万变不离其宗。

    本质都是实现 Runnable 。

    参考:

    https://www.jianshu.com/p/7950ea349dbb

  • 相关阅读:
    vs2008生成的各种文件
    spider_keeper
    scrapyd 参考(https://www.jianshu.com/p/2a189127901a)
    Django之ORM操作
    ES6 快速入门
    vue 数据(data)赋值问题
    vue嵌套路由-query传递参数(三)
    Vue页面加载时,触发某个函数的方法
    vue从一个页面跳转到另一个页面并携带参数
    单选框radio总结(获取值、设置默认选中值、样式)
  • 原文地址:https://www.cnblogs.com/Vincent-yuan/p/15106694.html
Copyright © 2011-2022 走看看