美文网首页
1.1为什么说本质只有一种实现线程的方式

1.1为什么说本质只有一种实现线程的方式

作者: printf200 | 来源:发表于2020-10-14 22:13 被阅读0次

    1.实现Runnable接口究竟比继承Thread类实现线程好在哪里?

    实现线程的方式到底有几种?

    第一种

    
    public class RunnableThread  implements Runnable{
        @Override
        public void run() {
            System.out.println("用实现Runnable接口实现线程");
        }
    }
    
    

    首先通过RunnableThread类实现Runnable接口
    然后重写了run方法
    之后只要把这个实现的run方法的实例传到Thread类中去就可以实现多线程。

    第二种方式

    public class ExtendsThread extends Thread{
        
        
        @Override
        public void run(){
            System.out.println("用Thread类实现线程");
        }
        
        
    }
    

    与第一种方法不同的是它没有实现接口,而是继承Thread类,并重写了其中的run方法

    第三种实现方式 线程池创建线程

    线程池实现多线程,比如可以给线程池的线程数量设置为10,那么就会有10个子线程来完场工作

    线程池源码
    static class DefaultThreadFactory implements ThreadFactory {
     
        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                poolNumber.getAndIncrement() +
                "-thread-";
        }
     
    
        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;
        }
    }
    
    

    对于线程池而言,本质上是通过线程工厂来创建线程的,默认采用DefaultThreadFactory,他会给线程池设置一些默认值,比如:线程的名字,是否守护线程,以及线程的优先级等。但是无论怎么设置这些属性,最终还是会通过new Thread()创建线程,只不过构造函数传入的参数要多一些。所以本质上还是通过new Thread()实现的。

    第四种 有返回值的Callable创建线程

    class CallableTask implements Callable<Integer> {
    
        @Override
        public Integer call() throws Exception {
            return new Random().nextInt();
        }
    }
    
    //创建线程池
    ExecutorService service = Executors.newFixedThreadPool(10);
    //提交任务,并用 Future提交返回结果
    Future<Integer> future = service.submit(new CallableTask());
    
    

    第 4 种线程创建方式是通过有返回值的 Callable 创建线程,Runnable 创建线程是无返回值的,而 Callable 和与之相关的 Future、FutureTask,它们可以把线程执行的结果作为返回值返回,如代码所示,实现了 Callable 接口,并且给它的泛型设置成 Integer,然后它会返回一个随机数。
    但是,无论是 Callable 还是 FutureTask,它们首先和 Runnable 一样,都是一个任务,是需要被执行的,而不是说它们本身就是线程。它们可以放到线程池中执行,如代码所示, submit() 方法把任务放到线程池中,并由线程池创建线程,不管用什么方法,最终都是靠线程来执行的,而子线程的创建方式仍脱离不了最开始讲的两种基本方式,也就是实现 Runnable 接口和继承 Thread 类。

    其他创建方式

    定时器 Timer

    class TimerThread extends Thread {
    //具体实现
    }
    

    比如,定时器也可以实现线程,如果新建一个 Timer,令其每隔 10 秒或设置两个小时之后,执行一些任务,那么这时它确实也创建了线程并执行了任务,但如果我们深入分析定时器的源码会发现,本质上它还是会有一个继承自 Thread 类的 TimerThread,所以定时器创建线程最后又绕回到最开始说的两种方式。

    其他方法

    /**
     *描述:匿名内部类创建线程
     */
    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }).start();
    
    }
    }
    
    

    比如匿名内部类或 lambda 表达式方式,实际上,匿名内部类或 lambda 表达式创建线程,它们仅仅是在语法层面上实现了线程,并不能把它归结于实现多线程的方式,如匿名内部类实现线程的代码所示,它仅仅是用一个匿名内部类把需要传入的 Runnable 给实例出来。

    new Thread(() -> System.out.println(Thread.currentThread().getName())).start();
    }
    

    我们再来看下 lambda 表达式方式。如代码所示,最终它们依然符合最开始所说的那两种实现线程的方式。

    实现线程只有一种方式

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

    首先,启动线程需要调用start()方法,而start方法最终还会调用run方法,第一种方式run方法的实现 如上代码。实际上target就是一个runnable,而runnable接口实现线程时传给Thread类的对象。
    第二种方式,也是继承thread方式,实际上继承之后会用上述的run方法重写,重写后的run方法里直接就是需要执行的任务,但最终还是需要调用thread.start()方法来启动线程,而start()方法最终也会调用这个已经被重写的run()方法来执行任务,所以事实上创建线程只有一种方式,就是构建一个Thread类,是创建线程的唯一方式。

    两种创建线程方式本质是一样的,他们的不同点仅仅在于实现线程运行内容的不同。

    运行内容

    要么来自target,要么来自于重写的run方法。在此基础上我们进行拓展,可以这样描述:本质上,实现线程只有一种方式,而要想实现线程执行的内容,却有两种方式,也就是可以通过 实现 Runnable 接口的方式,或是继承 Thread 类重写 run() 方法的方式,把我们想要执行的代码传入,让线程去执行,在此基础上,如果我们还想有更多实现线程的方式,比如线程池和 Timer 定时器,只需要在此基础上进行封装即可。

    实现Runnable接口比继承 Thread 类实现线程要好

    首先,我们从代码的架构考虑,实际上,Runnable 里只有一个 run() 方法,它定义了需要执行的内容,在这种情况下,实现了 Runnable 与 Thread 类的解耦,Thread 类负责线程启动和属性设置等内容,权责分明。
    第二点就是在某些情况下可以提高性能,使用继承 Thread 类方式,每次执行一次任务,都需要新建一个独立的线程,执行完任务后线程走到生命周期的尽头被销毁,如果还想执行这个任务,就必须再新建一个继承了 Thread 类的类,如果此时执行的内容比较少,比如只是在 run() 方法里简单打印一行文字,那么它所带来的开销并不大,相比于整个线程从开始创建到执行完毕被销毁,这一系列的操作比 run() 方法打印文字本身带来的开销要大得多,相当于捡了芝麻丢了西瓜,得不偿失。如果我们使用实现 Runnable 接口的方式,就可以把任务直接传入线程池,使用一些固定的线程来完成任务,不需要每次新建销毁线程,大大降低了性能开销。
    第三点好处在于 Java 语言不支持双继承,如果我们的类一旦继承了 Thread 类,那么它后续就没有办法再继承其他的类,这样一来,如果未来这个类需要继承其他类实现一些功能上的拓展,它就没有办法做到了,相当于限制了代码未来的可拓展性。

    相关文章

      网友评论

          本文标题:1.1为什么说本质只有一种实现线程的方式

          本文链接:https://www.haomeiwen.com/subject/bjmbrktx.html