并发学习(二) — 多线程的几种实现方式

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

1.前言

提供了三种多线程的实现方式(前两种都是实现Runnable接口):

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

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

2.实现Runnable接口

2.1.Runnable接口定义

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

总结:

  • 如果希望一个类的实例被当做一个线程执行,那么这个类必须实现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();

当然,这里也可以写Lambda表达式~

说明:

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

    @Override
    public void run() {
        System.out.println("hello");
        System.out.println(Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        new Thread(new Test(),"线程1").start();
    }
}
public class Test{
    //用Lambda表达式来写
    public static void main(String[] args) {
        new Thread(() -> {
            System.out.println("hello");
            System.out.println(Thread.currentThread().getName());
        },"线程1").start();
    }
}

实例场景:

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

实例代码:

@Slf4j
public class MyRunnableImpl implements Runnable{

    //线程名
    private String name;

    private String getName(){
        return name;
    }

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


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

    @Override
    public void run() {
        Integer interval = RandomUtils.nextInt(1000);    //1000内的随机数
        log.info("线程{}正在运行,预计时间{}.....",this.getName(),interval);
        try {
            Thread.sleep(interval);
        }catch (InterruptedException e){
            log.error("exception", e);
        }finally {
            log.info("线程{}运行结束....",this.getName());
        }
    }

    public static void main(String[] args) {
        //创建5个线程
        for(int i = 0; i < 5; i++){
            // new个线程
            Runnable runnable = new MyRunnableImpl("MYrun" + i);
            //启动
            new Thread(runnable).start();
        }
    }
}

结果:

并发学习(二) — 多线程的几种实现方式_第1张图片


3.继承Thread类(但一般不用这个的)

3.1.Thread类定义

说明:

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

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

语法:

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

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


--------或者----
Thread t1 = new Thread(new Runnable());
t1.start();

4.实现Callable接口

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

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

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

一般是配合线程池来使用。

总结:

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

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

实现Callable接口语法:

//定义
public class MyCallableImpl implements Callable {
    @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个任务,获取并打印出每个线程的执行时间。

实例代码:

@Slf4j
public class MyRunnableImpl implements Callable{

    @Override
    public Integer call() throws Exception {
        Integer interval = RandomUtils.nextInt(1000);    //1000内的随机数
        Thread.sleep(interval);
        return interval;
    }

    public static void main(String[] args) throws Exception{
        //Future、Callable一般与Executor结合使用
        //Executor负责创建线程池服务
        //实现Callable接口形成的线程类,负责处理业务逻辑,并将处理结果返回
        //Future接口负责接收Callable接口返回的值
        ExecutorService exec = Executors.newFixedThreadPool(5);
        try {
            //定义一组返回值
            Future[] futures = new Future[5];
            //向线程池提交任务
            for(int i = 0; i < 5; i ++){
                //注意Future的参数化类型要与Callable的参数化类型一致
                futures[i] = exec.submit(new MyRunnableImpl());
            }
            //输出结果
            for(int i = 0; i < 5; i ++){
                log.info("值为{}",futures[i].get());
            }
        }finally {
            exec.shutdown();
        }
    }
}
并发学习(二) — 多线程的几种实现方式_第2张图片
当然,后面会详细介绍这个~

你可能感兴趣的:(Java并发编程)