创建多线程的三种方式

简介: 在java中给我们提供了三种方式来创建多线程。前两种是我们比较常见的,第三种是JDK1.5之后提供给我们的。接下来我们详细的看一下这三种创建线程的写法。 继承Thread类 第一种方式是继承Thread类的写法,代码如下: Thread thread = new Thread(){ @Override public

在java中给我们提供了三种方式来创建多线程。前两种是我们比较常见的,第三种是JDK1.5之后提供给我们的。接下来我们详细的看一下这三种创建线程的写法。

继承Thread类

第一种方式是继承Thread类的写法,代码如下:
        Thread thread = new Thread(){
            @Override
            public void run() {
                for(int i=0;i<10;i++){
                    System.out.println("线程创建的第一种方式:"+Thread.currentThread().getName());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        thread.start();
注意这里我们覆盖的是run方法,而不是start方法,并且也千万不要覆盖start方法。为什么不能覆盖start方法呢?我们看一下Thread源码中start方法是怎么写的:
    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 */
            }
        }
    }
注意这个方法是加锁的方法。在这个方法中最重要的一段代码是:start0();我们接着再来看一下start0()这个方法:
    private native void start0();
它是个native方法。就是这个native的start0()方法,它实现了启动线程,申请栈内存、运行run方法、修改线程状态等职责。线程管理和栈内存管理都是由jvm负责的。如果你覆盖了start方法,也就是撤销了线程管理和栈内存管理的能力,这样还如何启动一个线程呢?不过Thread的这个设计是很精妙的,因为你只需要关注你的业务逻辑就行了,而对于线程和栈内存的管理都有JVM来做就行了。 如果在你的开发中不得不要覆盖start方法的话,请千万要记得调用super.start(),要不然你的线程无法启动。

实现Runnable接口

第二种创建线程的方式是实现Runnable接口。具体代码请看下面:
       Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<10;i++){
                    System.out.println("线程创建的第二种方式:"+Thread.currentThread().getName());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        thread2.start();
这种写法是实现了Runnable接口的一种写法。它的原理是什么呢?我们来看一下Thread源码中run的写法:
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
在run方法中我们可以看到如果target != null就调用target.run()方法。而这个target是从哪儿来的呢?我们继续看Thread的源码,发现在init的方法中有这样一句话:
this.target = target;
接下来我们再看init()这个方法是在哪儿被调用的?通过翻读源码我们可以发现在Thread的每一个构造函数中都会调用init这个方法,并且有这样一个构造函数:
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
看到了吧。我们就是在创建Thread的时候,通过Thread的构造函数传递进去的Runnable的实现类。而线程启动的时候,调用run方法,run方法又接着调用Runnable实现类的run方法!!!!

实现Callable接口

在JDK1.5之后又给我们提供了一种新的创建线程的方式:实现Callable方法。具体代码如下:
        FutureTask<Integer> ft = new FutureTask<Integer>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int i = 0;
                for(;i<10;i++){
                    System.out.println("线程创建的第三种方式:"+Thread.currentThread().getName());
                }
                return i;
            }
        });
        new Thread(ft).start();
在上面的代码中,在创建FutureTask对象的时候,我们把Callable的一个匿名实现类当做参数传到了FutureTask的构造函数中,而在启动线程的时候,我们又把创建的FutureTask的对象当做参数传到了Thread的构造函数中。在这里请注意我们此时不是覆盖的run方法,而是一个叫call的方法。大家可能会感到疑惑这个FutureTask和Callable这两个到底是个什么玩意?下面我们一个一个的分析:

FutureTask

通过翻读FutureTask的源码我们可以看出来实现了RunnableFuture接口,而RunnableFuture接口又继承了Runnable和Future接口。注意:这里是继承了两个接口!你可能会有疑问JAVA中不是没有多继承吗?不错,java中类是没有多继承的,而对于接口是有多继承的!!!到这里我们明白一件事,那就是FutureTask是Runnable接口的一个实现类。到这里你是不是明白了点什么呢?
public class FutureTask<V> implements RunnableFuture<V>
public interface RunnableFuture<V> extends Runnable, Future<V> 
如果你不明白的话,那就多看几遍第二种创建线程的方式和它的原理吧。接下来我们来看一下FutureTask这个类中的run方法是怎么写的:
    public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
上面这个方法中的代码我没有贴全,只贴出来了主要的部分。在这个方法中我们会发现这样的两句话Callable<V> c = callable;result = c.call();这两句话就是关键!!!通过翻读源码我们就会发现源码这个callable就是我们刚才传到FutureTask中的Callable的实现类啊!
    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }
c.call()那不就是调用的Callable实现类的call方法吗?!!!到这里终于真相大白了!FutureTask中其他方法有兴趣的同学可以继续研究一下。

Callable

在Callable这个接口中只有一个call方法。

总结

在实际编码中,我们看到创建线程更多的是使用第二种方式,因为它更符合java中面向接口编程的思想。
最后出个题考一下大家,请说出下面代码的运行结果:
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Runnable实现类的调用:");
            }
        }){
            @Override
            public void run() {
                System.out.println("继承Thread的调用:");
            }
        }.start();







相关文章
|
4月前
|
缓存 Java
线程的四种创建方式
线程的四种创建方式
|
8月前
|
Java
创建多线程的方式四:使用线程池
创建多线程的方式四:使用线程池
38 0
|
8月前
|
调度 双11
多线程的创建方法--多线程基础(一)
线程为一个"执行流". 每个线程之间都可以按照自己的顺序执行.
|
10月前
|
存储 Java C++
多线程的2种实现方式
多线程的2种实现方式
79 0
|
11月前
|
C++
线程的2种创建方式
当一个类继承了Thread类,该类就可以当作线程使用。 run方法其实是一个普通方法,是Runnable接口的一个方法。 Thread类也是进行了重写。 真正实现多线程的start方法中的start0方法。 这是一个本地native方法,由c/c++实现。
61 0
|
Java
Java实现多线程的第三种方式及多线程实现的方式间的比较
再调用get方法,就可以获取线程结束之后的结果。(==get方法一定要放在start之后执行,因为它是获取线程结束之后的结果,如果线程还没有开启或结束,那么get会在那卡着一直等==)
87 0
Java实现多线程的第三种方式及多线程实现的方式间的比较
|
存储
多线程原理和实现方式
多线程原理和实现方式
155 1
|
存储 API C#
C#多线程(15):任务基础③
任务基础一共三篇,本篇是第三篇,之后开始学习异步编程、并发、异步I/O的知识。 本篇会继续讲述 Task 的一些 API 和常用的操作。
138 0
|
并行计算 Java Spring
|
API C#
C#多线程(14):任务基础②
C#多线程(14):任务基础②
171 0
C#多线程(14):任务基础②