实现多线程的方法到底有1种还是2种还是4种?

图片.png

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情

1、Oracle官网的文档是如何写的?

  • 方法一:实现Runnable接口
  • 方法二:继承Thread类

实现Runnable接口

package threadcoreknowledge.createthreads;

/**
 * 描述: 用Runnable方式创建线程
 */
public class RunnableStyle implements Runnable{
    public static void main(String[] args) {
        Thread thread = new Thread(new RunnableStyle());
        thread.start();
    }
    public void run() {
        System.out.println("用Runnable方式创建线程");
    }
}
复制代码

继承Thread类

package threadcoreknowledge.createthreads;

/**
 * 描述:  用Thread方式实现线程
 */
public class ThreadStyle extends Thread{
    @Override
    public void run() {
        System.out.println("用Thread方式实现线程");
    }

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

2、两种方法的对比

方法1(实现Runnable接口)更好。

方法2的缺点:

  • 从代码的架构来考虑,具体执行的任务(run方法里面的内容)应该是和Thread解耦的,不应该把这两件事情混为一谈;
  • 从资源的节约上,继承了 Thread 类,每次我们想新建一个任务只能去新建一个独立的线程,而新建一个独立的线程损耗是十分大的(因为需要去创建,销毁);
  • 继承了Thread 类,由于Java不支持双继承,导致这个类无法去继承其他类了,这大大限制了代码的可扩性。

两种方法的本质对比

  • 方法一:最终调用target.run();
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
复制代码
  • 方法二:run()整个都被重写

3、思考题:同时用两种方法会怎么样?

package threadcoreknowledge.createthreads;

/**
 * 描述  同时使用Runnable和Thread两种实现线程的方式
 */
public class BothRunnableThread {
    public static void main(String[] args) {
        new Thread(new Runnable() {  //传入Runnable对象
            @Override
            public void run() {
                System.out.println("我来自Runnable");
            }
        }){
            @Override				//重写run()方法
            public void run() {      	
                System.out.println("我来自Thread");
            }
        }.start();
    }
}
复制代码

代码执行结果:

从面向对象的思想去考虑: 因为我们重写了run()方法,所以导致Thread的三行run()方法代码不再存在,即使传入传入Runnable对象,它也不再执行。

4、总结:最精准的描述

1、通常我们可以分为两类,Orracle也是这么说的

2、准确的讲,创建线程只有—种方式那就是构造Thread类,而实现线程的执行单元有两种方式

  • 方法一:实现Runnable接口的run方法,并把Runnable实例传给Thread类;
  • 方法二:重写Thread的run方法(继承Thread类)。

5、典型错误观点分析

1、"线程池创建线程也算是一种新建线程的方式"

package threadcoreknowledge.wrongways;

import javafx.concurrent.Task;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPools {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 1000; i++) {
            executorService.submit(new Tasks() {
            });
        }
    }
}
class Tasks implements Runnable{

    @Override
    public void run() {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName());
    }
}
复制代码

代码执行结果:

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;
}
复制代码

点进去源码可以看到,线程池本质创建线程的方法是new Thread,因此这并不是一种新的创建线程方式 。

2、"通过Callable和FutureTask创建线程,也算是一种新建线程的方式"

本质是实现Runnable接口和继承Thread类实现的。

3、"无返回值是实现runnable接口,有返回值是实现callable接口,所以callable是新的实现线程的方式"

本质依旧是实现Runnable接口和继承Thread类实现的。

4、定时器

package threadcoreknowledge.wrongways;

import java.util.Timer;
import java.util.TimerTask;

/**
 * 描述:      定时器创建线程
 */
public class DemoTimerTask {
    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);
    }
}
复制代码

代码执行结果:

5、匿名内部类

package threadcoreknowledge.wrongways;

public class AnonymousInnerClassDemo {
    public static void main(String[] args) {
        new Thread(){
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        }.start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        }).start();
    }
}
复制代码

代码执行结果:

本质一样。

6、Lambda表达式

package threadcoreknowledge.wrongways;

public class Lambda {
    public static void main(String[] args) {
        new Thread(()-> System.out.println(Thread.currentThread().getName())).start();
    }
}
复制代码

代码执行结果:

本质一样。

6、典型错误观点总结

多线程的实现方式,在代码中写法千变万化,但其本质万变不离其宗。他们通过各种各样的包装,比如线程池,定时器,包装的外表好像是实现线程池的一种方式,但是我们打开包装透过源码去看到他最终实现的道理的话,本质其实是实现Runnable接口和继承Thread类。

7、实现多线程——常见面试问题

有多少种实现线程的方法?思路有5点:

  1. 从不同的角度看,会有不同的答案。
  2. 典型答案是两种
  3. 我们看原理,两种本质都是一样的
  4. 具体展开说其他方式
  5. 结论

实现Runnable接口和继承Thread类哪种方式更好?

  1. 从代码架构角度
  2. 新建线程的损耗
  3. Java不支持双继承

猜你喜欢

转载自juejin.im/post/7082722150091587621