Java创建多线程的方式只有两种

Java并发方面有很多书籍以及博客,针对于线程创建方式有着不同描述,例如实现Runnable接口、集成Thread类、使用线程池工具类以及结合Callable和Future创建线程等。

创建线程的两种方式

Oracle官方文档,即java.lang.Thread类注释的表述是有如下两种创建线程的方式。https://docs.oracle.com/javase/8/docs/api/index.html

方式一:实现Runnable接口,传入Thread类

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类,重写run方法

public class ThreadStyle extends Thread {
    @Override
    public void run() {
        System.out.println("用Thread方式创建线程");
    }

    public static void main(String[] args) {
        new ThreadStyle().start();
    }
}

两种方式的比较

推荐采用实现Runnable接口,传入Thread类。

  • 代码架构角度:具体执行的任务(run方法)应该和创建/运行线程的机制(Thread类)解耦。
    与重写Thread类run方法一样,实现Runnable接口run方法的内容也是具体执行的任务。但是Runnable方式则可以创建单独任务类实现Runnable接口,然后传入任务实例到Thread类中。这样同一个任务类可以传给不同的Thread,同时任务类不需要负责创建线程等工作,因此是解耦的。

  • 继承的角度上,Java是单继承,如果继承了Thread类就无法继承其它类,限制可扩展性。

  • 资源节约的角度上讲,如果是Thread类方式,每次新建一个任务只能新建一个独立的线程,会有额外线程创建/销毁等损耗。而Runnable更加方便采用线程池工具,减少创建、销毁线程带来的损耗。

两种方式本质对比

两种方式在多线程实现的本质上是一致的,都是调用thread对象的start方法创建线程。主要区别在于run方法的内容来源上。

# java.lang.Thread
private Runnable target;
@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

实现Runnable方式,最终调用了Runnable对象target的run方法;而继承Thread方式是重写了整个run方法。

示例:代码同时使用两种方式
匿名内部类实现Runnable接口,并在类内部重写run方法。重写run方法的代码直接覆盖Thread类run方法代码,Runnable中run方法实现代码不会执行。

Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("使用Runnable方式创建线程");
    }
}) {
    @Override
    public void run() {
        System.out.println("使用Thread方式创建线程");
    }
};
thread.start();

多线程的其他代码实现形式

线程池创建线程的方式

public class ThreadPoolStyle {
    public static void main(String[] args) {
        // 不提倡的创建线程池方式--原因参见阿里规约
        ExecutorService pool = Executors.newCachedThreadPool();
        for (int i = 0; i < 1000; i++) {
            pool.submit(new Task());
        }
    }
}

class Task implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName());
    }
}

我们通过Executors创建线程池,深入源码可以看到,内部创建ThreadPoolExecutor时会使用ThreadFactory。以DefaultThreadFactory为例,其内部创建线程的方法newThread仍然是通过Runnable创建线程。

## java.util.concurrent.Executors
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创建线程的方式

public class CallableFutureTaskStyle {
    public static void main(String[] args) {
        // 创建任务,启动线程
        FutureTask<String> futureTask = new FutureTask<>(new CallableTask());
        new Thread(futureTask).start();
    }
}

class CallableTask implements Callable {
    @Override
    public String call() throws Exception {
        return "使用Callable和FutureTask创建线程";
    }
}

Callable接口是一个独立的接口,用于创建任务,通常与FutureTask配合使用。而FutureTask的父类是Runnable,因此其本质仍然是Runnable方式创建线程。

在这里插入图片描述

定时器创建线程

public class TimerStyle {
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        },100,100);
    }
}

定时器任务类TimerTask实现Runnable接口。

public abstract class TimerTask implements Runnable

匿名内部类和Lambda代码实现形式

多线程的代码实现方式有很多种,但其本质仍然是出于继承Thread和实现Runnable接口两种方式。线程池、Callable以及定时器,对创建线程做了一定的封装,但本质仍然没有变。至于线程匿名内部类和Lambda实现,只是代码实现形式的不同,不能作为实现线程的方式。

new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}).start();
new Thread(() -> System.out.println(Thread.currentThread().getName())).start();

较为准确的描述

(1)首先从不同的角度看,会有不同的答案。例如从代码实现,还是从本质上说。

(2)通常我们可以分为两种,分别是实现Runnable接口和继承Thread类。Thread类的注释也是这样表述的。

(3)描述Runnable方式和Thread方式的不同(3个角度)。

(4)其实Thread类实现了Runnable接口,类中实现了run方法。可以发现两种方式在创建线程的本质上是一样的,都是调用Thread对象的start方法,主要区别在于run方法内容的来源不同:Runnable方式最终是调用Ruannble对象target的run方法,而Thread方式则是使用了重写的run方法。

(5)还有其他实现线程的方式,例如线程池也能新建线程,但是细看源码,其本质是Runnable方式。

(6)准确的讲,创建线程只有一种方式,那就是创建Thread类,而实现线程的run方法有两种方式。除此之外,从表面上看线程池、定时器等工具类也可创建线程,但是本质没有变。

发布了72 篇原创文章 · 获赞 110 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/LIZHONGPING00/article/details/104118829