并非编程系列之创建线程的方法有多少种?

2021-08-24 10:29:27 浏览数 (1)

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

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

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

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

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

2、实现 Runnable 接口

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

代码语言:javascript复制
public class RunnableExample implements Runnable {
   @Override
    public void run() {
        // 重写run方法
    }
}

3、继承Thread类

代码语言:javascript复制
public class ThreadExample extends Thread {
    @Override

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

}

4、线程池创建线程

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

代码语言:javascript复制
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,也可以使用结合线程池来实现

代码语言:javascript复制
 @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表达式都可以

匿名内部类的方法:

代码语言:javascript复制
  new Thread(() ->{
            System.out.println(Thread.currentThread().getName());
        }).start();

使用jdk8中的lambda表达式

代码语言:javascript复制
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教程和示例

代码语言:javascript复制
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

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

代码语言:javascript复制
 @Override
 public void run() {
     if (target != null) {
         target.run();
     }
 }

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

代码语言:javascript复制
 /* What will be run. */
 private Runnable target;

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

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

代码语言:javascript复制
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去具体实现

代码语言:javascript复制
@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,有了前面的梳理,读者是否能够理解?下面给出一道面试题,如下代码,会打印出什么?

代码语言:javascript复制
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也有重写,但是不会调用的

0 人点赞