Java并发03:多线程实现三方式:继承Thread类、实现Runnable接口、实现Callable接口

本章主要对Java多线程实现的三种方式进行学习。

1.序言

在JDK5版本之前,提供了两种多线程的实现方式:

  • 继承Thread类,重写run()方法
  • 实现Runnable接口,实现run()方法

这两种种方式的本质都是一个:实现Runnable接口。

在JDK5版本时,提供了一种新的多线程实现方式:

  • Future接口+Callable接口+Executor接口

下面分别对这三种实现方式进行学习。


2.实现Runnable接口

2.1.Runnable接口定义

我们先来看以下Runnable接口的定义:

package java.lang;

/**
 * The <code>Runnable</code> interface should be implemented by any
 * class whose instances are intended to be executed by a thread. The
 * class must define a method of no arguments called <code>run</code>.
 * <p>
 * This interface is designed to provide a common protocol for objects that
 * wish to execute code while they are active. For example,
 * <code>Runnable</code> is implemented by class <code>Thread</code>.
 * Being active simply means that a thread has been started and has not
 * yet been stopped.
 * <p>
 * ...
 */
@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     */
    public abstract void run();
}

总结:

  • 如果希望一个类的实例被当做一个线程执行,那么这个类必须实现Runnable接口
  • Runnable接口被设计成线程设计的一种公共协议。
  • Thread类就是一种Runnable接口的实现
  • 当一个实现了Runnable接口的线程类开始运行,就会自动调用run()方法。
  • 实现Runnable接口,必须**重写run()**方法。
  • 建议在run()方法中存放所有的业务代码,做到线程控制与业务流程的分离。
  • run()方法返回类型为Void,参数为空
  • run()方法不能抛出异常

2.2.通过实现Runnable接口实现线程

实现Runnable实现线程的语法:

//定义
public class MyRunnableImpl implements Runnable {
    @Override
    public void run(){
        //..
    }
}

//使用
new Thread(new MyRunnableImpl()).start();

说明:

  • Runnable接口中只定义了run()方法,其他属性和方法,如name等,需要自己去定义。
  • Runnable实现类本身并不能启动,需要Thread()类的协助。

实例场景:

创建5个线程,每个线程随机执行一段时间后结束。

实例代码:

/**
 * <p>自定义线程02:实现Runnable接口</p>
 * @author hanchao 2018/3/8 23:04
 **/
public class MyRunnableImpl implements Runnable{
    private static final Logger LOGGER = Logger.getLogger(MyRunnableImpl.class);

    /** 线程名(需要手动指定) */
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public MyRunnableImpl(String name) {
        this.name = name;
    }

    /**
     * <p>义务代码在run()方法中,此方法无返回值</p>
     * @author hanchao 2018/3/8 23:07
     **/
    @Override
    public void run() {
        Integer interval = RandomUtils.nextInt(1000,5000);
        LOGGER.info("线程[" + this.getName() + "]正在运行,预计运行" + interval + "...");
        try {
            Thread.sleep(interval);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            LOGGER.info("...线程[" + this.getName() + "]运行结束");
        }
    }

    /**
     * <p>自定义线程实现类测试</p>
     * @author hanchao 2018/3/8 23:07
     **/
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            //通过new创建一个线程
            Runnable runnable = new MyRunnableImpl("MyRunnableImpl-" + i);
            //通过new Thread.start()启动线程
            new Thread(runnable).start();
        }
    }
}

运行结果:

2018-03-12 10:11:19 INFO  MyRunnableImpl:40 - 线程[MyRunnableImpl-0]正在运行,预计运行1442...
2018-03-12 10:11:19 INFO  MyRunnableImpl:40 - 线程[MyRunnableImpl-1]正在运行,预计运行1250...
2018-03-12 10:11:19 INFO  MyRunnableImpl:40 - 线程[MyRunnableImpl-2]正在运行,预计运行3603...
2018-03-12 10:11:19 INFO  MyRunnableImpl:40 - 线程[MyRunnableImpl-3]正在运行,预计运行3290...
2018-03-12 10:11:19 INFO  MyRunnableImpl:40 - 线程[MyRunnableImpl-4]正在运行,预计运行3758...
2018-03-12 10:11:20 INFO  MyRunnableImpl:46 - ...线程[MyRunnableImpl-1]运行结束
2018-03-12 10:11:21 INFO  MyRunnableImpl:46 - ...线程[MyRunnableImpl-0]运行结束
2018-03-12 10:11:22 INFO  MyRunnableImpl:46 - ...线程[MyRunnableImpl-3]运行结束
2018-03-12 10:11:23 INFO  MyRunnableImpl:46 - ...线程[MyRunnableImpl-2]运行结束
2018-03-12 10:11:23 INFO  MyRunnableImpl:46 - ...线程[MyRunnableImpl-4]运行结束

