乐趣区

关于多线程:工作三年小胖居然问我创建线程有几种方式

一个问题

哈喽,我是狗哥。话不多说,金三银四,很多同学马上就要加入春招了。而多线程必定是面试必问的,开篇之前,问大家一个问题:创立线程到底有几种形式?

  • 根底答案(答复谬误):两种,继承 Thread 和 实现 Runnable
  • 进阶答案(答复谬误):多种,继承 Thread、实现 Runnable、线程池创立、Callable 创立、Timer 创立等等

置信以上答案很多同学都能答出来。但它们都是谬误的,其实创立线程的形式只有一种。为什么?狗哥你丫逗我么?横看竖看,至多也得两种呀。别急,放下刀。且听我缓缓剖析:

第一种:继承 Thread

首先是继承 Thread,创立线程最经典的办法,这种办法很常见啦。刚入门的时候,狗哥写过不晓得多少遍了。它的写法是这样的:

public class MyThread extends Thread {

    @Override
    public void run() {System.out.println("通过集成 Thread 类实现线程");
    }

}

// 如何应用
new MyThread().start()

如代码所示:继承 Thread 类,并重写了其中的 run () 办法,之后间接调用 start() 即可实现多线程。置信下面这种形式你肯定十分相熟,并且常常在工作中应用它们。

第二种:实现 Runnable

也是最罕用的办法,写法如下:

public class MyRunnable implements Runnable {

    @Override
    public void run() {System.out.println("通过实现 Runnable 形式实现线程");
    }

}

// 应用
// 1、创立 MyRunnable 实例
MyRunnable runnable = new MyRunnable();
//2. 创立 Thread 对象
//3. 将 MyRunnable 放入 Thread 实例中
Thread thread = new Thread(runnable);
//4. 通过线程对象操作线程(运行、进行)
thread.start();

如代码所示,这种办法其实是 定义一个线程执行的工作(run 办法外面的逻辑)并没有创立线程 。它首先通过 MyRunnable 类实现 Runnable 接口,而后重写 run () 办法,之后还要 把这个实现了 run () 办法的实例传到 Thread 类中才能够实现多线程

第三种:线程池创立线程

说完这两种在工作中最罕用的,咱们再说说第三种。在 Java 中,咱们 创立线程池 是这样的:

// 10 是外围线程数量
ExecutorService service = Executors.newFixedThreadPool(10);

点进去 newFixedThreadPool 源码,在 IDEA 中调试,能够发现它的调用链是这样的:

Executors.newFixedThreadPool(10) --> new ThreadPoolExecutor(一堆参数) --> Executors.defaultThreadFactory() 

能够发现最终还是调用了 Executors.defaultThreadFactory() 办法,而这个办法的源码是这样的:

static class DefaultThreadFactory implements ThreadFactory {
    // 线程池序号
    static final AtomicInteger poolNumber = new AtomicInteger(1);
    // 线程序号
    final AtomicInteger threadNumber = new AtomicInteger(1);
    // 线程组
    final ThreadGroup group;
    // 线程池前缀
    final String namePrefix;

    DefaultThreadFactory() {SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() :
                Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" +
                poolNumber.getAndIncrement() +
                "-thread-";
    }

    /**
     * 重点办法
     * @param r
     * @return
     */
    @Override
    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 创立线程,用法是这样的:

public class MyCallable implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {return new Random().nextInt();}

    // 应用办法
    // 1、创立线程池
    ExecutorService service = Executors.newFixedThreadPool(10);
    // 2、提交工作,并用 Future 提交返回后果
    Future< Integer > future = service.submit(new MyCallable());

}

Callable 与 Runnable 名字还有点像,区别在于 Runnable 是无返回值的。它们的实质都是定义线程要做的工作(call 或 run 办法外面的逻辑),而不是说他们自身就是线程。但无论有无返回值,它们都是须要被线程执行。

