Skip to content

多线程

说明

我认为多线程在逐步学习Java的过程中只是在最初的SE部分简单涉猎, 其实它在真实业务场景中是非常非常重要的部分, 然而我们往往在项目的时候才用到多线程, 突然就上手非常困难, 因此我建议学完SE就可以在学框架基础的同时多去学学这个线程相关的东西

v1.1 2024/9/26 开始二刷多线程,此笔记中记录了多线程复习的学习,在这里要多打代码

对于Runnable\Callable\Future三个接口的理解

Callable 接口和 Future 接口在 Java 并发编程中扮演着不同的角色,尽管它们经常一起使用,但它们各自有着明确的职责和用途。

Callable 接口

Callable 接口类似于 Runnable 接口,但主要区别在于 Callable 可以返回一个结果,并且可以声明抛出一个异常。Callable 通常用于那些需要执行并产生结果的任务。当你将任务提交给 ExecutorService 时,如果任务需要返回结果,你会使用实现了 Callable 接口的类。

Future 接口

Future 接口表示一个可能尚未完成的异步计算的结果。它提供了一种方法来检查计算是否完成,等待计算完成,并检索计算结果。当你提交一个 Callable 任务给 ExecutorService 时,它会返回一个实现了 Future 接口的对象,这个对象代表了异步计算的结果。

为什么需要 Future 接口

尽管 Callable 接口提供了执行并返回结果的能力,但直接操作 Callable 本身并不能让你查询任务的执行状态或等待任务完成。这就是 Future 接口的用途所在

  1. 查询任务状态Future 接口允许你检查任务是否已经完成、是否被取消,以及是否因为异常而结束。
  2. 等待任务完成Future 接口提供了 get() 方法,允许你等待异步操作完成,并获取其结果。这个方法会阻塞调用它的线程,直到任务完成。
  3. 取消任务:某些情况下,你可能需要取消一个正在执行的任务。虽然这通常取决于任务的具体实现,但 Future 接口允许你尝试取消任务。

总结

Callable 接口定义了可以被异步执行的任务,这些任务可以返回结果并抛出异常。而 Future 接口提供了检查任务执行状态、等待任务完成以及获取任务结果的方法。它们一起工作,使得 Java 并发编程中处理异步计算和结果变得更加灵活和强大。通过将 Callable 任务提交给 ExecutorService 并获取返回的 Future 对象,你可以轻松地管理异步任务的执行和结果。

Callable 本身并不直接表示异步执行,而是定义了可以产生结果的任务。Future 接口也不直接执行异步操作,而是代表了异步操作的结果。真正让任务异步执行的是执行这些任务的线程池(如 ExecutorService)。

  • Callable:是一个函数式接口,它定义了一个可以产生结果并可能抛出异常的 call() 方法。Callable 本身并不涉及任何异步行为;它只是定义了可以被异步执行的任务的类型。
  • Future:是一个表示异步操作结果的接口。当你将一个 Callable 任务提交给 ExecutorService 或其他执行器时,执行器会异步地执行该任务,并返回一个 Future 对象,该对象代表了这个异步操作的结果。你可以通过 Future 对象来检查任务是否完成、等待任务完成并获取结果,或者尝试取消任务。
  • 异步执行:是由执行器(如 ExecutorService)提供的。当你将任务(实现了 RunnableCallable 接口的对象)提交给执行器时,执行器会安排任务在单独的线程中执行,从而允许主线程继续执行其他任务,实现异步行为。

因此,CallableFuture 都不是直接实现异步行为的组件,而是与异步执行相关的不同部分。Callable 定义了可以被异步执行的任务的类型,而 Future 提供了管理异步操作结果的方法。

当你将一个 Callable 任务提交给 ExecutorService 时,会发生以下事情:

  1. ExecutorService 接收 Callable 任务。
  2. 它从线程池中获取一个线程(如果线程池中有可用的线程)。
  3. 它在新线程中异步执行 Callable 任务的 call() 方法。
  4. 它返回一个 Future 对象,该对象与正在执行的异步任务相关联。

现在,你可以通过返回的 Future 对象来查询任务的执行状态、等待任务完成并获取结果,或者尝试取消任务,而不会阻塞主线程(除非你显式调用 Future.get() 并等待结果,这将会阻塞调用它的线程)。

通过这些接口的设计, 我们可以更深的理解“接口”在定义能力方面的真实含义, 定义了什么能力, 你就负责什么部分

