[JAVA基础知识汇总-1] 创建线程的几种方式(含线程池相关)

news2024/12/28 17:45:21

文章目录

  • 1. 继承Thread类
  • 2. 实现Runnable接口
  • 3. 实现Callable接口
  • 4. 线程池
    • 4.1 利用Executors工具类来创建线程池
    • 4.2 为什么不建议使用Executors来创建线程池?
    • 4.3 ThreadPoolExecutor是线程池的核心实现类,可以利用它来创建线程池
    • 4.4 线程池的状态

可以认为有四种方式,也可以认为有一种,因为都跟Runnable接口有关

1. 继承Thread类

代码

public class Thread1ExtendsThread extends Thread {
//    public Thread1(String name) {
//        super(name);
//    }

    @Override

    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println("这里是自定义线程:" + Thread.currentThread().getName() + ", i = " + i + ";");
        }
    }

    public static void main(String[] args) {
        // new Thread1("xinliushijian").start();
        new Thread1ExtendsThread().start();
        for (int i = 0; i < 3; i++) {
            System.out.println("这里是main线程:" + Thread.currentThread().getName() + ", i = " + i + ";");
        }
    }
}

打印

这里是main线程:main, i = 0;
这里是main线程:main, i = 1;
这里是main线程:main, i = 2;
这里是自定义线程:Thread-0, i = 0;
这里是自定义线程:Thread-0, i = 1;
这里是自定义线程:Thread-0, i = 2;

知识点

  1. new Thread 构造器的参数可以是Runnable,但没有Callable,因为如果是实现了Callable接口的线程,参数应该是FutureTask
  2. Thread 是Runnable接口的实现类
  3. 这种方式的坏处是不能再继承其他类了,因为java是单继承
  4. 没有返回值

在这里插入图片描述

2. 实现Runnable接口

代码

public class Thread2Runnable implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println("这里是自定义线程:" + Thread.currentThread().getName() + ", i = " + i + ";");
        }
    }

    public static void main(String[] args) {
        Thread2Runnable thread2 = new Thread2Runnable();
        new Thread(thread2).start();
        for (int i = 0; i < 3; i++) {
            System.out.println("这里是main线程:" + Thread.currentThread().getName() + ", i = " + i + ";");
        }
    }
}

打印

这里是main线程:main, i = 0;
这里是main线程:main, i = 1;
这里是main线程:main, i = 2;
这里是自定义线程:Thread-0, i = 0;
这里是自定义线程:Thread-0, i = 1;
这里是自定义线程:Thread-0, i = 2;

知识点

  1. 没有返回值

3. 实现Callable接口

代码

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

public class Thread3Callable implements Callable {

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 3; i++) {
            sum += i;
            System.out.println("这里是自定义线程:" + Thread.currentThread().getName() + ", i = " + i + ";");
        }
        return sum;
    }

    public static void main(String[] args) {
        // 使用Lambda表达式创建Callable对象, FutureTask类来包装Callable对象
//        FutureTask<Integer> future = new FutureTask<>(
//                () -> 3
//        );
        FutureTask<Integer> future = new FutureTask<>(new Thread3Callable());
        // 实质上还是以Callable对象来创建并启动线程
        new Thread(future).start();
        try {
            // get()方法会阻塞,直到子线程执行结束才返回
            System.out.println("自定义线程返回值:" + future.get());
        } catch (Exception e) {
            e.printStackTrace();
        }

        for (int i = 0; i < 3; i++) {
            System.out.println("这里是main线程:" + Thread.currentThread().getName() + ", i = " + i + ";");
        }
    }
}

打印

这里是自定义线程:Thread-0, i = 0;
这里是自定义线程:Thread-0, i = 1;
这里是自定义线程:Thread-0, i = 2;
自定义线程返回值:3
这里是main线程:main, i = 0;
这里是main线程:main, i = 1;
这里是main线程:main, i = 2;

知识点

  1. 有返回值,需要跟FutureTask搭配使用,来获得线程的执行结果
  2. FutureTask也是实现了Runnable接口
  3. 以上三种方式都是new Thread().start()的方式去启动线程
    在这里插入图片描述

4. 线程池

4.1 利用Executors工具类来创建线程池

代码

import com.google.common.util.concurrent.ThreadFactoryBuilder;

import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

public class ThreadDemo3Executors {
    public static void main(String[] args) {
        // 创建线程工厂
        ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("Async-pool-%d").build();

        // 创建定时任务的线程池
        Executors.newScheduledThreadPool(4, threadFactory);

        // 创建可缓存的线程池,只会重用空闲可用的线程,没有可用的线程时会创建新线程
        Executors.newCachedThreadPool();

        // 单个线程的线程池
        Executors.newSingleThreadExecutor();

        // 固定线程数量的线程池
        Executors.newFixedThreadPool(3);
    }
}

