多线程编程一-创建线程的三种方式

目录

1 继承Thread类

2 实现Runnable接口

3 线程启动原理

3.1 start 方法

3.2 run方法

4 实现Callable接口

4.1 Callable初步使用

4.2 Callable方法原理

4.2.1 FutureTask run方法实现

4.2.2 get方法

4.2.3 FutureTask不可重复使用


java支持三种线程创建方式:继承Thread类,实现Runnable接口,实现Callable接口

1 继承Thread类

@Test
public void testThread() throws InterruptedException {
    new MyThread().start();
    TimeUnit.SECONDS.sleep(1);
}

class MyThread extends Thread {

    @Override
    public void run() {
        System.out.println("thread start");
    }
}

输出thread start

2 实现Runnable接口

@Test
public void testRunnable() throws InterruptedException {
    new Thread(new MyRunnable()).start();
    TimeUnit.SECONDS.sleep(1);
}

class MyRunnable implements Runnable {

    @Override
    public void run() {
        System.out.println("runnable start");
    }
}

输出runnable start

3 线程启动原理

通过上述代码我们知道线程启动是通过start方法启动的,那么咱们看一下start方法

3.1 start 方法

public synchronized void start() {
    if (threadStatus != 0)
        throw new IllegalThreadStateException();
    group.add(this);

    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
        }
    }
}

start最终会执行start0方法,这是一个本地方法,会把线程设置为就绪状态,等待CPU调度。

3.2 run方法

我们都知道线程启动后最终会调用run方法,我们看一下run方法的实现
private Runnable target;
public void run() {
    if (target != null) {
        target.run();
    }
}

target在通过实现Runnable创建线程的时候注入给Thread类,所以我们可以知道继承Thread类和实现Runnable接口本质上没有太大区别

4 实现Callable接口

4.1 Callable初步使用

@Test
public void testCallable() throws InterruptedException, ExecutionException {
    FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
    new Thread(futureTask).start();
    System.out.println(DateUtil.datetimeToString(new Date()) + ":" + futureTask.get());
}

class MyCallable implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        System.out.println(DateUtil.datetimeToString(new Date()) + ":" + "callable start");
        TimeUnit.SECONDS.sleep(2);
        return 1;
    }
}

输出
2020-06-26 23:05:26:callable start
2020-06-26 23:05:28:1
这里可以看到,两次输出是间隔两秒的,所以get方法是阻塞方法

4.2 Callable方法原理

4.2.1 FutureTask run方法实现

我们知道Thread的构造器是Runnable接口引用的对象,FutureTask实现了又是实现Runnable接口,所以线程启动的时候调的也是FutureTask的run方法

public void run() {
    if (state != NEW ||
        !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                     null, Thread.currentThread()))
        // 如果state状态不是线程创建状态或者当前占用线程设置失败直接返回
        return;
    try {
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                // 获取线程call方法结果
                result = c.call();
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                setException(ex);
            }
            if (ran)
                // 如果call方法已经运行,设置运行后的结果
                set(result);
        }
    } finally {
        runner = null;
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}

接下来咱们看看set设置执行结果的方法

protected void set(V v) {
    // 把当前线程生命周期从新建改为运行完成
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        // 把call执行结果赋值给outcame变量
        outcome = v;
        // 把state设置为NORMAL状态
        UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
        finishCompletion();
    }
}

分析源码我们可以知道FutureTask方法实际上就是运行Callable的call方法,把运行结果赋值给成员变量outcome,并且把state值最终设置为NORMAL

4.2.2 get方法

get方法比较简单,如果call方法已经运行完成,直接返回outcome值;否则等待运行完成

public V get() throws InterruptedException, ExecutionException {
    int s = state;
    if (s <= COMPLETING)
        // 如果还没运行完成则等待运行完成
        s = awaitDone(false, 0L);
    // 返回结果
    return report(s);
}

// 如果是NORMAL状态直接返回,否者抛异常
private V report(int s) throws ExecutionException {
    Object x = outcome;
    if (s == NORMAL)
        return (V)x;
    if (s >= CANCELLED)
        throw new CancellationException();
    throw new ExecutionException((Throwable)x);
}

4.2.3 FutureTask不可重复使用

下面咱们把测试代码稍微改一下,MyCallable类随机返回一个整数,然后用两个线程启动一个FutureTask类查看返回数据
测试代码如下

@Test
public void testCallable() throws InterruptedException, ExecutionException {
    FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
    new Thread(futureTask).start();
    Integer result = futureTask.get();
    System.out.println(DateUtil.datetimeToString(new Date()) + ":" + result);
    new Thread(futureTask).start();
    result = futureTask.get();
    System.out.println(DateUtil.datetimeToString(new Date()) + ":" + result);
}

class MyCallable implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        System.out.println(DateUtil.datetimeToString(new Date()) + ":" + "callable start");
        TimeUnit.SECONDS.sleep(2);
        return new Random().nextInt(1000);
    }
}

输出
2020-06-26 23:24:30:callable start
2020-06-26 23:24:32:711
2020-06-26 23:24:32:711
只输出了一次callable start,说明call方法只运行了一次。get直接输出了711,并且没有阻塞,可以说明第二次get的结果读取的是第一个执行线程运行的结果
原因也很简单,我们再看看run方法代码,其中任意一个线程执行结束后会把state设置为NORMAL,下一个线程运行的时候判断不是NEW状态直接return掉了。
所以我们如果想用两个线程都要执行Callable方法一定不能偷懒,要用两个FutureTask实例来运行线程,正确姿势如下

@Test
public void testCallable() throws InterruptedException, ExecutionException {
    MyCallable myCallable = new MyCallable();
    FutureTask<Integer> task1 = new FutureTask<>(myCallable);
    FutureTask<Integer> task2 = new FutureTask<>(myCallable);
    // 这里同时启动两个线程,提高执行效率
    new Thread(task1).start();
    new Thread(task2).start();
    Integer result = task1.get();
    System.out.println(DateUtil.datetimeToString(new Date()) + ":" + result);
    result = task2.get();
    System.out.println(DateUtil.datetimeToString(new Date()) + ":" + result);
}

输出
2020-06-26 23:33:20:callable start
2020-06-26 23:33:20:callable start
2020-06-26 23:33:22:889
2020-06-26 23:33:22:748

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值