Callable和Runnable是Java中用于实现多线程编程的两个关键接口,它们之间存在一些显著的区别,但也有一些联系。

Callable和Runnable的区别

  1. 返回值:
    • Runnable 接口的 run() 方法没有返回值(返回类型为 void)。
    • Callable 接口的 call() 方法可以返回一个结果,这个结果可以是任何类型,因为 Callable 是一个泛型接口。
  2. 异常处理:
    • Runnablerun() 方法不能抛出受检查的异常(checked exceptions),所有异常都必须在方法内部处理或声明为运行时异常(unchecked exceptions)。
    • Callablecall() 方法可以抛出异常,包括受检查的异常,这些异常可以被调用者捕获和处理。
  3. 使用方式:
    • Runnable 可以直接作为 Thread 类的构造器参数,创建并启动新线程。
    • Callable 不能直接作为 Thread 类的构造器参数,但可以通过 FutureTask 包装后再传递给 Thread 执行,或者通过线程池(如 ExecutorService)的 submit() 方法提交执行,submit() 方法会返回一个 Future 对象,用于表示异步计算的结果。

Runnable和Callable的联系

尽管Runnable和Callable在接口定义和使用方式上存在差异,但它们都是用于实现多线程编程的接口,且在某些情况下可以相互转换或结合使用。

关于“为什么传入FutureTask可以传入Runnable并获取返回结果”的问题,实际上FutureTask的设计允许它包装一个Runnable或Callable对象。当FutureTask包装一个Runnable对象时,由于Runnable的run()方法没有返回值,FutureTask会将该Runnable的执行视为成功完成,但返回的结果是null(或者可以通过FutureTask的构造函数指定一个默认值作为结果)。这并不是说Runnable可以返回结果,而是FutureTask提供了一种机制,使得即使在没有返回值的情况下也能通过Future接口统一处理异步任务的结果。

然而,在大多数情况下,当你需要异步执行的任务需要返回结果时,应该使用Callable接口,并通过FutureTask或线程池的submit()方法提交执行。这样,你就可以通过返回的Future对象获取任务的执行结果了。

综上所述,Callable和Runnable在Java多线程编程中扮演着不同的角色,但它们之间也存在一定的联系和转换方式。

CompletableFuture中关于get和join的区别

一、相同点

1、get和join都是用来等待获取CompletableFuture执行异步的返回

二、不同点

1、join()方法抛出的是uncheckException异常(即RuntimeException),不会强制开发者抛出

2、get()方法抛出的是经过检查的异常,ExecutionException, InterruptedException 需要用户手动处理(抛出或者 try catch)

ThreadLocal 和 InheritableThreadLocal对比

说明:防止 博客园 没办法继续下去,把这篇博客存下来

原文链接https://www.cnblogs.com/shanheyongmu/p/17922183.html

一、ThreadLocal介绍

在多线程环境下访问同一个线程的时候会出现并发问题,特别是多个线程同时对一个变量进行写入操作时,为了保证线程的安全,通常会进行加锁来保证线程的安全,但是加锁又会造成效率的降低;ThreadLocal是jdk提供的除了加锁之外保证线程安全的方法,其实现原理是在Thread类中定义了两个ThreadLocalMap类型变量 threadLocalsinheritableThreadLocals 用来存储当前操作的ThreadLocal的引用及变量对象,这样就可以把当前线程的变量和其他的线程的变量之间进行隔离,从而实现了线程的安全性。

在使用ThreadLocal时,需要 把主线程中的ThreadLocal值传输到子线程(线程池维护)中,故使用了 InheritableThreadLocal 作为传输。后发现,主线程执行ThreadLocal.remove()后,子线程中的ThreadLocal并不会被remove(),导致线程池中维护的ThreadLocal存储的值一直不变。于是深入进行了研究。

二、ThreadLocal的简单示例

首先定义一个ThreadLocal全局变量,在main方法中开启两个线程,在线程启用之前先设置变量然后在线程内部和外部获取变量,发现输出结果有什么不同吗?疑问我们后面解答。

java
public class Test1 {
    public static final ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException, InterruptedException {
        threadLocal.set("主线程1。。。");
        System.out.println("线程1:" + threadLocal.get());
        new Thread(() -> {
            System.out.println("子线程1:" + Thread.currentThread().getName() + ":" + threadLocal.get());
        }).start();
        Thread.sleep(1000);

        threadLocal.set("主线程2。。。");
        System.out.println("线程2:" + threadLocal.get());
        new Thread(() -> {
            System.out.println("子线程2:" + Thread.currentThread().getName() + ":" + threadLocal.get());
        }).start();
        //删除本地内存中的变量
        threadLocal.remove();
    }
}

