继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

juc-01-创建线程

黑桃SEVEN_PIG
关注TA
已关注
手记 46
粉丝 11
获赞 8

这一系列文章开始讲解java多线程相关知识,下面这篇文章简单地介绍下新建线程的3种方式。

1 浅谈进程和线程

1.1 进程

进程是正在运行的程序的实例,是系统进行资源分配的基本单位

进程的3个基本状态

图片描述

就绪(Ready)

当进程已分配到除CPU以外的所有必要的资源,只要获得CPU便可立即执行。

运行(Running)

当进程已获得CPU,其程序正在CPU上执行。

阻塞(Blocked)

正在执行的进程,由于等待某个事件发生而无法执行时,便放弃CPU而处于阻塞状态。引起进程阻塞的事件可有多种,例如,等待I/O完成、申请缓冲区不能满足、等待信件(信号)等。

1.2 线程

线程是操作系统能够进行任务调度和执行的最小单位。它被包含在进程之中,是进程中的实际运作单位。
一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

线程5种状态

新建(new Thread)

当创建Thread类( new Thread() )的一个实例时,在调用start方法之前的处于新建状态,它还没有分配到系统资源,这时只能启动或终止它,任何其他操作都会引发异常。

就绪(runnable)

执行 thread.start() 方法后,操作系统分配该线程除CPU之外所必须的资源,等待CPU调度线程即可执行。

运行(running)

线程获得CPU资源(获得CPU调度)执行任务。

阻塞(Blocked)

如果一个线程在执行过程中需要等待某个事件发生,则让出CPU暂停运行,即进入堵塞状态。

死亡(dead)

当run() 方法返回,或别的线程调用stop()方法,线程进入死亡态。

线程状态转换图如下:
图片描述

1.3 进程和线程的关系

图片描述

2 Thread类创建API

方法 描述
Thread() 构造器,线程名称:“Thread-”+线程序号
Thread(String name) 带name的构造器,name: 新线程的名称
Thread(Runnable target) 带Runnable的构造器,
Runnable: 线程被启动后被CPU调度时,会执行Runnable实例的run()方法
Thread(Runnable target, String name) 带Runnable和name的构造器,
Runnable: 线程被启动后被CPU调度时,会执行Runnable实例的run()方法,
name: 新线程的名称
synchronized void start() 启动此线程,JVM将会执行这个线程的 run() 方法

3 创建线程方式一:extends Thread

定义一个类 extends Thread,并重写 run() 方法,run()方法的方法体就子线程的任务逻辑.。

/**
 * Description: 自定义线程类, extends Thread
 *
 * @author Xander
 * datetime: 2020/9/16 19:22
 */
public class XdThread extends Thread {

    public XdThread() {
    }

    /**
     * 指定线程名称
     * @param name 线程名称
     */
    public XdThread(String name) {
        super(name);
    }

    /**
     * run()方法是子线程执行的任务逻辑
     */
    @Override
    public void run() {
        System.out.println("子线程名称:" + Thread.currentThread().getName() + " 开始执行任务...");
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("子线程名称:" + Thread.currentThread().getName() + " 任务结束...");
    }
}

    /**
     * 创建线程方式一:定义一个类 extends Thread,并重写 run() 方法,run()方法的方法体就子线程的任务逻辑
     */
    @Test
    public void testNewThread() throws InterruptedException {
        //新建线程,不指定线程名称,系统会给线程定义一个名称
        XdThread thread = new XdThread();
        //启动子线程
        thread.start();

        //新建线程,指定线程名称
        XdThread threadWithName = new XdThread("Another-Thread");
        //启动子线程
        threadWithName.start();
        TimeUnit.SECONDS.sleep(3);
    }


运行结果:

子线程名称:Thread-0 开始执行任务...
子线程名称:Another-Thread 开始执行任务...
子线程名称:Thread-0 任务结束...
子线程名称:Another-Thread 任务结束...

4 创建线程方式二:Runnable

Runnable源码
Runnable 可以用 @FunctionalInterface 标识,是一个函数式接口,我们可以通过 lambda 表达式创建一个 Runnable 的实例。