4.2 为什么不建议使用Executors来创建线程池?

以Executors.newFixedThreadPool(3)来举例

源码

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    
    public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

    private static final RejectedExecutionHandler defaultHandler =
        new AbortPolicy();
        

知识点

我们发现其实他也是利用ThreadPoolExecutor来创建管理线程池,这种方式不是可以的吗!
问题是它没有定制化,
它只能控制核心线程数和最大线程数两个值,且它俩是相等的,其他的都是默认的。
阻塞队列是LinkedBlockingQueue,容量直接Integer最大值,其中可放21亿多的任务,若是真的任务巨多,会造成OOM

4.3 ThreadPoolExecutor是线程池的核心实现类,可以利用它来创建线程池

在这里插入图片描述

Executor线程池相关顶级接口,它将任务的提交与任务的执行分离开来
ExecutorService继承并扩展了Executor接口,提供了Runnable、FutureTask等主要线程实现接口扩展
ThreadPoolExecutor是线程池的核心实现类,用来执行被提交的任务
ScheduledExecutorService继承ExecutorService接口,并定义延迟或定期执行的方法
ScheduledThreadPoolExecutor继承ThreadPoolExecutor并实现了ScheduledExecutorService接口,是延时执行类任务的主要实现

源码

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

ThreadPoolExecutor包含了7个核心参数,参数含义:

corePoolSize:核心线程数
maximumPoolSize:最大线程数
keepAliveTime:当线程池中线程数大于corePoolSize,并且没有可执行任务时大于corePoolSize那部分线程的存活时间
unit:keepAliveTime的时间单位
workQueue:用来暂时保存任务的工作队列
threadFactory:线程工厂提供线程的创建方式,默认使用Executors.defaultThreadFactory()
handler:当线程池所处理的任务数超过其承载容量或关闭后继续有任务提交时,所调用的拒绝策略

代码

import com.google.common.util.concurrent.ThreadFactoryBuilder;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadDemo4ThreadPoolExecutor {

    public static final ThreadPoolExecutor EXECUTOR_SERVICE;
    static {
        int corePoolSize = 500;
        int maxPoolSize = 500;
        ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("xinliushijian-thread-pool-%d").build();
        RejectedExecutionHandler rejectedExecutionHandler = new RejectedExecutionHandler() {
            @Override
            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                if (!executor.isShutdown()) {
                    try {
                        executor.getQueue().put(r);
                    } catch (InterruptedException e) {
                        System.out.println("hahaha");
                    }

                }
            }
        };

        EXECUTOR_SERVICE = new ThreadPoolExecutor(corePoolSize, maxPoolSize, 0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue(), threadFactory, rejectedExecutionHandler);
    }

    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());
        List<String> nameList = new ArrayList<>();
        List<String> nameResultList = new ArrayList<>();
        nameList.add("xiaohua");
        nameList.add("xiaohua1");
        nameList.add("xiaohua2");
        nameList.add("xiaohua3");
        nameList.add("xiaohua4");
        nameList.add("xiaohua5");
        nameList.add("xiaohua6");
        nameList.add("xiaohua7");
        CountDownLatch countDownLatch = new CountDownLatch(nameList.size());
        nameList.forEach(name -> {
            dealWithName(name, countDownLatch, nameResultList);
        });

        System.out.println("nameResultList前: " + nameResultList);

        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            System.out.println("exception");
        }

        System.out.println("nameResultList后: " + nameResultList);
    }

    private static void dealWithName(String name, CountDownLatch countDownLatch, List<String> nameResultList) {
        EXECUTOR_SERVICE.execute(() -> {
            countDownLatch.countDown();
            System.out.println(name + "haha");
            System.out.println(Thread.currentThread().getName());
            // 打印当前线程数
            System.out.println("打印当前线程数: " + EXECUTOR_SERVICE.getPoolSize());

            // 打印执行任务的线程数
            System.out.println("打印执行任务的线程数: " + EXECUTOR_SERVICE.getActiveCount());

            // 总任务数 = 正在执行的任务数 + 队列任务数
            System.out.println("总任务数 = 正在执行的任务数 + 队列任务数: " + EXECUTOR_SERVICE.getTaskCount());

            nameResultList.add(name);
        });
    }
}

打印