如代码所示,它们能够提交到线程池执行,通过 sumbit 办法提交。这时就参考形式三,由线程工厂负责创立线程 。当然,还有其余办法执行 Callable 工作。然而不管怎么说,它还是 离不开实现 Runnable 接口和继承 Thread 类这两种形式

第五种:Timer 创立

咱们应用 Timer 的形式如下:

public class MyTimer {public static void main(String[] args) {timer();
    }

    /**
     * 指定工夫 time 执行 schedule(TimerTask task, Date time)
     */
    public static void timer() {Timer timer = new Timer();
        // 设定指定的工夫 time, 此处为 2000 毫秒
        timer.schedule(new TimerTask() {public void run() {System.out.println("执行定时工作");
            }
        }, 2000);
    }

}

如代码所示,Timer 定时器在两秒之后执行一些工作,它也的确创立了线程,然而深刻源码:

private final TimerThread thread = new TimerThread(queue);

public Timer() {this("Timer-" + serialNumber());
}

public Timer(String name) {thread.setName(name);
    thread.start();}

class TimerThread extends Thread {// 省略外部办法}

留神到 TimerThread,它还是继承于 Thread,所以 Timer 创立线程最初又绕回到最开始说的两种形式了。

为什么只有一种形式?

有同学可能说,狗哥你这扯半天不还是两种形式么?我答对了呀。。。别急,容我喝口水,上面剖析为何说它是一种?

留神到 Thread 类中有一个 run 办法:

private Runnable target;

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

先看实现 Runnable 形式,它启动线程还是须要调用 start 办法(因为是 Native 办法咱们看不到具体逻辑),然而线程要执行工作必须还是要调用 run 办法(不然线程执行的是啥?)。

咱们看代码,run 办法非常简单。它判断 target 不为 null 就间接执行 target 的 run 办法。而 target 正是咱们实现的 Runnable,应用 Runnable 接口实现线程时传给 Thread 类的对象

在看继承 Thread 形式,它调用 thread.start(),最终调用的还是 run 办法(run() 外面是工作)。只不过这个 run() 是咱们曾经重写的 run() 而不是下面 Runnable(target) 的 run()。

看到这里可算明确了,事实上创立线程实质只有一种形式,就是结构一个 Thread 类,这是创立线程的惟一形式,不同的只是 run 办法(执行内容)的实现形式

工作的起源

1、2 两种形式它们的不同点仅仅 在于实现线程执行内容的不同,那么运行内容来自于哪里呢?

实质上,实现线程只有一种形式,而要想实现线程执行的内容,却有两种写法:

  • 实现 Runnable 接口从而实现 run() 的形式
  • 继承 Thread 类重写 run () 办法的形式

而后把咱们想要执行的代码传入,让线程去执行。在此基础上,如果咱们还想有更多实现线程的形式,比方线程池、Callable 以及 Timer 定时器,只须要在此基础上进行封装即可

哪种写法好?

答案是:Runnable 写法

理由:

  • 易于扩大

这个不多说,Java 是单继承。如果应用继承 Thread 的写法。将不利于后续扩大。

  • 解耦

用 Runnable 负责定义 run() 办法(执行内容)。这种状况下,它与 Thread 实现理解耦。Thread 负责线程的启动以及相干属性设置。

  • 性能

在一些状况下能够进步性能。比方:线程执行的内容很简略,就是打印个日志。如果应用 Thread 实现,那它会从线程创立到销毁都要走一遍,须要屡次执行时,还须要屡次走这反复的流程,内存开销十分大。

然而咱们应用 Runnable 就不一样了。能够把它扔到线程池外面,用固定的线程执行。这样,显然是能够提高效率的。

伟人的肩膀

  • Java 并发编程 78 讲 — 徐隆曦

福利

如果看到这里,喜爱这篇文章的话,请帮点个难看。微信搜寻 一个优良的废人 ,关注后回复 电子书 送你 100+ 本编程电子书,不只 Java 哦,详情看下图。回复 1024 送你一套残缺的 java 视频教程。

退出移动版