为什么说创建线程的方法只有1种??并发多线程的知识是很重要而且比较杂的知识点,所以需要花不少时间用于整理。创建线程的方式是学习并发编程的一个很基础的问题,所以必须先掌握好
1、创建线程的方法有多少种?
这应该说是一个比较经典的面试题,创建线程的方式到底有多少种?有人可能会说有两种?三种?四种?
说两种的情况,可能就是指实现Runnable接口和extends Thread类。三种情况的可能就是指前面两种再加上线程池的方法。说四种情况的可能就是前面三种再加上,Callable的方式。总之实现线程的方式多种多样,其实从本质源码角度来说,是只有一种方法。也即new Thread 这种方式。为什么这么说?且听后文讲解
先复习一下,之前所说的创建线程方式
2、实现 Runnable 接口
这种方法,只要implements Runable接口,重写run方法即可
public class RunnableExample implements Runnable {
@Override
public void run() {
}
}
3、继承Thread类
public class ThreadExample extends Thread {
@Override
public void 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
private Runnable target;
所以这个逻辑就是new Thread 的时候有传target(Runnable) 的情况,就调用Runable的run方法,不传的情况,就是Thread 的实现类自己实现run 方法,所以实现通过继承Thread 类,或者是通过实现Runable 的方法,其本质不就是一样的?
ok,分析了Runable 和Thread 的情况,现在翻下线程池的源码,默认是通过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方法,也就是说子类重写的方法优先级是会比父类Thread 的run 方法级别高的,所以就会执行重写的run方法,而我们的Runnable也有重写,但是不会调用的
|