main
xiaohuahaha
xinliushijian-thread-pool-0
xiaohua1haha
xinliushijian-thread-pool-1
打印当前线程数: 3
xiaohua3haha
xinliushijian-thread-pool-3
xiaohua2haha
xinliushijian-thread-pool-2
打印当前线程数: 5
打印当前线程数: 4
打印执行任务的线程数: 6
打印当前线程数: 5
总任务数 = 正在执行的任务数 + 队列任务数: 6
xiaohua5haha
xinliushijian-thread-pool-5
打印当前线程数: 7
打印执行任务的线程数: 6
总任务数 = 正在执行的任务数 + 队列任务数: 8
xiaohua6haha
xinliushijian-thread-pool-6
打印当前线程数: 8
打印执行任务的线程数: 6
总任务数 = 正在执行的任务数 + 队列任务数: 8
打印执行任务的线程数: 5
xiaohua7haha
xinliushijian-thread-pool-7
打印当前线程数: 8
nameResultList前: [xiaohua2, xiaohua5]
打印执行任务的线程数: 6
总任务数 = 正在执行的任务数 + 队列任务数: 8
打印执行任务的线程数: 6
打印执行任务的线程数: 5
总任务数 = 正在执行的任务数 + 队列任务数: 8
总任务数 = 正在执行的任务数 + 队列任务数: 8
总任务数 = 正在执行的任务数 + 队列任务数: 8
xiaohua4haha
xinliushijian-thread-pool-4
nameResultList后: [xiaohua2, xiaohua5, xiaohua6, xiaohua3, xiaohua, xiaohua7, xiaohua1]
打印当前线程数: 8
打印执行任务的线程数: 1
总任务数 = 正在执行的任务数 + 队列任务数: 8

源码

ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("xinliushijian-thread-pool-%d").build();
public ThreadFactory build() {
    return doBuild(this);
}
private static ThreadFactory doBuild(ThreadFactoryBuilder builder) {
        final String nameFormat = builder.nameFormat;
        final Boolean daemon = builder.daemon;
        final Integer priority = builder.priority;
        final Thread.UncaughtExceptionHandler uncaughtExceptionHandler = builder.uncaughtExceptionHandler;
        final ThreadFactory backingThreadFactory = builder.backingThreadFactory != null ? builder.backingThreadFactory : Executors.defaultThreadFactory();
        final AtomicLong count = nameFormat != null ? new AtomicLong(0L) : null;
        return new ThreadFactory() {
            public Thread newThread(Runnable runnable) {
                Thread thread = backingThreadFactory.newThread(runnable);
                Objects.requireNonNull(thread);
                if (nameFormat != null) {
                    thread.setName(ThreadFactoryBuilder.format(nameFormat, ((AtomicLong)Objects.requireNonNull(count)).getAndIncrement()));
                }

                if (daemon != null) {
                    thread.setDaemon(daemon);
                }

                if (priority != null) {
                    thread.setPriority(priority);
                }

                if (uncaughtExceptionHandler != null) {
                    thread.setUncaughtExceptionHandler(uncaughtExceptionHandler);
                }

                return thread;
            }
        };
    }
    public interface ThreadFactory {

    /**
     * Constructs a new {@code Thread}.  Implementations may also initialize
     * priority, name, daemon status, {@code ThreadGroup}, etc.
     *
     * @param r a runnable to be executed by new thread instance
     * @return constructed thread, or {@code null} if the request to
     *         create a thread is rejected
     */
    Thread newThread(Runnable r);
}

知识点

从上面源码可以清楚看到,其中创建线程的步骤在ThreadFactory,线程工厂创建的线程是实现了Runnable接口,所以利用线程池来创建线程也是跟Runnable有关,所以这四种创建线程的方式其实都跟Runnable有关。

4.4 线程池的状态

在这里插入图片描述

线程池的5种状态

    private static final int COUNT_BITS = Integer.SIZE - 3;

    // runState is stored in the high-order bits
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;
  • 1. RUNNING 运行状态
    线程池新建或调用execute()方法后,处于运行状态,能够接收新的任务

  • 2. SHUTDOWN 关闭状态
    调用shutdown()方法后的状态,此时线程池不再接收新的任务,但会继续处理已提交的任务队列中的任务

  • 3. STOP 停止状态
    调用shutdownNow()方法后的状态,此时线程池不再接收新的任务,不处理任务队列中的任务,并且中断正在进行的任务

  • 4. TIDYING 整理状态
    只是一个中间状态,不做任务处理

    final void tryTerminate() {
        for (;;) {
            int c = ctl.get();
            if (isRunning(c) ||
                runStateAtLeast(c, TIDYING) ||
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                return;
            if (workerCountOf(c) != 0) { // Eligible to terminate
                interruptIdleWorkers(ONLY_ONE);
                return;
            }

            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                    try {
                        terminated();
                    } finally {
                        ctl.set(ctlOf(TERMINATED, 0));
                        termination.signalAll();
                    }
                    return;
                }
            } finally {
                mainLock.unlock();
            }
            // else retry on failed CAS
        }
    }

    /**
     * Method invoked when the Executor has terminated.  Default
     * implementation does nothing. Note: To properly nest multiple
     * overridings, subclasses should generally invoke
     * {@code super.terminated} within this method.
     */
    protected void terminated() { }