输出结果如下:

线程1:主线程1。。。
子线程1:Thread-0:null
线程2:主线程2。。。
子线程2:Thread-1:null

三、ThreadLocal原理及源码分析

Thread类有两个属性 threadLocalsinheritableThreadLocals,这两个属性都 是ThreadLocal类的内部类ThreadLocalMap类型变量,ThreadLocalMap是一个类似于HashMap类型的容器类,默认情况下两个变量都为null,只有当第一次调用ThreadLocal的get或set方法的时候才会创建它们。

java
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

每个线程的变量 不是存放在ThreadLocal类中,而是存放在当前线程的变量之中,也就是说存放在当前线程的上下文空间中,其线程本身相当于变量的一个承载工具,通过set方法将变量添加到线程的threadLocals变量中,通过get方法能够从它的threadLocals变量中获取变量。如果线程一直不终止,那么这个本地变量将会一直存放在它的threadLocals变量中,所以可以通过remove方法删除本地变量(尤其在使用线程池复用线程时,remove是必须的)。

java
public void set(T value) {
    //获取当前线程
    Thread t = Thread.currentThread();
    //以当前线程为参数,查找线程的变量threadLocals(1)
    ThreadLocalMap map = getMap(t);
    //如果线程变量map不为null,直接添加本地变量,
    //key为当前定义的ThreadLocal变量的this引用,值为添加到本地的变量值
    if (map != null) {
        map.set(this, value);
    } else {
        //如果为null,这创建一个ThreadLocalMap对象赋值给当前线程t(2)
        createMap(t, value);
    }
}

上述代码(1)处获取当前线程的threadLocals变量,方法如下:就是获取Thread类的属性值

java
//获取线程的threadLocals属性,并将本地变量和ThreadLocal对象引用绑定到属性上
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

上述代码(2)处 创建ThreadLocalMap对象 并初始化当前线程的threadLocals变量,方法如下:

java
void createMap(Thread t, T firstValue) {
    //this是当前ThreadLocal对象的引用,firstValue是变量值
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

再看看get方法

java
public T get() {
    //获取当前线程|调用者的线程(1)
    Thread t = Thread.currentThread();
    //获取当前线程的threadLocals变量(2)
    ThreadLocalMap map = getMap(t);
    //如果ThreadLocalMap变量不为null,就可以在map中查找到本地变量的值(3)
    if (map != null) {
        //通过当前ThreadLocal对象的this引用获取变量中
        ThreadLocalMap.Entry e = map.getEntry(this);
        // 如果变量值不为空,则转换为指定的类型返回
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    //threadLocals变量为null,则对当前线程的threadLocals的变量进行初始化
    return setInitialValue();
}

private T setInitialValue() {
    //调用抽象初始化方法,默认:null
    T value = initialValue();
    //获取当前线程
    Thread t = Thread.currentThread();
    //查找当前线程的threadLocals变量
    ThreadLocalMap map = getMap(t);
    //如果map不为null,则直接添加本地变量,key为当前ThreadLocal的this引用,value为本地变量
    if (map != null) {
        map.set(this, value);
    } else {
        //首次添加,创建对应的threadLocals变量
        createMap(t, value);
    }
    if (this instanceof TerminatingThreadLocal) {
        TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
    }
    return value;
}

remove方法

java
public void remove() {
    //获取当前线程的threadLocals变量,如果变量非null,则删除当前ThreadLocal引用对应的变量值
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null) {
        //删除ThreadLocalMap中存储的数据(1)
        m.remove(this);
    }
}

上述(1)中的remove方法是ThreadLocal.ThreadLocalMap类的方法:

java
private void remove(ThreadLocal<?> key) {
    //当前ThreadLocal变量所在的table数组位置
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len - 1);
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        if (e.get() == key) {
            //调用WeakReference的clear方法清除对ThreadLocal的弱引用
            e.clear(); 
            //清理key为null的元素
            expungeStaleEntry(i); 
            return;
        }
    }
}

四、ThreadLocal不支持继承性