3.继承Thread类

3.1.Thread类定义

先来看Thread类的定义:

/**
 * A <i>thread</i> is a thread of execution in a program. The Java
 * Virtual Machine allows an application to have multiple threads of
 * execution running concurrently.
 * <p>
 * ...
 */
public
class Thread implements Runnable {
     //...
        /**
     * If this thread was constructed using a separate
     * <code>Runnable</code> run object, then that
     * <code>Runnable</code> object's <code>run</code> method is called;
     * otherwise, this method does nothing and returns.
     * <p>
     * ...
     */
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
}

说明:

  • Thread线程类实际上是实现的Runnable接口。

3.2.通过继承Thread类实现线程

语法:

//定义
public class MyThread extends Thread {
    @Override
    public void run(){
        //..
    }
}

//使用
new MyThread().start();

说明:

  • 相较于Runnable类,Thread类提供了一系列方法(后续章节中会学习),都可以在自定义线程中通过super来调用。
  • 虽然Thread类提供了一些方法,简化了线程开发。但是通过继承的方式实现线程,会增加程序的耦合性,不利于维护。

实例场景:

创建5个线程,每个线程随机执行一段时间后结束。

实例代码:

/**
 * <p>自定义线程01:继承自Thread</p>
 * @author hanchao 2018/3/8 22:54
 **/
public class MyThread extends Thread {
    private static final Logger LOGGER = Logger.getLogger(MyThread.class);

    /**
     * <p>重写Thread类的构造器,用以给线程命名</br>
     * 此种方式无需定义name变量以指定线程名,因为父类Thread中已有。</p>
     * @author hanchao 2018/3/8 22:59
     **/
    public MyThread(String name) {
        super(name);
    }

    /**
     * <p>业务代码写在run()方法中,此方法无返回值</p>
     * @author hanchao 2018/3/8 22:55
     **/
    @Override
    public void run(){
        //run()方法无法抛出异常
//    public void run() throws Exception{
        Integer interval = RandomUtils.nextInt(1000,9000);
        LOGGER.info("线程[" + super.getName() + "]正在运行,预计运行" + interval + "...");
        try {
            Thread.sleep(interval);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            LOGGER.info("...线程[" + super.getName() + "]运行结束");
        }
    }

    /**
     * <p>测试自定义线程</p>
     * @author hanchao 2018/3/8 22:57
     **/
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            //通过new创建一个线程
            Thread thread = new MyThread("MyThread-" + i);
            //通过start()启动线程
            thread.start();
        }
    }
}

运行结果:

2018-03-12 10:18:35 INFO  MyThread:31 - 线程[MyThread-3]正在运行,预计运行6561...
2018-03-12 10:18:35 INFO  MyThread:31 - 线程[MyThread-4]正在运行,预计运行7464...
2018-03-12 10:18:35 INFO  MyThread:31 - 线程[MyThread-1]正在运行,预计运行4806...
2018-03-12 10:18:35 INFO  MyThread:31 - 线程[MyThread-2]正在运行,预计运行5214...
2018-03-12 10:18:35 INFO  MyThread:31 - 线程[MyThread-0]正在运行,预计运行8557...
2018-03-12 10:18:40 INFO  MyThread:37 - ...线程[MyThread-1]运行结束
2018-03-12 10:18:40 INFO  MyThread:37 - ...线程[MyThread-2]运行结束
2018-03-12 10:18:42 INFO  MyThread:37 - ...线程[MyThread-3]运行结束
2018-03-12 10:18:43 INFO  MyThread:37 - ...线程[MyThread-4]运行结束
2018-03-12 10:18:44 INFO  MyThread:37 - ...线程[MyThread-0]运行结束

4.实现Callable接口

4.1.JDK5之前线程实现的弊端

先来分析之前两种实现方式的弊端
通过分析Runnable接口的定义,很容易总结出来:

  • **没有返回值:**如果想要获取某个执行结果,需要通过共享变量等方式,需要做更多的处理。
  • **无法抛出异常:**不能声明式的抛出异常,增加了某些情况下的程序开发复杂度。
  • **无法手动取消线程:**只能等待线程执行完毕或达到某种结束条件,无法直接取消线程任务。

为了解决以上的问题,在JDK5版本的java.util.concurretn包中,引入了新的线程实现机制:Callable接口

 

4.2.Callable接口定义

我们先来看一下Callable接口的语法:

package java.util.concurrent;

