线程是实现多线程的基础,本篇主要讲解关于线程的两个问题。
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
静态工厂方法模式,可以创建多种ExecutorService
的ThreadPoolExecutor
实例对象。其中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方式实现多线程。