1. 通过继承Thread类来创建并启动多线程的方式
2. 通过实现Runnable接口来创建并启动线程的方式
3. 通过实现Callable接口来创建并启动线程的方式
4. 总结Java中创建线程的方式,比较各自优势和区别
Java使用 Thread类 代表 线程 , 所有的线程对象 都必须是 Thread类 或其 子类 的 实例 。 每个线程的作用 是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。Java中通过继承Thread类来 创建 并 启动 多线程 的步骤如下:
01. 定义Thread类的 子类 ,并 重写 该类的 run()方法 ,该run()方法的方法体就代表了线程需要完成的任务
因此把run()方法称为线程执行体
02. 创建Thread子类的 实例 ,即创建了线程对象
03. 调用线程对象的start()方法来 启动 该线程
下面程序示范了通过继承Thread类来创建并启动多线程:
// 通过继承 Thread 类来创建线程类
public class MyThreadTest extends Thread {
private int i;
// 重写 run 方法, run 方法的方法体就是线程执行体
public void run() {
for (; i < 100; i++) {
// 当线程类继承 Thread 类时,直接使用 this 即可获取当前线程
// Thread 对象的 getName() 返回当前该线程的名字
// 因此可以直接调用 getName() 方法返回当前线程的名
System.out.println(getName() + "" + i);
public static void main(String[] args) {
for ( int i = 0; i < 100; i++) {
// 调用 Thread 的 currentThread 方法获取当前线程
System.out.println(Thread.currentThread().getName() + "" + i);
if (i == 20) {
// 创建、并启动第一条线程
new MyThreadTest().start();
// 创建、并启动第二条线程
new MyThreadTest().start();
运行部分结果:
虽然上面程序只显式地创建并启动了2个线程,但实际上程序有3个线程,即程序显式创建的 2个 子线程 和 1个 主线程 。前面已经提到,当Java程序开始运行后,程序至少会创建一个主线程, 主线程 的线程 执行体 不是由run()方法确定的,而是由 main()方法 确定的,main()方法的方法体代表主线程的线程执行体。
该程序无论被执行多少次输出的记录数是一定的,一共是300条记录。 主线程 会执行for循环打印100条记录, 两个子线程 分别打印100条记录,一共300条记录。因为i变量是MyThreadTest的实例 属性 ,而不是 局部变量 ,但因为程序每次创建线程对象时都需要创建一个MyThreadTest对象,所以Thread-0和Thread-1不能共享该实例属性,所以每个线程都将执行100次循环。
实现Runnable接口来创建并启动多线程的步骤如下:
01. 定义Runnable接口的 实现类 ,并 重写 该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体
02. 创建Runnable实现类的 实例 ,并以此实例作为Thread的 target 来创建Thread对象,该Thread对象才是真正的线程对象
03. 调用线程对象的start()方法来启动线程
需要注意的是: Runnable对象 仅仅作为 Thread对象的 target , Runnable实现类里包含的run()方法仅作为 线程执行体 。而实际的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run()方法。
下面程序示范了通过实现Runnable接口创建线程步骤:
public class MyRunnableTest implements Runnable {
private int i;
void print(){
System.out.println(Thread.currentThread().getName() + "" + i);
// run 方法同样是线程执行体
public void run() {
for (; i < 100; i++) {
// 当线程类实现 Runnable 接口时,
// 如果想获取当前线程,只能用 Thread.currentThread() 方法。
print();
public static void main(String[] args) {
for ( int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "" + i);
if (i == 20) {
MyRunnableTest st = new MyRunnableTest();
// 通过 new Thread(target , name) 方法创建新线程
new Thread(st, " 新线程 -1 ").start();
new Thread(st, " 新线程 -2 ").start();
运行部分结果:
从该运行结果中我们可以看出,控制台上输出的内容是乱序的,而且每次结果不尽相同。这是因为:
01. 在这种方式下,程序所创建的Runnable对象只是线程的target,而多个线程可以共享同一个target。
02. 所以 多个线程 可以 共享同一个线程类 即线程的 target类的实例 属性。
03. 往控制台窗口print()输出的过程并不是多线程安全的,在一个线程输出过程中另一个线程也可以输出。
为能够保证顺序输出,我们可以对打印方法设置Synchronized,让每次只能有一个进程能够访问打印,代码如下:
public class MyRunnableTest implements Runnable {
private int i;
synchronized void print(){
System.out.println(Thread.currentThread().getName() + "" + i);
// run 方法同样是线程执行体
public void run() {
for (; i < 100; i++) {
// 当线程类实 现 Runnable 接口时,
// 如果想获取当前线程,只能用 Thread.currentThread() 方法。
print();
public static void main(String[] args) {
for ( int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "" + i);
if (i == 20) {
MyRunnableTest st = new MyRunnableTest();
// 通过 new Thread(target , name) 方法创建新线程
new Thread(st, " 新线程 -1 ").start();
new Thread(st, " 新线程 -2 ").start();
运行结果:
该运行结果,更加明显的证明了,在这种方式下,多个线程共享了 tartget类实例 的属性
也许受此启发,从Java 5开始,Java提供了 Callable接口 ,该接口怎么看都像是Runnable接口的增强版,Callable接口提供了一个 call() 方法可以作为线程执行体,但call()方法比run()方法功能更强大。
01. call()方法可以 有返回值
02. call()方法可以 声明抛出异常
因此我们完全可以提供一个Callable对象作为 Thread的target ,而该线程的线程执行体就是该Callable对象的call()方法。问题是: Callable接口 是Java 5新增的接口,而且它不是Runnable接口的子接口,所以 Callable对象不能直接作为Thread的target 。而且call()方法还有一 个返回值-----call()方法并不是直接调用,它是作为线程执行体被调用的。那么如何获取call()方法的返回值呢?
Java 5提供了 Future接口 来代表Callable接口里 call()方法的返回值 ,并为Future接口提供了一个 FutureTask实现类 ,该实现类 实现了 Future接口 和 Runnable接口 可以作为Thread类的target。在Future接口里定义了如下几个公共方法来控制它关联的Callable任务:
1. boolcan cancel (boolean maylnterruptltRunning):试图取消该Future里关联的Callable任务
2. V get ():返回Callable任务里call()方法的返回值。调用该方法将导致程序阻塞,必须等到子线程结束后才会得到返回值
3. V get (long timeout,TimeUnit unit):返回Callable任务里call()方法的返回值。
该方法让程序最多阻塞 timeout 和 unit 指定的时间,如果经过指定时间后Callable任务依然没有返回值,
将会抛出TimeoutExccption异常
4. boolean isCancelled ():如果在Callable任务正常完成前被取消,则返回true
5. boolean isDone ():妇果Callable任务已完成,则返回true
注意:Callable接口有泛型限制,Callable接口里的泛型形参类型与call()方法返回值类型相同。
01. 创建Callable接口的 实现类 ,并实现 call()方法 ,该cal()方法将作为线程执行体,且该call()方法有返回值
02. 创建Callable实现类的 实例 ,使用FutureTask类来包装Callable对象
该FutureTask对象封装了该Callable对象的call()方法的返回值
03. 使用FutureTask对象作为Thread对象的 target创建 并启动新线程
04. 调用FutureTask对象的 get() 方法来获得子线程执行结束后的返回值
下面程序示范了通过实现 Callable 接口创建线程步骤:
public class MyCallableTest implements Callable<Integer>{
// 实现 call 方法,作为线程执行体
public Integer call(){
int i = 0;
for ( ; i < 100 ; i++ ){
System.out.println(Thread.currentThread().getName()+ " /t " + i);
// call() 方法可 以有返回值
return i;
public static void main(String[] args) {
// 创建 Callable 对象
MyCallableTest myCallableTest = new MyCallableTest();
// 使用 FutureTask 来包装 Callable 对象
FutureTask<Integer> task = new FutureTask<Integer>(myCallableTest);
for ( int i = 0 ; i < 100 ; i++){
System.out.println(Thread.currentThread().getName()+ " /t " + i);
if (i == 20){
// 实质还是以 Callable 对象来创建、并启动线程
new Thread(task , " callable ").start();
// 获取线程返回值
System.out.println(" callable 返回值: " + task.get());
catch (Exception ex){
ex.printStackTrace();
运行上面程序,将看到主线程和call()方法所代表的线程交替执行的情形,程序最后还会输出call()方法的返回值:
上面程序中创建Callable实现类与创建Runnable实现类并没有太大的差别,只是Callable的call()方法允许 声明抛出异常 , 而且允许 带返回值 。当主线程中当循环变量i等于20时,程序启动以FutureTask对象为target的线程。程序最后调用FutureTask对象 的 get() 方法来返回call()方法的返回值——该方法将导致 主线程 被 阻塞 ,直到call()方法结束并返回为止。
通过以下三种途径可以实现多线程:
01. 继承Thread类
02. 实现Runnable接口
03. 实现Callable接口
不过实现Runnable接口与实现Callable接口的方式基本相同,只是Callable接口里定义的方法有返回值,可以声明抛出异常而已。 因此可以将实现Runnable接口和实现Callable接口归为一种方式。这种方式与继承Thread方式之间的主要差别如下。
线程类只是 实现 了Runnable接口或Callable接口,还可以 继承 其他类。
在这种方式下,多个线程可以共享同一个target对象,所以非常适合 多个相同线程来处理同一份资源的情况 ,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
劣势:编程稍稍复杂,如果需要访问当前线程,则必须使用Thread.currentThread()方法。
劣势:因为线程类已经继承了Thread类,所以不能再继承其他父类
如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】。
如果,您希望更容易地发现我的新博客,不妨点击一下左下角的【关注我】。
如果,您对我的博客所讲述的内容有兴趣,请继续关注我的后续博客,我是【Sunddenly】。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利