/**
 * A task that returns a result and may throw an exception.
 * Implementors define a single method with no arguments called
 * {@code call}.
 *
 * <p>The {@code Callable} interface is similar to {@link
 * java.lang.Runnable}, in that both are designed for classes whose
 * instances are potentially executed by another thread.  A
 * {@code Runnable}, however, does not return a result and cannot
 * throw a checked exception.
 * ...
 */
@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

总结:

  • Callable接口更倾向于针对任务这个概念。一个任务其实就是一个线程
  • Callable接口实现的任务必须返回一个结果,当无法正常返回结果时抛出一个异常
  • Callable接口的实现类需要重写一个无参的方法Call()
  • Callable接口Runnable接口类似,都是为了实现多线程而设计的。
  • Runnable接口没有返回值,也无法抛出异常。
  • Callable接口是一个泛型接口。

4.3.通过实现Callable接口实现线程

实现Callable接口的语法:

//定义
public class MyCallableImpl implements Callable<T> {
    @Override
    public T call() throws Exception {
        //...
    }
}

//使用
//一般配置Executor使用,Executor提供线程池服务
ExecutorService executor = new ....
//一般配置Future接口使用,Future用于保存返回结果
//向线程池提交一个任务,并把此任务的执行情况保存在future中
Futrue future = executor.submit(new MyCallableImple());
//获取返回结果
future.get();
//关闭线程池和任务
executor.shutdwon();

说明:

  • Future、Callable一般与Executor结合使用
  • Callable接口用于定义任务类并在**Call()**方法中定义业务代码。
  • Executor接口负责线程池的管理(计划在后续章节进行学习)。
  • Future接口负责保持在Executor线程池中运行的Callable任务的运行状态。
  • Callable接口实现类,通过executor.submit()向线程池提交任务
  • Future接口通过get()方法获取执行结果(一直等待知道结果产生)。
  • 一定要记得通过executor.shutdwon()关闭线程池。推荐在finally中进行这个操作。

实例场景:

定义一个最大线程数为5的线程池,运行5个任务,获取并打印出每个线程的执行时间。

实例代码:

/**
 * <p>自定义线程03:实现Callable接口</p>
 *
 * @author hanchao 2018/3/12 8:56
 **/
//注意,Callable是一个泛型接口
public class MyCallableImpl implements Callable<Integer> {
    private static final Logger LOGGER = Logger.getLogger(MyCallableImpl.class);

    /**
     * <p>实现Callable需要重写call方法,此方法有返回值</p>
     *
     * @author hanchao 2018/3/12 8:59
     **/
    @Override
    public Integer call() throws Exception {
        Integer interval = RandomUtils.nextInt(1000, 5000);
        Thread.sleep(interval);
        return interval;
    }

    /**
     * <p>实现Callable示例</p>
     *
     * @author hanchao 2018/3/12 9:00
     **/
    public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        //Future、Callable一般与Executor结合使用
        //Executor负责创建线程池服务
        //实现Callable接口形成的线程类,负责处理业务逻辑,并将处理结果返回
        //Future接口负责接收Callable接口返回的值
        ExecutorService executor = Executors.newFixedThreadPool(5);
        try {
            //定义一组返回值
            Future<Integer>[] futures = new Future[5];
            //向线程池提交任务
            for (int i = 0; i < 5; i++) {
                //注意Future的参数化类型要与Callable的参数化类型一致
                futures[i] = executor.submit(new MyCallableImpl());
            }
            //输出执行结果
            for (int i = 0; i < 5; i++) {
                LOGGER.info(futures[i].get());
            }
        }finally {//将关闭线程池放在finally中,最大限度保证线程安全
            //记得关闭这个线程池
            executor.shutdown();
        }
    }
}

运行结果:

2018-03-12 10:44:28 INFO  MyCallableImpl:50 - 1564
2018-03-12 10:44:30 INFO  MyCallableImpl:50 - 2992
2018-03-12 10:44:30 INFO  MyCallableImpl:50 - 1629
2018-03-12 10:44:30 INFO  MyCallableImpl:50 - 1454
2018-03-12 10:44:30 INFO  MyCallableImpl:50 - 1941

4.4.更多的Future方法

前面只展示了Future类的get()方法。
为了更详细的体现**[实现Callable接口]**这种方式的优点,对Future接口的其他方法进行简单学习。

Future接口的主要方法如下:

  • isDone():判断任务是否完成。
  • isCancelled():判断任务是否取消。
  • get():获取计算结果(一致等待,直至得到结果)。
  • cancel(true):取消任务。
  • get(long,TimeUnit):规定时间内获取计算结果(在long时间内等待结果,如果得到则返回;如果未得到,则结束,并抛出TimeoutException异常)。

说明:

  • get(long,TimeUnit)的第一个参数是最大超时时间,第二个是时间单位,可以通过enum TimeUnit获取。

下面通过两段代码进行实例学习。