@FunctionalInterface
public interface Runnable {
    void run();
}
/**
 * Description: 通过实现 Runnable 接口创建线程
 *
 * @author Xander
 * datetime: 2020/9/16 19:34
 */
public class XdRunnable implements Runnable {

    /**
     * 线程任务执行逻辑
     */
    @Override
    public void run() {
        System.out.println("Runnable方式,子线程名称:" + Thread.currentThread().getName() + " 开始执行任务...");
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Runnable方式,子线程名称:" + Thread.currentThread().getName() + " 任务结束...");
    }
}

    /**
     * 创建线程方式二:实现Runnable接口
     */
    @Test
    public void testRunnable() throws InterruptedException {
        Thread thread = new Thread(new XdRunnable());
        //启动子线程
        thread.start();
        // 通过 Lambda 创建Runnable实例
        new Thread(() -> {
            System.out.println("我是另一个子线程,开始执行任务...");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("我是另一个子线程,执行结束...");
        }).start();
        TimeUnit.SECONDS.sleep(3);
    }

运行结果:

Runnable方式,子线程名称:Thread-0 开始执行任务...
我是另一个子线程,开始执行任务...
我是另一个子线程,执行结束...
Runnable方式,子线程名称:Thread-0 任务结束...

5 创建线程方式三:Callable + FutureTask

上面两种方式创建线程是没有返回值,也不能抛出 checked Exception
如果我们的业务场景需要接收返回值,或者当子线程执行异常时,我们需要catch子线程抛出的异常,这时,我们就可以用到 Callable + FutrueTask 新建线程。

5.1 Runnable、Future、RunnableFuture、FutureTask 和 Callable 之间的关系

从下面的类图中,我们可以看出,RunnableFuture 接口继承了 Runnable 接口和 Future 接口,而 FutureTask 类实现了 RunnableFuture 接口,同时 CallableFutureTask类的一个成员变量。

图片描述

5.2 Runnable、Future、RunnableFuture、FutureTask 和 Callable 的源码

5.2.1 Callable

定义 call() 方法计算结果,或在无法正常计算时抛出异常。
泛型V指定返回结果值的类型。

@FunctionalInterface
public interface Callable<V> {
    /**
     * 计算结果,或在无法正常计算时抛出异常。
     */
    V call() throws Exception;
}

5.2.2 Runnable

当线程被cpu执行时,会执行run()方法。

@FunctionalInterface
public interface Runnable {
    void run();
}

5.2.3 Future

Future定义了几个操作方法,用于操作 Callable 的 call() 方法的执行任务。

public interface Future<V> {

    /**
     * 取消任务执行
     */
    boolean cancel(boolean mayInterruptIfRunning);

    /**
     * 判断任务是否是在完成之前被取消了
     */
    boolean isCancelled();

    /**
     * 如果任务执行结束,返回true
     */
    boolean isDone();

    /**
     * 等待任务完成,获取结果
     */
    V get() throws InterruptedException, ExecutionException;

    /**
     * 在规定时间内完成了任务,那么就会正常获取到返回值;而如在指定时间内没有计算出结果,那么就会抛出 TimeoutException
     */
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

5.2.4 RunnableFuture

继承 Runnable, Future接口。

public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

5.2.5 FutureTask

实现 RunnableFuture,因此FutureTask也是Runnable和Future接口的实现类,也就是实现了 Runnable 和 Future 接口中的抽象方法。
也就是说 FutureTask 可以作为 Runnable ,通过Thread(Runnable target)或者Thread(Runnable target, String name)方式新建线程,当线程被CPU执行时,会调用 FutureTask 的 run()方法。从下面源码中,我们看到run()方法中会执行 Callable 实例的 call() 方法。

public class FutureTask<V> implements RunnableFuture<V> {
...
    //成员变量Callable
    private Callable<V> callable;
...
    // 构造器,入参是一个 Callable 实例
    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }
...
    // FutureTask也是一个Runnable接口的实现类,线程被CPU执行时,会调用run()方法,run()方法中会执行 Callable 实例的 call() 方法。
    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 {
                    // 执行 Callable 实例的 call() 方法,并保存结果
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }
...
}

