IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 为什么说创建线程的方法只有1种? -> 正文阅读

[Java知识库]为什么说创建线程的方法只有1种?

为什么说创建线程的方法只有1种??并发多线程的知识是很重要而且比较杂的知识点,所以需要花不少时间用于整理。创建线程的方式是学习并发编程的一个很基础的问题,所以必须先掌握好

1、创建线程的方法有多少种?

这应该说是一个比较经典的面试题,创建线程的方式到底有多少种?有人可能会说有两种?三种?四种?

说两种的情况,可能就是指实现Runnable接口和extends Thread类。三种情况的可能就是指前面两种再加上线程池的方法。说四种情况的可能就是前面三种再加上,Callable的方式。总之实现线程的方式多种多样,其实从本质源码角度来说,是只有一种方法。也即new Thread这种方式。为什么这么说?且听后文讲解

先复习一下,之前所说的创建线程方式

2、实现 Runnable 接口

这种方法,只要implements Runable接口,重写run方法即可

public class RunnableExample implements Runnable {
   @Override
    public void run() {
        // 重写run方法
    }
}

3、继承Thread类

public class ThreadExample extends Thread {
    @Override

    public void run() {
       // 实现run方法
    }

}

4、线程池创建线程

使用线程池类ThreadPoolExecutor,Executors在阿里编程规范说出有内存泄露问题,这里就不使用

ExecutorService service = new ThreadPoolExecutor(10, 10,
                60L, TimeUnit.SECONDS,
                new ArrayBlockingQueue(10));
service.execute(() ->{
            System.out.println(String.format("thread name:%s",Thread.currentThread().getName()));
        });

5、Callable 创建线程

Callable 创建方式可以使用FutureTask,也可以使用结合线程池来实现

 @Test
 void contextLoads() throws Exception{
     ExecutorService service = new ThreadPoolExecutor(10, 10,
             60L, TimeUnit.SECONDS,
             new ArrayBlockingQueue(10));

     Future<Integer> future = service.submit(new CallableTask());
     Thread.sleep(3000);
     System.out.println("future is done?" + future.isDone());
     if (future.isDone()) {
         System.out.println("callableTask返回参数:"+future.get());
     }

 }

 class CallableTask implements Callable<Integer>{
     @Override
     public Integer call() {
         return ThreadLocalRandom.current().ints(0, (99 + 1)).limit(1).findFirst().getAsInt();
     }
 }

6、创建线程方法多种多样

前面就是主流的创建线程的方法,当然除了上述的写法,其它一些方法都是有的,比如匿名内部类或者lambda表达式都可以

匿名内部类的方法:

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

使用jdk8中的lambda表达式

new Thread(() -> {
            System.out.println("runable run.");
        }) {
            @Override
            public void run() {
                System.out.println(String.format("thread name %s is run.", Thread.currentThread().getName()));
            }
        }.start();

总之,创建线程的语法是多种多样的,但是我们要从本质源码上学习,这些方法只是语法不同而已,不能以后jdk推出其它api,然后就说另外的方法

7、实现线程只有1种方式

这里,需要先翻下源码,挑Runnable接口看看,可以看到Runnable只是一个接口而已,里面只有抽象的run方法,@FunctionalInterface说明这是一个函数式接口,jdk8中的新特性,详细可以参考我之前博客JDK8系列之Functional Interfaces教程和示例

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

{@See java.lang.Thread#run},可以看到Thread其实是implements Runnable接口的,然后Override了run方法

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

然后,这个target对象是什么?翻了源码,其实也就是这个Runnable

 /* What will be run. */
 private Runnable target;

所以这个逻辑就是new Thread的时候有传target(Runnable)的情况,就调用Runable的run方法,不传的情况,就是Thread的实现类自己实现run方法,所以实现通过继承Thread类,或者是通过实现Runable的方法,其本质不就是一样的?

ok,分析了RunableThread的情况,现在翻下线程池的源码,默认是通过DefaultThreadFactory创建的

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来创建的,所以这种方法也是一样的

Callable接口也是一样的,Callable也是函数式接口,创建线程也是通过new Thread去具体实现

@FunctionalInterface
public interface Callable<V> {
 
    V call() throws Exception;
}

所以,综上所述,实现线程的方法本质上是只有一种的,都是通过new Thread的方法进行创建的,其实的实现方法只是语法上的不同

8、Runnable 和Thread 对比

  • Runnable 里只有一个 run() 方法,在这种情况下,实现了 Runnable 与 Thread 类的解耦,Thread 类负责线程启动和属性设置等内容,权责分明。
  • 实现Runnable可以提高性能,使用继承 Thread 类方式是需要创建独立线程的,这个需要花费资源
  • 设计模式中也强调面向接口编程,而且在Java中是不允许使用双继承的,也就是如果继承了Thread类,然后要再继承其它类是做不到的,所以使用Runnable是有这个好处的

ok,有了前面的梳理,读者是否能够理解?下面给出一道面试题,如下代码,会打印出什么?

new Thread(() -> {
   System.out.println("runable run.");
 }) {
     @Override
     public void run() {
         System.out.println(String.format("thread name %s is run.", Thread.currentThread().getName()));
     }
 }.start();

聪明的你是否想到了?下面给出答案,是会打印出thread name ... is run的,为什么?因为这里Override重写了Thread的run方法,也就是说子类重写的方法优先级是会比父类Threadrun方法级别高的,所以就会执行重写的run方法,而我们的Runnable也有重写,但是不会调用的

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-08-25 23:42:19  更:2021-08-25 23:42:41 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 9:09:57-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码