Java中创建线程的3种方式及区别

进程:进行中的程序,执行中的程序。在使用电脑时,我们通常会打开多个应用程序,如浏览器,网易云音乐等,而这些打开着的应用程序就称之为进程。

线程:一个进程可以同时执行多个任务,如一个视频播放器可以同时缓存多集电视剧(VIP用户),一个浏览器可以同时下载多张图片等,通常我们将这一个个任务称为线程。

Java中Thread类是线程操作的核心类。而通过下图我们可以发现Thread类其实是Runnable接口的实现类,即Thread类实现了Runnable接口的抽象方法->run()方法。

Runnable接口:


Thread类是Runnable接口的实现类: 

 


 Thread类覆写了Runnable接口的方法: 

那么我们自然而然可以想到第一种创建线程的方法就是直接继承Thread类,然后通过覆写父类,即Thread类的run方法方法来实现自己的功能。

但是直接调用run方法是否可行呢?我们先通过一个简单的例子来看一下:

public class MyThread extends Thread {//直接继承Thread类创建线程
    private String title;

    public MyThread(String title) {
        this.title = title;
    }


    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(this.title + ",i = " + i);
        }
    }

    public static void main(String[] args) {
        MyThread myThread1 = new MyThread("myThread1");
        MyThread myThread2 = new MyThread("myThread2");
        MyThread myThread3 = new MyThread("myThread3");
        myThread1.run();
        myThread2.run();
        myThread3.run();
    }
}

附上上面程序执行的结果:

 

通过观察该运行结果我们发现,此时只是按照这三个线程调用run方法的先后次序顺序打印了结果而已。而我们知道多线程是异步执行,并发执行,所以我们不能直接通过run方法启动线程。那么我们该通过什么方法启动线程呢-> 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 */
            }
        }
    }

通过观察start()方法的源码我们可以看到在threadStatus为0的情况下,start()调用了start0()方法,并将started变量从false置为了true,标志线程已经被启动。那么start0()又是什么方法呢?

private native void start0();

start0()是一个被native 修饰的本地方法,需要依赖平台实现。而Thread类中有一个静态代码块,此代码块中存放了一个方法->registerNatives()方法,该方法主要就是注册一些本地方法供Thread类使用。其中就包括start0()方法,而start0()又会调用JVM_StartThread方法,该方法则会调用run方法。

public
class Thread implements Runnable {
    /* Make sure registerNatives is the first thing <clinit> does. */
    private static native void registerNatives();
    static {
        registerNatives();
    }

所以,我们可以得出的结论是,start()方法中虽然没有run方法,但是它会间接调用run方法:start() -> start0() -> JVM_StartThread -> run()。所以,我们可以通过覆写run方法来实现我们想要的功能,并调用start方法启动线程去执行。 


第一种创建线程的方式:直接继承Thread类

步骤:

  1. 创建一个子类(该子类直接继承Thread类),该子类通过覆写Thread类的run()方法来实现自己的逻辑操作。
  2. 实例化这个子类
  3. 通过这个子类的实例化对象调用start()方法启动线程
public class MyThread extends Thread {
    private String title;

    public MyThread(String title) {
        this.title = title;
    }


    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(this.title + ",i = " + i);
        }
    }

    public static void main(String[] args) {
        MyThread myThread1 = new MyThread("myThread1");
        MyThread myThread2 = new MyThread("myThread2");
        MyThread myThread3 = new MyThread("myThread3");
        myThread1.start();
        myThread2.start();
        myThread3.start();
    }
}

该程序某一次运行结果(交替执行)如下:

 


虽然直接继承Thread类创建线程很简单方便,但是我们知道Java只允许单继承,而不允许多继承,即一个类只能继承一个父类,而不允许同时继承多个父类。所以

1. 如果只为了创建线程而直接继承Thread类的话,该类就不能再继承并接收其他类的功能了。

2. 如果一个类已经继承了别的类,那么就不能再继承Thread类了,即通过第一种方式创建线程便不可行了。

为了解决直接继承Thread类的单继承局限,看来我们得另寻他法了。而通过上面第一张图我们可以知道Runnable接口里有抽象的run()方法,再结合Thread类有一个形式参数为Runnable类型的构造方法(如下图所示),那么我们是不是可以通过将实现了Runnable接口的类的实例化对象作为参数传给Thread类的构造方法呢?答案是可以的。


第二种创建线程的方式:通过实现Runnable接口

步骤:

  1. 创建一个Runnable接口的实现类,通过实现Runnable接口的抽象方法来实现自己的功能。
  2. 实例化这个实现类
  3. 将这个实现类的实例化对象作为target传入如上图所示的Thread类的构造方法中,并调用start()方法启动线程
public class MyThread implements Runnable {
    private String title;

    public MyThread(String title) {
        this.title = title;
    }


    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(this.title + ",i = " + i);
        }
    }

    public static void main(String[] args) {
        Runnable myThread1=new MyThread("myThread1");
        Runnable myThread2=new MyThread("myThread2");
        Runnable myThread3=new MyThread("myThread3");
        new Thread(myThread1).start();
        new Thread(myThread2).start();
        new Thread(myThread3).start();
    }
}

 该程序某一次运行结果(交替执行)如下:


