实现多线程的正确姿势

孤街醉人 提交于 2020-01-13 21:57:49

一:实现多线程的方法有几种?

Oracle官网的文档已经告诉了我们:

  • 方法一:继承Thread类
public class ThreadStyle extends Thread{

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

    @Override
    public void run() {
        System.out.println("用Thread类实现线程");
    }
}
  • 方法二:实现Runnable接口
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方法实现线程");
    }
}

二:两种方法的对比

方法二(实现Runnable接口)更好,why? 那我们分析一下方法一的缺点

  • 从代码架构的角度考虑,具体执行的任务,也就是run()方法里面的内容,它应该和线程的创建,运行这个机制(Thread类)应该是解耦的,所以我们不应该把这两个事情混为一谈。
  • 从资源的损耗考虑,我们每次新建一个任务,就需要创建一个独立的线程,这样的损耗是比较大的,它需要创建,执行,销毁。而如果我们使用runnable,我们就可以利用线程池这类的工具,大大减小创建线程,销毁线程所带来的损耗。
  • 从Java不支持多继承的角度考虑,Java的单继承性带来的局限性,这就很容易导致我们的代码不易扩展。

两种方法的本质对比

  • 方法一:run()方法整个都被重写
  • 方法二:最终调用target.run()方法

源码分析

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

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

public class BothRunnableThread {

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("来自runnable");
            }
        }){
            @Override
            public void run() {
                System.out.println("来自Thread");
            }
        }.start();
    }

}

答案如下,你想到了吗

E:\tools\jdk1.8.0_201\bin\java.exe com.example.demo.createthreads.BothRunnableThread
来自Thread

Process finished with exit code 0

总结

根据Oracle官方文档,通常我们可以分为两类,准确的讲,创建线程只有一种方式,那就是构造Thread类,而实现线程的执行单元有两种方式(Thread类的run()方法有两种不同的情况)

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

彩蛋之典型错误观点分析

  • 错误观点一:线程池创建线程也算是一种新建线程的方式。

代码

public class ThreadPoolStyle {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for(int i = 0;i<100;i++){
            executorService.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());
    }
}

源码分析

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;
        }

通过源码可以看到,事实上它用的还是Thread类。

  • 错误观点二:通过Callable和FutureTask创建线程,也算是一种新建线程的方式。
    通过类图我们可以直观的看到它们也是离不开Thread和Runnable的

  • 错误观点三:定时器

public class TimmerTask {

    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);
    }
}

同线程池一样,这个一样离不开Thread类。

典型错误观点总结

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!