只有一种实现多线程的方式 ?

线程是实现多线程的基础,本篇主要讲解关于线程的两个问题。
1 为什么说本质上只有一种实现多线程的方式?
2 实现Runnable的方式和继承Thread的方式哪种好?好在哪里?

为什么说本质上只有一种实现多线程的方式?
  • 在回答这个问题之前,我们首先看看常用的实现多线程的方式。
1 实现Runnable接口的方式
class RunnableImpl : Runnable {
    override fun run() {
        println("this is test for runnable")
    }
}

fun main() {
    Thread(RunnableImpl()).start()
}
  • 通过实现Runnable接口的方式,并且实现其中的run方法,并将实现run方法的实例对象作为Thread的构造方法参数,然后调用新创建的Thread的start方法,就可以实现多线程了。
2 继承Thread类的方式
class ThreadExtend : Thread() {
    override fun run() {
        println("this is test for thread")
    }
}

fun main() {
    ThreadExtend().start()
}
  • 通过继承Thread的方式,并且重写其中的run方法,然后用此实例对象调用start方法即可实现多线程。
3 通过Executors线程池模型实现多线程
fun main() {
    Executors.newSingleThreadExecutor().execute {
        println("thread pool")
    }
}
  • 通过Executors静态工厂方法模式,可以创建多种ExecutorServiceThreadPoolExecutor实例对象。其中ThreadPoolExecutor中有一个参数为ThreadFactory,它的默认实现是DefaultThreadFactory,下面我们看一下它的实现:
public interface ThreadFactory {
    Thread newThread(Runnable r);
}

private static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        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;
        }
}
  • 线程池模型中的线程是怎么来的呢?主要就是看newThread方法的实现了,我们可以看到首先通过构造方法传入了方法名字,然后设置是否为守护线程,设置线程的优先级,最终将此线程返回了。所以通过线程池创建线程的方式还是属于通过前面new Thread()的方式实现的。
4 通过Callable实现多线程
class CallableImpl : Callable<String> {
   override fun call(): String {
       Thread.sleep(3000)
       return "callable impl"
   }
}

fun main() {
   println("before future get")
   Executors.newSingleThreadExecutor().submit(CallableImpl()).let {
       println(it.get())
   }
   println("after future get")
}
  • 通过Callable的方式和Runnable的方式的区别是前者是有返回值的,并且调用future.get()方法会阻塞当前线程直到获取到返回值。但是实际上调用了submit(callable)之后,内部还是将Callable封装为了Runnable,这种实现多线程的方式还是离不开最开始说的两种方式。

实现多线程只有一种方式

  • 其实通过上面的例子就会发现,不管实现多线程的方式千变万化,肯定离不开上面最开始的两种方式,下面我们对最开始的两种方式做一个分析,来回答为什么说本质上只有一种实现多线程的方式?
  • 调用Thread的start方法,会执行run方法,我们查看Thread中run方法的源码:
    class Thread {
    	public Thread(Runnable target) {
            init(null, target, "Thread-" + nextThreadNum(), 0);
        }
        
        private Runnable target;
    	  @Override
          public void run() {
              if (target != null) {
                  target.run();
              }
          }
    }
    
  • 如果通过实现Runnable的方式的话,执行run方法的时候,内部的target不为null,执行的就是我们自定义Runnable中实现的run方法的内容。
  • 如果通过继承Thread的方式的话,就会重写这里的run方法,就执行的是自定义的Thread的run方法。
  • 所以实际上实现多线程唯一的方式就是通过构造一个Thread类,这也是本质上实现多线程的唯一方式。
  • 其实上面不管是上面的Runnable也好,Callable也好,重写Thread的run方法也好,他们并没有实现多线程的功能,他们只是提供了多线程需要执行的内容,按照此思路,实现多线程的方式就会越来越多,但是本质上还是上面说的唯一的一种方式。

Runnable和Thread哪种方式好?好在哪里?

下面我们对这两种方式做一下对比,就知道Runnable方式为什么比Thread方式好。

  • 1 从代码架构的角度来,实现Runnable的方式,内部只定义了需要执行的内容,这样就将线程的执行和执行的内容分开了,达到了解耦的目的。
  • 2 从性能方方面考虑,因为线程的创建和销毁是有开销的,如果创建和销毁线程所引起的性能消耗远远大于执行run方法所带来的性能消耗,这样的话频繁的创建线程和销毁线程就得不偿失了,我们可以将任务用Runnable的方式传入线程池中,交给线程池来维护线程。
  • 3 从扩展性来讲,我们直到Java不支持多继承,但是支持多实现,如果我们自定义了Thread类,已经继承了Thread类,想要再继承其它类就不可以了,限制了它在未来的扩展性。
  • 综上所述Runnable方式更加优于Thread方式实现多线程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值