多线程的实现
实现方式总共有三种(JDK1.5后增加了第三种: Callable接口),
分别为继承Thread类,实现Runnable或Callable接口
继承Thread类
- 该类在 java.lang.Thread下,从JDK1.0时就已存在,我们认为任何一个类只要继承了Thread类就可以被成为一个线程类。
- Thread类有一个 public void run()方法,子类必须重写这个方法,方法体里写的是线程需要做的事。注意:这个方法没有返回值,表示只是用来消费,不能获取。
- 可以直接调用Thread.run()方法,但是这样运行跟普通方法一样,并没有实现线程调度(线程交替执行)的效果。Thread类还有一个start()方法(调用此方法执行的方法体为run()方法中的代码块),这个方法才真正表示启动线程,交由CPU调度。
- 疑问?为什么多线程启动不是调用run()而必须调用start() ?
- 这个问题可以通过源码来解析(基于JDK1.8)
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
复制代码
- 通过观察Thread类中的start()源码中的实现,我们会看到前两行有个判断然后抛出个 IllegalThreadStateException 这是个运行时的异常,属于RuntimeException的子类。这个异常是用来判断同一个线程是否多次执行,如果多次执行则抛出这个异常。所以我们可以根据源码来断定一个线程如果重复调用是不合理的,会抛出异常。
- 在代码块的最下方还有一个 start0()方法,Thread的start()方法中调用了这个方法。我们初学者看到这个方法可能会有点奇怪,感觉跟抽象方法有点像,但是这个native是个啥玩意。没见过这种修饰符呀。
- Java开发里有一种称为JNI的技术,全称为 Java Native Interface,这个技术是调用本机的操作系统提供的接口。我们知道线程需要CPU的调度进行资源分配才能运行,所以此操作是JVM根据不同的操作系统做特定的实现,这样就不用改代码了。
总结:
因为直接调用run()方法就跟调用普通方法一样,没有线程的概念。调用start()方法的话不仅仅调用run()方法的执行代码,还会根据不同的操作系统CPU的调度进行资源分配。
复制代码
实现Runnable接口
虽然Thread类可以直接实现多线程了,但是我们知道Java中有一个单继承的问题,所以我们对于类的继承都应该尽量回避。那么对于多线程也一样,为了解决单继承的限制,而有了Runnable接口。
复制代码
- Runnable接口上加了一个注解,表明为函数式接口(只有一个抽象方法,JDK1.8后可以增加默认方法)。
- 我们只需要实现Runnable接口并重写里面的run()方法就可以(跟继承Thread类一样,代码没有变动)。
但是我们怎么启动实现了Runnable接口的线程类呢?前面的Thread类有start()方法启动,但是现在是个接口呀。
- 首先,我们需要记住不管在何种情况下,如果想启动线程一定要依靠Thread类来完成。
我们看下Thread类的构造函数:
- 我们发现Thread类的构造器可以传入一个Runnable接口,那么根据Java的多态性,Runnable的实现类肯定也可以传入。
- 这样我们就可以启动多线程了。
总结:
实现Runnable接口就避免了单继承局限,所以在日常工作中,我们一般都采用实现Runnable接口的方式来实现多线程。
复制代码
两种多线程实现方式的区别?(面试题)
- 首先,使用Runnable接口解决了单继承的局限。至少在这一点上就已经下了死定义 ----------如果要使用多线程,就采用实现Runnable接口的方案。
- 我们先看下Thread类源码中的定义:
范例2:
我们修改了一些代码,自定义线程类实现了Runnable接口,然后创建3个Thread对象共同对自定义线程类操作。
这样的话3个Thread类都指向了自定义线程类的对象。
总结:
- Thread类时Runnable接口的实现类,使用Runnable接口实现多线程可以避免单继承局限
- 实现Runnable接口的线程类可以比继承Thread类的线程类更加清楚的描述数据共享这一概念。
这篇先介绍在这里,从中我们可以详细的了解了多线程的两种实现方式,但是这两种方式中的run()方法都属于消费器,没有返回值。如果我们希望线程执行完能给我们一个返回呢?这就用到了JDK1.5新出的Callable接口。好的,我们下篇再介绍如何使用Callable接口实现多线程并有返回值以及一些线程中常用的方法。大家喜欢我的博客的话请关注点赞分享呀,如有哪些地方说的不对的,也欢迎在评论区一起探讨探讨。See you~~