此时通过实现Runnable接口来创建线程虽然解决了单继承局限,但是我们发现通过此方法创建并执行线程run()方法并没有返回任何信息,即没有返回值。但在有些场景下我们是需要知道线程的执行情况的,那么有没有什么方法可以取得线程的执行结果呢?答案是有的。

此时引入另外一个接口:Callable接口

通过上图我们可以知道Callable接口是一个泛型接口,即此时我们并不知道需要返回什么类型的值,只有通过子类实现此接口的时候指明返回值类型。所以目前为止我们可以利用Callable接口的call()方法来取得返回值。但是由于Thread类的构造方法只接收Runnable接口的实现类的实例化对象,所以我们还得找一个既能接收Callable接口的实现类的实例化对象,又实现了Runnable接口的类-->FutureTask类

查看源码我们可以发现FutureTask是RunnableFuture接口的实现类,而RunnableFuture接口又同时实现了Runnable接口和Future接口。

RunnableFuture接口同时实现了Runnable接口和Future接口: 


FutureTask实现了RunnableFuture接口: 

再次查看FutureTask类的源码,我们可以发现如下构造方法:

 

 通过上图我们发现FutureTask类的构造方法可以接收Callable接口实现类的实例化对象,而FutureTask类同时又实现了Runnable接口,所以我们可以通过此种方式来创建线程并取得返回结果。但是到目前为止我们好像并不知道如何取得返回结果,所以接下来就来介绍一下Future接口中的5个抽象方法(如下图所示):

public interface Future<V> {
     boolean cancel(boolean mayInterruptIfRunning);
     boolean isCancelled();
     boolean isDone();
     V get() throws InterruptedException, ExecutionException;
     V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}

1. cancel方法:用来取消任务,如果任务取消成功则返回true,否则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行的任务,如果为true,表示允许取消正在执行的任务,否则表示不允许。由于此参数针对的是是否取消正在执行的任务,所以如果一个任务已经执行完毕了,那么无论参数mayInterruptIfRunning置为true还是false,cancel方法都会返回false,因为任务已经完成了,无法执行取消任务操作。如果任务处于执行状态,那么当参数mayInterruptIfRunning置为true时cancel方法返回true,否则cancel方法返回false。

2. isCancelled方法:表示任务是否被取消成功,如果任务在正常执行期间被取消成功则返回true。

3. isDone方法:表示任务是否已经执行完毕,如果已经执行完毕则返回true。

4. get方法:用来获取任务的执行结果,该方法会一直阻塞,等到任务执行完毕才返回结果。

5. get(long timeout,TimeUnit unit)方法: 用来获取任务的执行结果,但是如果阻塞到timeout时间任务还没有完成就抛出TimeoutException()异常。


介绍完Future接口里的5个抽象方法我们可以知道Future接口针对Callable或Runnable任务的执行可以实现如下功能:

  • 是否取消相应任务
  • 任务是否被取消成功
  • 查询任务是否执行完成
  • 返回任务执行结果

那么到这里对于如何获取Callable任务的执行结果的疑惑就可以解答了-->调用get方法。

总结一下为什么使用FutureTask类的实例化对象作为Thread类构造方法的target:

首先,Callable接口中的抽象方法call方法执行后有返回值,而FutureTask类的构造方法中恰好有可以接收Callable接口实现类的实例化对象的。

其次,为了取得Callable任务的执行结果,我们需要用到Future接口的抽象方法get方法。但是此时并不能结束,因为Thread类的构造方法中只接收Runnable接口的实现类的实例化对象。

最后,由于FutureTask类实现了RunnableFuture接口,而RunnableFuture接口又同时实现了Runnable接口和Future接口,所以通过实例化FutureTask类,并将其实例化对象作为Thread类构造方法的target参数,调用start()方法启动线程后最后通过get方法便可以得到线程的执行结果。


第三种创建线程的方式:实现Callable接口

步骤:

  1. 创建一个Callable接口的实现类(指明返回结果的类型),并覆写Callable接口的call方法
  2. 实例化Callable接口的实现类,并将其作为FutureTask类的构造方法的参数实例化FutureTask类
  3. 将FutureTask类的实例化对象作为Thread类中构造方法的target,实例化Thread类,同时调用start()方法启动线程
  4. 通过调用FutureTask类的实例化对象的get方法获取线程执行的结果
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class MyThread implements Callable<String> {
    private String title;

    public MyThread(String title) {
        this.title = title;
    }


    public String call() {
        for (int i = 0; i < 5; i++) {
            System.out.println(this.title + ",i = " + i);
        }
        return this.title+"执行完毕";
    }

    public static void main(String[] args) {
        Callable<String> myThread1=new MyThread("myThread1");
        Callable<String> myThread2=new MyThread("myThread2");
        Callable<String> myThread3=new MyThread("myThread3");
        FutureTask<String> task1=new FutureTask<>(myThread1);
        FutureTask<String> task2=new FutureTask<>(myThread2);
        FutureTask<String> task3=new FutureTask<>(myThread3);
        new Thread(task1).start();
        new Thread(task2).start();
        new Thread(task3).start();
        try {
            System.out.println(task1.get());
            System.out.println(task2.get());
            System.out.println(task3.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

该程序某一次运行结果(交替执行)如下:

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值