Java Thread

Thread

一般而言,线程是CPU资源调度的基本单位。在java中,线程通过系统内核线程实现,每个Java线程对应着一个内核线程。

以HotSpot JVM为例,它的每一个Java线程都是直接映射到一个操作系统原生线程来实现的,中间没有额外的结构,HotSpot不会干涉线程调度。线程调度全由操作系统去处理,包括何时冻结或唤醒线程、该给线程分配多少处理器执行时间、该把线程安排给哪个处理器核心去执行等。

新建/运行

创建一个可执行任务,然后再创建一个线程,创建线程时需要绑定一个可执行任务。调用thread.start()方法后,线程就会执行这个任务。

Runnable runnable = new Runnable() {
    @Override
    public void run() {
        System.out.println("hello");
    }
};

// 创建一个Thread执行runnable任务
Thread thread = new Thread(runnable);
thread.start();

线程状态

在java.lang.Thread类的内部类State中,定义了线程所拥有的六种状态。

public enum State {
    NEW,
    RUNNABLE,
    BLOCKED,
    WAITING,
    TIMED_WAITING,
    TERMINATED;
}

状态含义

  • NEW:新建状态,一个线程创建后,但还未调用start()方法时的状态
  • RUNNABLE:就绪状态,调用start()方法后的状态,可能在执行状态,也可能在等待CPU时间,对应操作系统中的Running和Ready
  • WAITING:无限期等待状态,不会被分配CPU时间,需要等待被其它线程显式唤醒,转换为RUNNABLE。例如,在没有设置timeout调用Object.wait()、Thread.join()、LockSupport.park()
  • TIMED_WAITING:有限期等待状态,不会被分配CPU时间,但一定时间后会被自动唤醒,转换为RUNNABLE。例如,在设置timeout之后调用Object.wait()、Thread.join()、LockSupport.parkUntil()
  • BLOCKED:阻塞状态,不会被分配CPU时间,在资源可获取时被唤醒,转换为RUNNABLE。例如,在竞争锁对象时,若获取失败,则阻塞等待,在可获得时转换为RUNNABLE;在发出IO请求后,阻塞等待,在IO处理完成后,转换为RUNNABLE。
  • TERMINATED:终止状态,线程中的任务执行完成之后处于此状态。

状态转换

找了一张状态流程图

常用方法

动态方法

序号 方法描述
1 public void start() 启动线程,由New状态转换为Runnable状态
2 public final void setName(String name) 改变线程名称,使之与参数 name 相同
3 public final void join() 等待线程执行完成,转换为TERMINATED状态,可指定等待时间
4 public void interrupt() 中断线程执行

静态方法

序号 方法描述
1 public static void yield() 当前线程让出CPU时间,由Running转换为Ready状态
2 public static void sleep(long millisec) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响

需要注意的是,不管是yield还是sleep,都不会释放当前线程持有的锁(如果持有的话)。

任务

线程是用于执行任务的,那么,如何定义一个任务,又如何执行一个任务?

定义任务

java中主要有两种方式,其余均为相应扩展。

Runnbale

Runnable是一个接口,表示一个可运行的任务。要创建一个可执行任务,实现Runnable接口,重写run()方法即可。

@FunctionalInterface
public interface Runnable {

public abstract void run();
}

Callable

Callable是一个接口,表示一个有返回结果的任务,但任务不可直接运行。实现Callable接口,重写call方法,即可创建一个Callable任务。

@FunctionalInterface
public interface Callable<V> {

V call() throws Exception;
}

执行任务

任务的执行有多种方式,但本质上都是通过一个子线程来执行的。

executor

  • 对于Runnable任务,调用executor.execute()方法执行即可。
// 定义一个单线程任务处理器
ThreadPoolExecutor executor = Executors.newSingleThreadExecutor();

// 执行runnable任务
Runnable runnable = new Runnable() {
    @Override
    public void run() {
        System.out.println("hello");
    }
};

executor.execute(runnable);
  • 对于Callable任务,调用executor.submit()方法提交,无法直接执行。executor将其转换为一个FutureTask后再执行任务。