在同一个ThreadLocal变量在父线程中被设置值之后,在子线程中是获取不到的(由二中的实例输出可以看出),threadLocals中为当前调用线程的本地变量,所以子线程是无法获取父线程的变量的

一开始我们介绍的时候说Thread类中还有一个 inheritableThreadLocals 变量,其值是 存储的子线程的变量,所以可以通过InheritableThreadLocal类获取父线程的变量;

五、InheritableThreadLocal类源码简介

InheritableThreadLocal类是ThreadLocal类的子类,重写了chidValue、getMap、createMap三个方法

其中 createMap 方法在被调用的时候创建的是 inheritableThreadLocals 变量值(ThreadLocal类中创建的是threadLocals变量的值),getMap方法在被get或set方法调用的时候返回的也是线程的inheritableThreadLocals变量。

java
public class InheritableThreadLocal<T> extends ThreadLocal<T> {

    protected T childValue(T parentValue) {
        return parentValue;
    }

    ThreadLocalMap getMap(Thread t) {
        return t.inheritableThreadLocals;
    }
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

那chidValue方法又是在哪里被调用呢?它会在ThreadLocalMap的中被调用:

java
private ThreadLocalMap(ThreadLocalMap parentMap) {
    Entry[] parentTable = parentMap.table;
    int len = parentTable.length;
    setThreshold(len);
    table = new Entry[len];

    for (Entry e : parentTable) {
        if (e != null) {
            @SuppressWarnings("unchecked")
            ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
            if (key != null) {
                //调用重写的childValue方法
                Object value = key.childValue(e.value);
                Entry c = new Entry(key, value);
                int h = key.threadLocalHashCode & (len - 1);
                while (table[h] != null)
                    h = nextIndex(h, len);
                table[h] = c;
                size++;
            }
        }
    }
}

子线程是如何继承父线程的变量的,看Thread类的如下代码

java
private Thread(ThreadGroup g, Runnable target, String name,
               long stackSize, AccessControlContext acc,
               boolean inheritThreadLocals) {
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }

    this.name = name;
    //获取当前线程(父线程)
    Thread parent = currentThread();
    //安全校验
    SecurityManager security = System.getSecurityManager();
    if (g == null) {
        /* Determine if it's an applet or not */

        /* If there is a security manager, ask the security manager
               what to do. */
        if (security != null) {
            g = security.getThreadGroup();
        }

        /* If the security manager doesn't have a strong opinion
               on the matter, use the parent thread group. */
        if (g == null) {
            g = parent.getThreadGroup();
        }
    }

    /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
    g.checkAccess();

    /*
         * Do we have the required permissions?
         */
    if (security != null) {
        if (isCCLOverridden(getClass())) {
            security.checkPermission(
                SecurityConstants.SUBCLASS_IMPLEMENTATION_PERMISSION);
        }
    }

    g.addUnstarted();


    this.group = g;// 设置为当前线程组
    this.daemon = parent.isDaemon();// 判定是否是守护线程同父线程
    this.priority = parent.getPriority();// 优先级同父线程
    if (security == null || isCCLOverridden(parent.getClass()))
        this.contextClassLoader = parent.getContextClassLoader();
    else
        this.contextClassLoader = parent.contextClassLoader;
    this.inheritedAccessControlContext =
        acc != null ? acc : AccessController.getContext();
    
    this.target = target;
    setPriority(priority);
    
    // 如果是否初始化线程的inheritThreadLocals变量为true并且父线程的inheritThreadLocals变量不为null
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        // 设置子线程的inheritThreadLocals变量为父线程的inheritThreadLocals变量
        this.inheritableThreadLocals =
        ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    
    /* Stash the specified stack size in case the VM cares */
    this.stackSize = stackSize;

    /* Set thread ID */
    this.tid = nextThreadID();
}

InheritableThreadLocal 类通过重写方法在调用 get、set 方法的时候会调用重写的方法,调用方法时会对线程的 inheritableThreadLocals 变量进行初始化,在对子线程进行初始化的时候会将子线程的 inheritableThreadLocals 变量赋值为父线程的 inheritableThreadLocals 变量值,这样就实现了子线程继承父线程问题。

六、拓展ThreadLocal使用不当引起的内存泄漏问题

首先了解下什么是 弱引用,弱引用也就是GC会主动的帮你把内存回收掉,但是当弱引用指定的对象有强引用指向时GC则不会回收对象。

