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

Posted 林小鹿@

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了实现多线程的方法到底有1种还是2种还是4种?相关的知识,希望对你有一定的参考价值。


目录

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不支持双继承

以上是关于实现多线程的方法到底有1种还是2种还是4种?的主要内容,如果未能解决你的问题,请参考以下文章

Redis到底是多线程还是单线程?线程安全吗

用户线程还是内核线程,pthread到底是哪个?

Java开启线程的4种方式

Java开启线程的4种方式

Java开启线程的4种方式

java基础--31.多线程常见的面试题