Callable callable = new Callable() {
    @Override
    public Object call() throws Exception {
        return "hello";
    }
};
Future future = executor.submit(callable);
Objects result = future.get();

thread

Runnable runnable = new Runnable() {
    @Override
    public void run() {
        System.out.println("hello");
    }
};

// 创建一个Thread执行runnable任务
Thread thread = new Thread(runnable);
thread.start();

处理返回结果

在提交任务后,如果任务有返回值,应该如何获取返回值呢?例如,向executor中提交一个callable任务后,获取返回值。

Future

这就不得不说到Future了,在executor.submit(Callable callable)执行之后,返回的就是一个Future对象。通过Future对象你可以获取任务的执行状态和执行结果。

Future:表示异步计算结果的接口,提供了一系列方法来管理异步任务的执行状态和结果。

public interface Future<V> {

    boolean isDone();

    V get() throws InterruptedException, ExecutionException;

V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

executor.submit(Callable callable)返回的Future对象其实是一个RunnableFuture,最终实现是一个FutureTask。

RunnableFuture:继承自 Runnable 和 Future 接口的接口,继承了两个的特性,表示一个既可运行,又有返回结果的任务

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

FutureTask: FutureTask类是RunnableFuture接口的实现类,由一个runnable对象或者callable对象创建。

public class FutureTask<V> implements RunnableFuture<V> {

    // 忽略其余方法和变量
    
    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }
}

处理异常

子线程中执行的任务,如果在执行过程中,抛出运行时异常,应该怎么处理?

任务中捕获

在任务代码中捕获Exception,然后做一些日志记录的操作。但这种方式侵入性较大,也不优雅。

Runnable runnable = new Runnable() {
    @Override
    public void run() {
        try{
            System.out.println("hello");
        } catch (Exception ex) {
            // 处理异常,例如记录日志或其他操作
}
    }
};

线程中捕获

为Thread设置ExceptionHandler,针对任务抛出的异常进行捕获,然后做一些基础的日志记录操作。这种方式无侵入性,简单易用,但是不灵活,无法为不同的任务设置不同的异常处理策略。

Thread thread = new Thread(runnable);
thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
    public void uncaughtException(Thread t, Throwable e) {
        // 处理异常,例如记录日志或其他操作
System.out.println("Thread " + t.getName() + " threw an exception: " + e);
    }
});

针对ThreadPoolExecutor,ExceptionHandler在自定义ThreadFactory时设置。

new ThreadFactory() {
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r);
            t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
                public void uncaughtException(Thread t, Throwable e) {
                    // 处理异常,例如记录日志或其他操作
                    System.out.println("Thread " + t.getName() + " threw an exception: " + e);
                }
            });
            return t;
        }
    }

使用Future

前面两种方式都有自己的缺陷,而使用Future的方式,正好可以结合它们的优点又规避缺陷。使用JDK 8中提供的CompletableFuture类,可以以更加灵活的方式对执行过程中抛出的异常进行处理,示例如下

  • 无返回值
Runnable runnable = new Runnable() {
    @Override
    public void run() {
        System.out.println("hello");
    }
};

CompletableFuture.runAsync(runnable, executor).whenComplete((useless, throwable) -> {
    if (Objects.nonNull(throwable)) {
        // 日志记录
        log.error(throwable.getMessage(), throwable);
        // 设置一些特殊逻辑
}
});
  • 有返回值

Supplier是一个函数式接口,可提供返回结果,在CompletableFuture中,Supplier实现会被转换为一个AsyncSupply,其实现了Runnable、Future接口,所以可以执行,也可以获取返回结果。

Supplier<String> supplier = () -> "hello"; 
CompletableFuture.supplyAsync(supplier, executor).whenComplete((result, throwable) -> {
    if (Objects.nonNull(throwable)) {
        // 日志记录
        log.error(throwable.getMessage(), throwable);
        return;
    }
    log.info("execute result : {}", result);
});
posted @   cd_along  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示