线程学习(2)-Java线程创建

线程创建方式

线程创建方式,划分为三种,Thread创建,Runnable创建,以及futureTask整合Callable来实现访问,代码如下:

package com.bo.threadstudy.two;


import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.openjdk.jmh.annotations.*;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 创建线程测试类
 * 使用JMH来完成测试功能
 */
@Slf4j
//查看平均耗时
@BenchmarkMode(Mode.AverageTime)
//执行次数,设置为5
@Measurement(iterations = 5)
//预热次数,设置为3
@Warmup(iterations = 3)
public class CreateThreadTest {

    /**
     * thread创建线程的方式
     *
     */
    @Test
    @Benchmark
    public void createThreadTest(){
        Thread thread = new Thread("线程创建"){
            @Override
            public void run() {
                log.debug("执行线程");
            }
        };
        thread.start();
        log.debug("执行主程");
    }

    /**
     * 使用Runable的方式来创建对象,这里采用了lambda表达式来解决
     */
    @Test
    @Benchmark
    public void createRunnableTest(){
        new Thread(() -> log.debug("使用runnable来创建线程"), "runnable创建").start();
        log.debug("主程游走");
    }

    /**
     * 使用futureTask来创建线程,与runAble不同的,可以再当前线程中返回结果来再当前线程中进行调用
     * 这种不常见,但是调用多线程的时候,这种按道理应该用的比较多点,因为涉及到使用返回结果来实现线程之间的通信
     */
    @Test
    public void createFutureTaskTest(){
        FutureTask<String> futureTask = new FutureTask<>(() -> {
            log.debug("测试call方法正在走");
            return "call方法返回结果";
        });
        Thread thread = new Thread(futureTask, "futureTask创建线程");
        thread.start();

        log.debug("主程正在执行");
        String s = null;
        try {
            s = futureTask.get();
            log.debug("主程获取到结果");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        log.debug("futureTask获取到的结果为"+s);
    }

}

简要对比下三种创建的方式。

以Thread创建线程,采用了继承的方式来重写了Thread类之种的run方法,相当于将线程与任务写到了一块。因为java底层类继承仅支持单继承,所以这种方案会导致耦合性较高,对于业务后续扩展并不友好。

以Runnable整合Thread来创建线程,Runnable相当于任务对象,Thread相当于线程对象,这种方式将线程对象与任务这种行为给划分开来,实现了解耦,保证了系统的后期扩展。

以FutureTask结合Callable来整合到Thread中来创建线程,这种方案与Runnable方式类似,只不过我们可以使用FutureTask的get()方法来得到Callable之中的返回结果,从一定程度上保证了线程通信。(类似于线程内部的join方法,等线程执行完成后再执行当前线程,不过这个没有返回结果)。

源码

run方法解析

简单看一下源码,难的我也捋不明白。Thread类下的run方法。

Run方法中依赖于target中的run方法,那么这个target对象是什么,

 

 这个target对象其实就是一个Runable对象,也就是我们第二种创建线程的方式。试想一下,重写

相当于在原有代码上进行改动,而使用Runable则相当于另外定义了接口,在接口中实现该功能。这种方式的实现模式是策略模式。

Runable方法就不介绍了,一个接口,就是为了实现这个run方法来创建的接口。

那么FutureTask结合Callable的实现,底层调用的也是这个run方法,那么FutureTask类与Runnable类是否有一定的联系。

 

 源码种,FutureTask类是实现了Runable类,并整合了泛型来进行操作。而FutureTask中的run方法代码如下:

 底层调用的其实是callable中传入的call方法,根据内部实际实现的call方法来执行操作。

所以Thread类中的run方法根据不同入参传入时执行run方法的操作如上述流程。

FutureTask的get方法

我刚才说了,get方法拥有阻塞当前线程的作用,那么是怎么阻塞的。

 在这里写了个死循环(自旋方式,也就是CAS),只有state这个状态值满足大于COMPLETING时,才可以跳出这个循环。

那么这个状态在哪里改动呢,既然要当前线程执行完后,get方法才能得到返回结果,那么去run方法接着看看。

 刚才在run方法中,我们看到内部有一个set方法。具体源码如下:

 这个方法底层是一个native方法,是在这一步,对state状态进行偏移,然后转化为NORMAL状态。从而让get方法执行结束。

中间涉及的state,aqs这个后续随着深入慢点了解,先不着急。

发现一个问题,在测试类中使用Thread.sleep()方法时。设置睡眠时间一长,会导致sleep后续的程序全部不执行,这个得看看。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值