ThreadLocalMap的内部实现实际上是一个Entry集合,Entry类继承了WeakReference类,表示其是一个弱引用对象,其构造函数super调用了父类的构造函数,传递了k变量,即ThreadLocal对象的引用,也就是说Map集合中存储的是ThreadLocal的弱引用。

java
static class ThreadLocalMap {

    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
}

如果当前线程一直存在且没有调用ThreadLocal的remove方法,那么ThreadLocal会在下次GC的时候将弱引用对象回收,这样就会造成ThreadLocalMap对象中的Entry对象的 key为null,但是value值还是存在强引用关系,这样就会造成内存泄漏问题;引用链关系如下:

Thread->ThreadLocalMap->Entry->value

弱引用会在没有强引用的情况下在下次GC的时候自动的回收掉,每次使用完后要记得主动的调用remove方法,而且ThreadLocal每次调用get、set、remove的时候都会直接或者间接的调用 expungeStaleEntry方法 清除掉key为null的Entry,从而避免了内存泄漏。

七、拓展InheritableThreadLocal类子线程使用线程池更改存储变量值不变问题

java
public class Test {
    public static final ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
    private static ExecutorService executorService = Executors.newFixedThreadPool(1);

    public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException, InterruptedException {
        threadLocal.set("主线程1。。。");
        System.out.println("线程1:" + threadLocal.get());
        executorService.submit(TtlRunnable.get(() -> System.out.println("子线程:" + Thread.currentThread().getName() + ":" + threadLocal.get())));
        Thread.sleep(1000);
        threadLocal.set("主线程2。。。");
        System.out.println("线程2:" + threadLocal.get());
        executorService.submit(TtlRunnable.get(() -> System.out.println("子线程:" + Thread.currentThread().getName() + ":" + threadLocal.get())));
    }

}

输出结果:

线程1:主线程1。。。
子线程:pool-1-thread-1:主线程1。。。
线程2:主线程2。。。
子线程:pool-1-thread-1:主线程1。。。

可以看到在运行线程2之前会修改InheritableThreadLocal内部存的值,但是在线程池内部获取值还是原来的,这其实又涉及到了线程池会池化复用线程情况下,提供ThreadLocal值传递问题,推荐使用阿里开源的TTLhttps://github.com/alibaba/transmittable-thread-local

注意项

  1. ThreadLocal与当前线程绑定,如果不用的时候需及时进行remove,否则会导致内存泄露。
  2. InheritableThreadLocal 中是将父线程中的 inheritableThreadLocals 浅拷贝到到子线程中,代码上看就算不使用也会拷贝。(如理解有误请指正)
  3. 父子线程的 inheritableThreadLocals 并不共享,如果你在父线程中执行了remove,子线程中不会受影响,依旧可以get出来。如果有使用到判空get值做判空处理,需注意。
  4. inheritableThreadLocals 只会在初始化时进行拷贝,如果使用线程池需要注意。可看下面例子。

注意 使用静态和不使用静态时候

使用静态的 InheritableThreadLocal 线程池复用时候不会有问题

ThreadLocal是线程本地变量,每个线程有自己的副本;而InheritableThreadLocal具有继承性,在创建子线程时,子线程可以继承父线程的变量副本。

java
private static final InheritableThreadLocal<T> t1 = new InheritableThreadLocal<T>();
java
ThreadLocal<String> t = new InheritableThreadLocal<>();
t.set("test");

ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(() -> {
    System.out.println(t.get());
    t.remove();
});
// 确保上面代码执行完毕
Thread.sleep(1000);

// 再次执行,在同一线程中,没有新建线程,所以不会进行重新拷贝,输出为null
executorService.execute(() -> {
    System.out.println(t.get());
});

v1.1 二刷JUC