5.2.6 小结

我们可以用 Future.get() 来获取 Callable 接口返回的执行结果,还可以通过 Future.isCancelled() 判断任务是否被取消了等。
在call() 未执行完毕之前,调用get()的线程(假定此时是主线程)会被阻塞,直到 call() 方法返回了结果后,此时future.get() 才会得到该结果,然后主线程才会切换到 runnable(就绪) 状态,申请cpu调度。
所以 Future 是一个 存储器,它存储了 call() 这个任务的结果,而这个任务的执行时间是无法提前确定的,因为这完全取决于 call() 方法执行的情况。

Future接口API

方法 描述
boolean cancel(boolean mayInterruptIfRunning) 取消任务执行
boolean isCancelled() 判断任务是否是在完成之前被取消了
boolean isDone() 如果任务执行结束,返回true
V get() throws InterruptedException, ExecutionException 等待任务完成,获取结果
- 1. 任务正常完成:get方法会立刻返回结果
- 2. 任务尚未完成(任务还没开始或者进行中):get将阻塞并直到任务完成。
- 3. 任务执行过程中抛出 Exception:get 方法会抛出 ExecutionException:这里的抛出异常,是Call()执行时产生的那个异常,看到这个异常类型是 java.util.concurrent.ExecutionException。不论 call() 执行时抛出的异常类型是什么,最后get方法抛出的异常类型都是 ExecutionException。
- 4. 任务被取消:get方法会抛出 CancellationException
- 5. 任务超时,get方法有一个重载方法,是传入一个延迟时间的,如果时间到了还没有获得结果,get方法就会抛出TimeoutException。
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; 在规定时间内完成了任务,那么就会正常获取到返回值;而如在指定时间内没有计算出结果,那么就会抛出 TimeoutException,超时不获取,任务需取消

5.3 Callable + FutureTask 创建线程,并获取结果

因为 FutureTask是 Runnable 和 Future 接口的实现类。

/**
 * Description: 通过实现 Callable 接口创建线程,允许有返回值
 *
 * @author Xander
 * datetime: 2020/9/16 19:52
 */
public class XdCallable implements Callable<Integer> {

    /**
     * 线程任务执行逻辑
     *
     * @return
     * @throws Exception
     */
    @Override
    public Integer call() throws Exception {
        System.out.println("Callable方式,子线程名称:" + Thread.currentThread().getName() + " 开始执行任务...");
        // 返回一个 0-100(不包含) 随机整数
        Random random = new Random();
        int num = random.nextInt(100);
        TimeUnit.SECONDS.sleep(2);
        System.out.println("Callable方式,子线程名称:" + Thread.currentThread().getName() + " 任务结束...");
        return num;
    }
}


    /**
     * 创建线程方式三:实现 Callable 接口
     */
    @Test
    public void testCallable() throws ExecutionException, InterruptedException {
        // 通过子线程返回一个 0-100(不包含) 随机整数
        FutureTask future = new FutureTask(new XdCallable());
        // FutureTask 可以作为 Runnable ,通过 Thread(Runnable target) 新建线程
        Thread thread = new Thread(future);
        //启动子线程
        thread.start();
        System.out.println("子线程返回:" + future.get());
        System.out.println("主线程结束");
    }

运行结果:

Callable方式,子线程名称:Thread-0 开始执行任务...
Callable方式,子线程名称:Thread-0 任务结束...
子线程返回:40
主线程结束

6 结语

  • 方式一:extends Thread,实现简单,但是由于类单一继承原则,这种方式下线程类已经继承Thread类了,就不能再继承其他类;
  • 方式二:实现 Runnable 接口方式,不能返回一个返回值,也不能抛出 checked Exception。
  • 方式三:实现 callable 接口,允许子线程有返回值,能够抛出 checked Exception。

extends Thread 方式限制了类扩展,所以在工作中用得较少;
如果子线程没有返回值,也不需要在调用线程中捕获子线程抛出的异常,则可以使用 Runnable 方式新建线程;
如果需要子线程有返回值或者能够抛出 checked Exception,则可以使用 Callable + FutureTask 方式新建线程。

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP