Java多线程的创建方式

132 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天,点击查看活动详情

前期准备

(1)创建Maven项目

(2)在pom.xml中引入如下依赖

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.22</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.22</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.3</version>
    </dependency>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>RELEASE</version>
        <scope>compile</scope>
    </dependency>
</dependencies>

(3)在resources目录下创建logback.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration
        xmlns="http://ch.qos.logback/xml/ns/logback"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://ch.qos.logback/xml/ns/logback logback.xsd">
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!--%date{HH:mm:ss.SSS} %c -->
            <pattern>%date{HH:mm:ss.SSS} %c [%t] - %m%n</pattern>
        </encoder>
    </appender>

    <!--<logger name="org.springframework.security.web.FilterChainProxy" level="DEBUG" additivity="false">
        <appender-ref ref="STDOUT"/>
    </logger>
    <logger name="org.springframework.security.web.access.intercept.FilterSecurityInterceptor" level="DEBUG" additivity="false">
        <appender-ref ref="STDOUT"/>
    </logger>-->

    <!--<logger name="org.springframework.security.web" level="DEBUG" additivity="false">
        <appender-ref ref="STDOUT"/>
    </logger>-->
    <logger name="c" level="debug" additivity="false">
        <appender-ref ref="STDOUT"/>
    </logger>

    <root level="ERROR">
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>

一、直接使用Thread创建线程

语法

// 创建线程对象
Thread t = new Thread() {
    public void run() {
        // 要执行的任务
    }
};
// 启动线程
t.start();

示例

import lombok.extern.slf4j.Slf4j;

/**
 * Created by lilinchao
 * Date 2022/10/3
 * Description 使用 Thread创建线程
 */
@Slf4j(topic = "c.Test01")
public class Test01 {
    public static void main(String[] args) {
        // 匿名内部类方式创建 Thread
        // 参数"t1":表示该线程名称
        Thread t1 = new Thread("t1") {
            @Override
            // run 方法内实现了要执行的任务
            public void run() {
                log.debug("t1 running");
            }
        };

        //启动线程
        t1.start();
//        t1.run();

        //在main线程中输出日志到控制台
        log.debug("main running");
    }
}

运行结果

17:08:35.519 c.Test01 [main] - main running
17:08:35.519 c.Test01 [t1] - t1 running

Java 8 以后可以使用 lambda 精简代码

Thread t1 = new Thread(()->{ log.debug("t1 running"); }, "t1");
//启动线程
t1.start();

start()和run()方法的区别

在使用Thread 创建线程时,不管使用start()方法还是使用run()方法,程序都可以正常运行起来,但是还是有本质区别的

  • start():作用是启动一个新线程,新线程会执行相应的run()方法。start()不能被重复调用。

    通过调用Thread类的 start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,这里方法 run()称为线程体,它包含了要执行的这个线程的内容,run()方法运行结束,此线程随即终止。

  • run():和普通的成员方法一样,可以被重复调用。单独调用run()的话,会在当前线程中执行run(),而并不会启动新线程。

    如果在主线程中直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径只有一条,程序执行时还是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到多线程的目的。

start()方法会启动一个新线程,并在新线程中运行run()方法。而run()则会直接在当前线程中运行该方法,并不会启动一个新线程来运行。

二、使用 Runnable 配合 Thread创建线程(推荐)

把【线程】和【任务】(要执行的代码)分开

  • Thread 代表线程
  • Runnable 可运行的任务(线程要执行的代码)

语法

Runnable runnable = new Runnable() {
    public void run(){
        // 要执行的任务
    }
};
// 创建线程对象
Thread t = new Thread( runnable );
// 启动线程
t.start(); 

示例

import lombok.extern.slf4j.Slf4j;

/**
 * Created by lilinchao
 * Date 2022/10/3
 * Description 使用 Runnable 配合 Thread
 */
@Slf4j(topic = "c.Test02")
public class Test02 {
    public static void main(String[] args) {
        // 创建任务对象
        Runnable task2 = new Runnable() {
            @Override
            public void run() {
                log.debug("t2 running");
            }
        };
        // 参数1 是任务对象; 参数2 是线程名称
        Thread t2 = new Thread(task2, "t2");
        t2.start();

        log.debug("main running");
    }
}

运行结果

17:54:19.618 c.Test01 [main] - main running
17:54:19.619 c.Test01 [t2] - running

使用 lambda 精简代码

因为 Runnable 接口标注了 @FunctionalInterface 这个注解,表示是一个函数式接口,可以使用 lambda 表达式

// 创建任务对象
Runnable task2 = () -> log.debug("t2 running");
// 参数1 是任务对象; 参数2 是线程名称
Thread t2 = new Thread(task2, "t2");
t2.start();
Thread 与 Runnable 的关系

通过源码层面来说明Thread 与 Runnable 的关系

  • Runnable源码
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
  • Thread源码(部分)
public class Thread implements Runnable {
    /* What will be run. */
    private Runnable target;

    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        //...
        this.target = target;
        //...
    }
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

结论

  • Test01类中是把线程和任务合并在了一起,Test02类中是把线程和任务分开了
  • 用 Runnable 更容易与线程池等高级API 配合
  • 用 Runnable 让任务类脱离了 Thread 继承体系,更灵活

三、FutureTask 配合 Thread创建线程

FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况

import lombok.extern.slf4j.Slf4j;

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

/**
 * Created by lilinchao
 * Date 2022/10/3
 * Description FutureTask 配合 Thread
 */
@Slf4j(topic = "c.Test03")
public class Test03 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建任务对象
        FutureTask<Integer> task3 = new FutureTask<>(() -> {
            log.debug("t3 running");
            //执行完毕后返回结果 100
            return 100;
        });

        // 参数1 是任务对象; 参数2 是线程名称
        new Thread(task3, "t3").start();

        // 主线程阻塞,同步等待 task 执行完毕的结果
        Integer result = task3.get();   //get()方法获取返回结果
        log.debug("main running");
        log.debug("结果是:{}", result);
    }
}

运行结果

18:23:39.883 c.Test03 [t3] - t3 running
18:23:39.886 c.Test03 [main] - main running
18:23:39.886 c.Test03 [main] - 结果是:100
源码分析
  • FutureTask源码(部分)
public class FutureTask<V> implements RunnableFuture<V> {
    /** The underlying callable; nulled out after running */
    private Callable<V> callable;
    
    /** The result to return or exception to throw from get() */
    private Object outcome; // non-volatile, protected by state reads/writes
    
    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }
    
    public void run() {
       //...
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        }
        //...
    }
    
    protected void set(V v) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = v;
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
            finishCompletion();
        }
    }
    
    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);
    }
    
    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);
    }
}    
  • Callable源码
@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;
}

说明

  • FutureTask内置了一个Callable对象,初始化方法将指定的Callable赋给这个对象。
  • FutureTask实现了Runnable接口,并重写了Run方法,在Run方法中调用了Callable中的call方法,并将返回值赋值给outcome变量
  • get方法就是取出outcome的值。