从上面源码可以看到,此时所有任务都已终止,workerCount为0。

当线程池处于调整状态后执行了一个terminated()方法,而它是个空方法,所以调整状态默认是不会做任务动作的。

通过注释我们发现设计它的用处,它是一个可以被子类继承的protected修饰的方法,我们可以认为调整状态的作用就是方便子类继承扩展的,在任务都终止后做我们自定义的动作。

  • 5. TERMINATED 终止状态
    线程池内所有的线程都已终止时,就会进入终止状态

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2113126.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数字化转型的关键指南:《数字化专业知识体系》深度剖析应用策略

数字化浪潮下的企业生存法则 随着全球企业加速数字化转型&#xff0c;如何有效应对技术变革带来的挑战和机遇成为各行业关注的焦点。传统的IT管理模式已经无法满足日益复杂的数字化需求&#xff0c;亟需一种新型、综合的知识体系来引导企业迈向成功。《数字化专业知识体系》&a…

【MySQL】MySQL基础

目录 什么是数据库主流数据库基本使用MySQL的安装连接服务器服务器、数据库、表关系使用案例数据逻辑存储 MySQL的架构SQL分类什么是存储引擎 什么是数据库 mysql它是数据库服务的客户端mysqld它是数据库服务的服务器端mysql本质&#xff1a;基于C&#xff08;mysql&#xff09…

【unix高级编程系列】线程

引言 我们知道unix进程中可以有多个线程&#xff0c;进程中的线程可以访问该进程的所有组成部分。并且CPU的调度单元就是线程。这就面临一个问题&#xff1a;当进程中的临界资源需要在多个线程中共享时&#xff0c;如何解决一致性问题&#xff1f; 本文将从线程的概念、线程的…

Bootstrap 字体图标无法显示问题,<i>标签字体图标无法显示问题

bootstrap fileInput 以及 Bootstrap 字体图标无法显示问题。 今天在用 bootstrap fileInput 插件的时候发现图标无法显示&#xff0c;如下&#xff1a; 查看DOM&#xff0c;发现那些图标是<i>标签做的&#xff1a; 网上的方案 方案1 网上很多人说是我们打乱了boots…

dfs序+线段树,CF 838B - Diverging Directions

目录 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 838B - Diverging Directions 二、解题报告 1、思路分析 注意这是一棵有…

正点原子STM32F103+ESP8266+DS18B20+DHT11连接阿里云

文章目录 MQTT协议1. 基础知识2. 报文形式3. 连接报文4. 心跳报文5. 订阅报文5.1. 订阅主题报文SUBSCRIBE5.2. 订阅确认SUBACK5.3. 取消订阅UNSUBSCRIBE5.4. 取消订阅确认UNSUBACK 6. 发布报文6.1. 发布消息PUBLISH6.2. 发布确认PUBACK 7. 阿里云账号创建8. 网络调试助手接入阿…

机器人助力上下料搬运,加速仓库转运自动化

近年来&#xff0c;国内制造业领域掀起了一股智能化改造的浪潮&#xff0c;众多工厂纷纷采纳富唯智能提供的先进物流解决方案&#xff0c;这一举措显著优化了生产流程&#xff0c;实现了生产效率的飞跃式增长。得益于这些成功案例&#xff0c;某信息技术服务企业在工厂智能物流…

如何打造高校实验室教学管理系统?Java SpringBoot助力,MySQL存储优化,2025届必备设计指南

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

每日一题——第八十一题

打印如下图案: #include<stdio.h> int main() {int i, j;char ch A;for (i 1; i < 5; i, ch){for (j 0; j < 5 - i; j){printf(" ");//控制空格输出}for (j 1; j < 2 * i; j)//条件j < 2 * i{printf("%c", ch);//控制字符输出}prin…

❤《实战纪录片 1 》原生开发小程序中遇到的问题和解决方案