代码段1:学习了**isDone、isCancelled、get()**方法

System.out.println();
//Future接口方法简单展示: isDone/isCancelled/get()
//创建单线程池
ExecutorService executor1 = Executors.newSingleThreadExecutor();
//向线程池提交任务
Future<Integer> future = executor1.submit(new MyCallableImpl());
try {
    //计算执行时间
    Long begin = System.currentTimeMillis();
    LOGGER.info("future开始执行任务...当前时间:" + begin);
    LOGGER.info("通过future.isDone()判断任务是否计算完成:" + future.isDone());
    LOGGER.info("通过future.isCancelled()判断任务是否取消:" + future.isCancelled());
    LOGGER.info("通过future.get()获取任务的计算结果(从任务开始就一直等待,直至有返回值):" + future.get());
    LOGGER.info("future结束执行任务...共计用时:" + (System.currentTimeMillis() - begin) + "ms..\n");
}finally {//将关闭线程池放在finally中,最大限度保证线程安全
    LOGGER.info("通过future.isDone()判断任务是否计算完成:" + future.isDone());
    LOGGER.info("通过future.isCancelled()判断任务是否取消:" + future.isCancelled());
    //记得关闭这个线程池
    executor1.shutdown();
}

代码段1运行结果:

2018-03-12 11:02:28 INFO  MyCallableImpl:66 - future开始执行任务...当前时间:1520823748695
2018-03-12 11:02:28 INFO  MyCallableImpl:67 - 通过future.isDone()判断任务是否计算完成:false
2018-03-12 11:02:28 INFO  MyCallableImpl:68 - 通过future.isCancelled()判断任务是否取消:false
2018-03-12 11:02:31 INFO  MyCallableImpl:69 - 通过future.get()获取任务的计算结果(从任务开始就一直等待,直至有返回值):2830
2018-03-12 11:02:31 INFO  MyCallableImpl:70 - future结束执行任务...共计用时:2843ms..

2018-03-12 11:02:31 INFO  MyCallableImpl:72 - 通过future.isDone()判断任务是否计算完成:true
2018-03-12 11:02:31 INFO  MyCallableImpl:73 - 通过future.isCancelled()判断任务是否取消:false

代码段2:学习了**isDone、isCancelled、cancel()、get(long,TimeUnit)**方法

 //get(long,TimeUnit):最多等待多长时间就不再等待
        //创建单线程池
        ExecutorService executor2 = Executors.newSingleThreadExecutor();
        //向线程池提交任务
        Future<Integer> future2 = executor2.submit(new MyCallableImpl());
        Long begin2 = System.currentTimeMillis();
        try {
            LOGGER.info("future开始执行任务...当前时间:" + begin2);
            LOGGER.info("通过future.get(long,TimeUnit)获取任务的计算结果(5秒钟之后再获取结果):" + future2.get(500,TimeUnit.MILLISECONDS));
            LOGGER.info("future结束执行任务...共计用时:" + (System.currentTimeMillis() - begin2) + "ms..\n");
        }catch (TimeoutException e){
            LOGGER.info("在限定时间内没有等到查询结果,不再等待..");
            //关闭任务
            LOGGER.info("当前任务状态:future2.isDone() = " + future2.isDone());
            LOGGER.info("当前任务状态:future2.isCancelled() = " + future2.isCancelled());
            LOGGER.info("通过future.cancel()取消这个任务:");
            future2.cancel(true);
            LOGGER.info("当前任务状态:future2.isDone() = " + future2.isDone());
            LOGGER.info("当前任务状态:future2.isCancelled() = " + future2.isCancelled());

            //关闭线程池
            executor2.shutdown();

            //意料之中的结果,无需打印日志
            //e.printStackTrace();
        }

代码段2运行结果:

2018-03-12 11:03:15 INFO  MyCallableImpl:85 - future开始执行任务...当前时间:1520823795611
2018-03-12 11:03:16 INFO  MyCallableImpl:89 - 在限定时间内没有等到查询结果,不再等待..
2018-03-12 11:03:16 INFO  MyCallableImpl:91 - 当前任务状态:future2.isDone() = false
2018-03-12 11:03:16 INFO  MyCallableImpl:92 - 当前任务状态:future2.isCancelled() = false
2018-03-12 11:03:16 INFO  MyCallableImpl:93 - 通过future.cancel()取消这个任务:
2018-03-12 11:03:16 INFO  MyCallableImpl:95 - 当前任务状态:future2.isDone() = true
2018-03-12 11:03:16 INFO  MyCallableImpl:96 - 当前任务状态:future2.isCancelled() = true

 

posted @ 2020-10-26 17:12  姚春辉  阅读(400)  评论(0编辑  收藏  举报