实现多线程的方法:2种?3种?4种?

      Java中有多少种方式实现线程呢?百度一下发现答案不唯一,这就尴尬了。所以本文主要探究实现多线程的具体方式。
实现多线程的方式

      抛弃搜索引擎,我们之间看Java的官方文档,可以知道我们有2种方式实现线程。
请输入图片描述

  1. 继承Thread类
  2. 实现Runnable接口

继承Thread类

/**
 * 继承Thread实现线程
 */
public class SubThread extends Thread {
    @Override
    public void run(){
        System.out.println("继承Thread实现线程");
    }

    public static void main(String[] args) {
        Thread thread = new SubThread();
        thread.start();
    }
}

实现Runnable接口

/**
 * 实现Runnable接口实现线程
 */
public class BothRunnableThread{

    public static void main(String[] args) throws Exception {
       new Thread(()-> System.out.println("from Runnable")){
           @Override
           public void run(){
               System.out.println("from thread");
           }
       }.start();

    }

}

选用哪种

      推荐实现Runnable接口创建线程。

  1. 避免了单继承的局限性

一个类只能继承一个类(一个人只能有一个亲爹),类继承了Thread类就不能继承其他的类
实现了Runnable接口,还可以继承其他的类,实现其他的接口

  1. 增强了程序的扩展性,降低了程序的耦合性(解耦)

实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
实现类中,重写了run方法:用来设置线程任务
创建Thread类对象,调用start:用来开启新线程

底层区别

      以上两种实现线程的方式有什么不一样呢?底层实现是什么呢?现在我们来稍微看一下Thread类的源代码吧!

/* What will be run. */
private Runnable target;

public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}

public void run() {
    if (target != null) {
        target.run();
    }
}

      可以发现如果Thread的target赋值了就使用Runnable实现类的run方法,否则使用Thread的run方法。那么,如果我们同时使用这两种方式创建线程会如何呢?

public static void main(String[] args) throws Exception {
    new Thread(()-> System.out.println("from Runnable")){
        @Override
        public void run(){
            System.out.println("from thread");
        }
    }.start();
}
/**
 * 运行结果为:from thread
 * 因为我们重写了Thread的run方法,缺少了
 * if (target != null) {
 *      target.run();
 * }
 * 所以Runnable实现类的run方法根本不会被使用
 */

其他问题观点

  • 线程池创建线程
public class ThreadPoolCreate {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            executorService.submit(new TaskDemo());
        }
    }
}

class TaskDemo implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

      没错,线程池可以创建线程共我们使用,但是,我们要揭开表象看本质,默认使用Executors.DefaultThreadFactory类创建线程,它的底层如下代码所示,r就是Runnable的实现类。所以本质上还是没脱离以上两种方法。

public Thread newThread(Runnable r) {
    Thread t = new Thread(group, r,namePrefix + threadNumber.getAndIncrement(),0);
    ...
}
  • 通过Callable和FutureTask创建线程
/**
 * 通过实现接口,重写call()方法【有返回值】,通过 FutureTask装饰,最终也是需要Thread.start()方法启动线程
 */
public class FutureTaskDemo{
    public static void main(String[] args) {
        FutureTask futureTask = new FutureTask(new Callable() {
            @Override
            public Object call() throws Exception {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return 1;
            }
        });

        new Thread(futureTask).start();
        try {
            System.out.println(futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

FutureTask类图

      通过UML类图不难看出FutureTask本质上还是是实现Runnable接口。

总结

      多线程的实现方式很多,但是其本质依旧是继承Thread类和实现Runnable接口。面试回答实现线程的方式,思路如下:

  • 从不同角度看有不同的答案
  • 官方文档是两种
  • 查看其它实现方式发现本质上依旧是上文所提及的两种
  • 具体说其它方式
  • 结论
Last modification:February 10th, 2020 at 09:43 pm
如果觉得我的文章对你有用,请随意赞赏