《实战纪录片 1 》原生开发小程序中遇到的问题和解决方案 文章目录 《实战纪录片 1 》原生开发小程序中遇到的问题和解决方案1、问题一&#xff1a;原生开发中 request请求中返回 的数据无法 使用this传递给 data{}中怎么办&#xff1f;2、刚登录后如何将token信息保存&#xf…

[网鼎杯 2020 青龙组]bang-快坚持不下去的第五天

ps:安卓的解壳&#xff0c;也就是frida-dexdump一把嗦&#xff0c;其他wp上说用真机成功的几率大一些&#xff0c;我就用的模拟器 详细的看这一篇博客&#xff1a; 安卓模拟器Frida环境搭建 &#xff08;mumuadbfrida&#xff09; 在python中安装frida时多一步 pip install…

【Dash】feffery_antd_componenet 中的 AntdSpace

一、feffery_antd_componenet 中的 AntdSpace feffery_antd_components&#xff08;简称fac&#xff09;中的AntdSpace组件是一个基于Ant Design的Dash第三方组件&#xff0c;它用于在水平或垂直方向上放置多个元素&#xff0c;并提供元素之间的间距。以下是AntdSpace组件的一…

一文说清什么是AI原生(AI Native)应用以及特点

引言&#xff1a;智能新纪元 如今&#xff0c;走在街头&#xff0c;哪儿不被智能科技包围&#xff1f;智能音箱、自动驾驶汽车、聊天机器人......这些都在用不同的方式提升我们的生活体验。然而&#xff0c;究竟什么才能称得上“AI原生应用”呢&#xff1f; 什么是AI原生&…

C++:STL简介

✨ Blog’s 主页: 白乐天_&#xff3e;&#xff56;&#xff3e; &#x1f308; 个人Motto&#xff1a;他强任他强&#xff0c;清风拂山岗&#xff01; &#x1f4ab; 欢迎来到我的学习笔记&#xff01; 一、STL引入 STL&#xff08;Standard template libaray-标准模板库…

最新厦门新能安社招入职Verify测评笔试攻略敬请查收SHL

作为全球知名锂电池供应商——ATL与CATL的合资公司&#xff0c;新能安专注高端锂电池的研究与创新&#xff0c;聚焦储能系统、微型车、智能产品驱动电池等三大领域的研发、生产、销售与服务。 a.测评内容包含演绎推理数字推理两部分&#xff0c;大约用时40分钟左右; b.正式测评…

node.js实现阿里云短信发送

效果图 实现 一、准备工作 1、官网直达网址&#xff1a; 阿里云 - 短信服务 2、按照首页提示依次完成相应资质认证和短信模板审核&#xff1b; 3、获取你的accessKeySecret和accessKeyId&#xff1b; 方法如下&#xff1a; 获取AccessKey-阿里云帮助中心 4、获取SignNa…

测试你们认为最好的AI工具,是不是好用得自己试试!

大家好&#xff0c;我是凡人&#xff0c;在 OpenAI 春季发布会后&#xff0c; GPT-4o 一时风光无量&#xff0c;一个同事不信邪&#xff0c;非要用 GPT-4o 版本对 OpenAI 官网上的例子尝试生成&#xff0c;本来还是嘲笑他的心态&#xff0c;但他还真的发现了点有意思的事情。 …

多线程篇(阻塞队列- BlockingQueue)(持续更新迭代)

目录 一、了解什么是阻塞队列之前&#xff0c;需要先知道队列 1. Queue&#xff08;接口&#xff09; 二、阻塞队列 1. 前言 2. 什么是阻塞队列 3. Java里面常见的阻塞队列 三、BlockingQueue&#xff08;接口&#xff09; 1. 前言 2. 简介 3. 特性 3.1. 队列类型 …

实时地图+瞬移,黑神话地图工具来了

工具下载: https://pan.quark.cn/s/12b9cef46bf0 瞬移功能使用说明&#xff1a; 1、必须在一修大师客户端: 使用猫修APP扫码登陆后使用&#xff1b; 2、打开《黑神话》游戏&#xff1b; 3、点击修改器页面右上角“开始使用”按钮&#xff1b; 4、点击你想要瞬移的点位图标…

使用Azure Devops Pipeline将Docker应用部署到你的Raspberry Pi上

文章目录 1. 添加树莓派到 Agent Pool1.1 添加pool1.2 添加agent 2. 将树莓派添加到 Deployment Pool2.1 添加pool2.2 添加target 3. 添加编译流水线3.1 添加编译命令3.2 配置触发器 4. 添加发布流水线4.1 添加命令行4.2 配置artifact和触发器 5. 完成 1. 添加树莓派到 Agent P…