线程基础知识复习

  • 1把锁:synchronized

  • 2个并:

    • 并发(concurrent):是在同一实体上的多个事件,是在一台机器上“同时”处理多个任务,同一时刻,其实是只有一个事情再发生。
    • 并行(parallel):是在不同实体上的多个事件,是在多台处理器上同时处理多个任务,同一时刻,大家都在做事情,你做你的,我做我的,各干各的。
  • 严谨说明

  • ①现在都是多核CPU,在多核CPU下,并行指的是多个任务在同一时刻同时执行,比如4核CPU同时执行4个线程。并发指的是多个任务在同一时间段内交替执行,比如多个线程轮流使用1个或多个CPU。

  • 而在单核CPU架构下,同一时刻只能运行1个线程,操作系统通过CPU的时间片机制提高CPU的并发能力,由于CPU在线程间的切换非常快,所以人类感觉是同时运行的,也就是常说的微观串行、宏观并行。

  • ②同步指的是线程按照顺序依次执行任务,线程执行完当前任务后,才能继续执行下一个任务。

  • 异步指的是主线程调用一个任务后,无需等待任务的完成,可以继续向下执行,这个任务通常由其他线程去处理。

  • :::

  • 3个程:

    • 进程:在系统中运行的一个应用程序,每个进程都有它自己的内存空间和系统资源

    • 线程:也被称为轻量级进程,在同一个进程内会有1个或多个线程,是大多数操作系统进行时序调度的基本单元。

    • 管程:Monitor(锁),也就是我们平时所说的锁。Monitor其实是一种同步机制,它的义务是保证(同一时间)只有一个线程可以访问被保护的数据和代码,JVM中同步是基于进入和退出监视器(Monitor管程对象)来实现的,底层由C++语言实现。

      Java对象是在Java堆中创建的,而Monitor是与Java对象关联的、由JVM内部管理的机制。Monitor的创建和销毁并不直接对应于Java对象的创建和销毁。更确切地说,当Java对象被用作同步锁时,JVM会为该对象关联一个Monitor;而当该对象不再被用作同步锁,或者对象被垃圾回收时,与其关联的Monitor可能会被JVM内部释放或重新利用。

  • 线程分类(一般不做特别说明配置,默认都是用户线程):

    • 用户线程:是系统的工作线程,它会完成这个程序需要完成的业务操作。
    • 守护线程:是一种特殊的线程为其他线程服务的,在后台默默地完成一些系统性的任务,比如垃圾回收线程就是最典型的例子。守护线程作为一个服务线程,没有服务对象就没有必要继续运行了,如果用户线程全部结束了,意味着程序需要完成的业务操作已经结束了,系统可以退出了。所以假如当系统只剩下守护线程的时候,守护线程伴随着JVM一同结束工作。
java
/**
 * @author York
 * @className DaemonDemo
 * @date 2024/09/26 09:13
 * @description
 */
public class DaemonDemo {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "开始运行,"
                    + (Thread.currentThread().isDaemon() ? "守护线程" : "用户线程"));
            while (true) {

            }
        }, "t1");

        // 设置为守护线程必须在线程start之前设置,如果已经start了再设置就会报错
        t1.setDaemon(true); 
        t1.start();

        // 暂停几秒钟线程
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println((Thread.currentThread().getName() + "---主线程结束"));
    }
}

CompletableFuture

CompletableFuture实现了两个接囗: FutureCompletionstage

Future表示异步计算的结果,Completionstage用于表示异步执行过程中的一个步骤(Stage),这个步骤可能是由另外一个Completionstage触发的,随着当前步骤的完成,也可能会触发其他一系列Completionstage的执行。从而我们可以根据实际业务对这些步骤进行多样化的编排组合,Completionstage接口正是定义了这样的能力我们可以通过其提供的thenApply、thenCompose等函数式编程方法来组合编排这些步骤。

Future接口理论知识复习

Future接口(FutureTask实现类)定义了操作 异步任务 执行一些方法,如获取异步任务的执行结果、取消异步任务的执行、判断任务是否被取消、判断任务执行是否完毕等。

举例:比如主线程让一个子线程去执行任务,子线程可能比较耗时,启动子线程开始执行任务后,主线程就去做其他事情了,忙完其他事情或者先执行完,过了一会再才去获取子任务的执行结果或变更的任务状态(老师上课时间想喝水,他继续讲课不结束上课这个主线程,让学生去小卖部帮老师买水完成这个耗时和费力的任务)。

Future接口常用实现类FutureTask异步任务

Future接口能干什么

Future是Java5新加的一个接口,它提供一种 异步并行计算 的功能,如果主线程需要执行一个很耗时的计算任务,我们就可以通过Future把这个任务放进异步线程中执行,主线程继续处理其他任务或者先行结束,再通过Future获取计算结果。

Future接口相关结构

  • 目的:异步 多线程任务执行且 返回有结果,三个特点:多线程、有返回、异步任务(班长为老师去买水作为新启动的异步多线程任务且买到水有结果返回)

  • 代码实现:Runnable接口+Callable接口+Future接口和FutureTask实现类。

技术漫游

本站访客数 人次 本站总访问量