并发编程(六)—AbstractExecutorService源码分析
一、AbstractExecutorService简介
AbstractExecutorService是一个抽象类,实现了ExecutorService接口,提供了线程池的基本实现。它是Java Executor框架的核心类,提供了线程池的基本操作,如提交任务、管理线程池、执行任务等。
自定义执行器服务可用扩展AbreactExecutorService 并覆盖其方法以提供自己的实现。这使开发人员可以创建适合其特定需求的执行器服务。
二、AbstractExecutorService如何使用
AbstractExecutorService是一个抽象类,不能直接实例化,需要通过继承它的子类ThreadPoolExecutor或ScheduledThreadPoolExecutor来使用。使用AbstractExecutorService创建线程池的步骤如下:
2.1 没有返回值
// 1、创建ThreadPoolExecutor 创建一个固定大小的线程池,核心线程数为1,最大线程数为5,任务队列大小为10
ExecutorService executorService = new ThreadPoolExecutor(1, 5, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(10));
// 2、提交任务
executorService.submit(new Runnable() {@Overridepublic void run() {//执行任务}
});
// 3、关闭线程池
executorService.shutdown();2.2 有返回值
// 1、创建ThreadPoolExecutor 创建一个固定大小的线程池,核心线程数为1,最大线程数为5,任务队列大小为10
ExecutorService executorService = new ThreadPoolExecutor(1, 5, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(10));
// 2、提交一个Callable任务
Future<String> future = executorService.submit(new Callable<String>() {@Overridepublic String call() throws Exception {//执行任务,返回结果return "hello ExecutorService";}
});
// 3、获取值
String result = future.get();
// 4、关闭线程池
executorService.shutdown();总之,使用AbstractExecutorService创建线程池非常简单,只需要创建线程池对象、提交任务、关闭线程池即可。同时,AbstractExecutorService也提供了一些高级功能,如异步任务、定时任务等,可以根据具体需求选择使用。
三、如何自定义AbstractExecutorService
要自定义一个延迟AbstractExecutorService,以下步骤:
继承AbstractExecutorService类
实现 submit 方法 覆盖 submit 方法,将任务添加到队列中,其中在任务执行前应用延迟。
实现 execute 方法 覆盖 execute 方法,调用 submit 方法并返回 Future 对象,等待任务完成。
实现shutdown 方法 覆盖 shutdown 方法,停止所有任务并等待它们完成。
实现 DelayedTask 类 创建一个 DelayedTask 类,用于封装任务和延迟时间。
实现 RunnableTask 和 CallableTask 类 创建 RunnableTask 和 CallableTask 类,用于封装任务。
public class CustomDelayedExecutorService extends AbstractExecutorService {private final DelayQueue<DelayedTask<?>> queue = new DelayQueue<>();@Overridepublic <T> Future<T> submit(Callable<T> task) {return schedule(new CallableTask<>(task));}@Overridepublic Future<?> submit(Runnable task) {return schedule(new RunnableTask(task));}private <T> Future<T> schedule(DelayedTask<T> task) {queue.offer(task);return task;}@Overridepublic void execute(Runnable command) {submit(command);}@Overridepublic void shutdown() {for (DelayedTask<?> task : queue) {task.cancel(false);}}@Overridepublic List<Runnable> shutdownNow() {List<Runnable> tasks = new ArrayList<>();for (DelayedTask<?> task : queue) {if (task.cancel()) {tasks.add(task.getTask());}}return tasks;}@Overridepublic boolean isShutdown() {return false;}@Overridepublic boolean isTerminated() {return false;}@Overridepublic boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {return false;}
}
class DelayedTask<T> implements RunnableFuture<T>, Delayed {private final long delay;private final long expire;private final RunnableFuture<T> task;public DelayedTask(RunnableFuture<T> task, long delay) {this.task = task;this.delay = delay;this.expire = System.nanoTime() + TimeUnit.NANOSECONDS.convert(delay, TimeUnit.MILLISECONDS);}@Overridepublic long getDelay(TimeUnit unit) {return unit.convert(expire - System.nanoTime(), TimeUnit.NANOSECONDS);}@Overridepublic int compareTo(Delayed o) {return Long.compare(expire, ((DelayedTask<?>) o).expire);}@Overridepublic void run() {if (!isCancelled()) {task.run();}}@Overridepublic boolean cancel(boolean mayInterruptIfRunning) {return task.cancel(mayInterruptIfRunning);}@Overridepublic boolean isCancelled() {return task.isCancelled();}@Overridepublic boolean isDone() {return task.isDone();}@Overridepublic T get() throws InterruptedException, ExecutionException {return task.get();}@Overridepublic T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {return task.get(timeout, unit);}public RunnableFuture<T> getTask() {return task;}
}
class RunnableTask implements RunnableFuture<Void> {private final Runnable task;public RunnableTask(Runnable task) {this.task = task;}@Overridepublic void run() {task.run();}@Overridepublic boolean cancel(boolean mayInterruptIfRunning) {return false;}@Overridepublic boolean isCancelled() {return false;}@Overridepublic boolean isDone() {return true;}@Overridepublic Void get() throws InterruptedException, ExecutionException {return null;}@Overridepublic Void get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {return null;}
}
class CallableTask<T> extends FutureTask<T> {public CallableTask(Callable<T> callable) {super(callable);}
}四、AbstractExecutorService源码分析
4.1 AbstractExecutorService dragrams

4.2 方法分析

4.2.1 newTaskFor(Runnable runnable, T value) 方法
AbstractExecutorService中的newTaskFor方法,用于创建一个RunnableFuture对象,其中RunnableFuture是Future和Runnable接口的结合体,可以用来表示异步执行的结果。该方法的参数包括一个Runnable对象和一个泛型T的值。通过传入的Runnable对象和T类型的值,创建了一个FutureTask对象,并将其返回。
FutureTask是RunnableFuture的一个具体实现,它表示一个可取消的异步计算,可以通过调用get方法获取计算结果。在创建FutureTask对象时,需要传入一个Callable对象或Runnable对象,用于执行异步计算。而在这里,我们传入了一个Runnable对象和一个T类型的值,表示该Runnable对象的执行结果为T类型的值。
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {// 将runnable包装成FutureTask类型return new FutureTask<T>(runnable, value);}4.2.2 submit(Runnable task)方法

AbstractExecutorService中的submit方法是ExecutorService接口中的一个方法,用于提交一个Runnable任务,并返回一个表示该任务异步执行结果的Future对象。
如果传入的task为null,则抛出NullPointerException异常;
调用newTaskFor(Runnable runnable, T value)方法创建一个RunnableFuture对象ftask,其中RunnableFuture是Future和Runnable接口的结合体,用于表示异步执行的结果。在该方法中,我们将泛型T的值设置为null,表示该Runnable任务没有返回值;
通过调用execute(Runnable command)方法提交任务进行执行,该方法是一个抽象方法,需要由子类实现。在该方法中,我们将创建的ftask对象作为参数传入,表示需要执行的任务为ftask对象。
最后,将ftask对象返回,作为Future对象,用于获取任务执行结果或取消任务。
public Future<?> submit(Runnable task) {// 如果任务为null,则抛出nullPointerExceptionif (task == null) throw new NullPointerException();// 将task包装成RunnableFutureRunnableFuture<Void> ftask = newTaskFor(task, null);// 通过调用execute(Runnable command)方法提交任务进行执行execute(ftask);// 返回RunnableFuturereturn ftask;}4.2.3 doInvokeAny(Collection<? extends Callable<T>> tasks,boolean timed, long nanos)方法

AbstractExecutorService中doInvokeAny方法用于执行一个任务集合中的所有任务并返回第一个成功执行的任务的结果,如果所有任务都执行失败则抛出ExecutionException异常。其中,方法参数tasks为任务集合,timed和nanos用于指定超时时间,如果超时则抛出TimeoutException异常。
如果任务集合是否为空,则抛出NullPointerException异常
如果任务集合的大小为0,则抛出IllegalArgumentException异常
futures用于存储所有任务的Future对象, ecs对象用于异步执行任务集合中的任务。
从迭代器中获取一个任务xecutorCompletionService,并将该任务的执行结果的Future添加到futures列表中
死循环不断从ExecutorCompletionService中获取已完成的任务的结果Future,直到有一个任务完成为止,具体细流程如下:
调用ExecutorCompletionService的poll()方法获取一个已完成的任务的结果Future,如果poll()返回null,表示当前没有已完成的任务。
当前没有已完成的任务
还有未执行的任务(ntasks > 0),则从任务集合中获取下一个任务,并将其提交给ExecutorCompletionService执行。提交成功后,活跃线程数active加一;
没有执行的任务,并且活跃线程数为0(即所有任务都已执行完毕),则退出循环;
指定超时时间(timed为true),则调用poll(nanos, TimeUnit.NANOSECONDS)方法获取已完成的任务的结果Future,并等待最多nanos纳秒的时间。如果超时则抛出TimeoutException;
没有指定超时时间,则调用take()方法阻塞等待已完成的任务的结果Future。
如果获取到了已完成的任务的结果Future,则将活跃线程数active减一,并尝试获取该任务的执行结果。
如果获取结果时出现异常,则将异常保存在ExecutionException中,继续下一轮循环;
如果成功获取到了一个任务的执行结果,则直接返回该结果。
如果所有任务都已经完成,但是没有找到任何一个成功的任务,则抛出ExecutionException异常。
最后,取消所有尚未完成的任务,以便节省资源并提高效率
private<T> T doInvokeAny(Collection<? extends Callable<T>> tasks,boolean timed, long nanos)throws InterruptedException, ExecutionException, TimeoutException {// 如果任务集合是否为空,则抛出NullPointerException异常if (tasks == null)throw new NullPointerException();int ntasks = tasks.size();// 如果任务集合的大小为0,则抛出IllegalArgumentException异常if (ntasks == 0)throw new IllegalArgumentException();// futures用于存储所有任务的Future对象 ArrayList<Future<T>> futures = new ArrayList<Future<T>>(ntasks);ExecutorCompletionService<T> ecs =new ExecutorCompletionService<T>(this);try {ExecutionException ee = null;// 计算deadline时间final long deadline = timed ? System.nanoTime() + nanos : 0L;Iterator<? extends Callable<T>> it = tasks.iterator();// 从迭代器中获取一个任务xecutorCompletionService,并将该任务的执行结果的Future添加到futures列表中。futures.add(ecs.submit(it.next()));// 任务数减1--ntasks;// 退出循环标识(也就是调用ecs.sumbit的任务数)int active = 1;// 自旋、死循环for (;;) {// 获取Future对象Future<T> f = ecs.poll();// 判断future 任务是否已完成if (f == null) {// 任务没有完成,继续判断是否还有未执行的任务if (ntasks > 0) {// 任务s数减1--ntasks;// 再从tasks中获取一个任务,提交到ExecutorCompletionService中进行执行。futures.add(ecs.submit(it.next()));// 提交ecs中加1++active;}else if (active == 0)break;// 有超时时间限制 else if (timed) {//调用ExecutorCompletionService的poll(long timeout, TimeUnit unit)方法等待指定的超时时间。f = ecs.poll(nanos, TimeUnit.NANOSECONDS);// 在等待过程中已经超过了指定的超时时间,因此会抛出 TimeoutException 异常if (f == null)throw new TimeoutException();// 将剩余的超时时间重新计算,并继续执行后续的代码。nanos = deadline - System.nanoTime();}// 如果没有超时时间限制,则调用ExecutorCompletionService的take()方法一直等待,直到有任务执行完成。elsef = ecs.take(); }// 任务已完成(获取到Future对象)if (f != null) {--active;try {// 尝试获取Future对象的执行结果,return f.get();} catch (ExecutionException eex) {// 如果获取失败,则将异常保存在ee变量中,继续等待下一个任务的执行结果ee = eex;} catch (RuntimeException rex) {ee = new ExecutionException(rex);}}}// 如果所有任务都已经完成,但是没有找到任何一个成功的任务,则抛出ExecutionException异常。if (ee == null)ee = new ExecutionException();throw ee;} finally {// 取消所有尚未完成的任务,以便节省资源并提高效率for (int i = 0, size = futures.size(); i < size; i++)futures.get(i).cancel(true);}}4.2.4 invokeAll(Collection<? extends Callable<T>> tasks)方法

AbstractExecutorService中的invokeAll方法,它的作用是在执行给定的任务集合tasks中的所有任务,并等待所有任务完成后返回一个包含Future对象的列表,Future对象可以用来获取每个任务的执行结果。如果其中某个任务抛出异常,则该异常将传播到调用者。
判断任务是否为空,如果空,则抛出NullPointerException异常;
遍历参数tasks中的每个Callable任务,将每个任务转换成一个RunnableFuture对象,并把任务添加到futures中,然后调用execute方法执行任务。
循环遍历futures中的每个Future对象,如果某个Future对象的任务还没有完成,则调用get()方法等待任务完成,如果任务抛出异常,则忽略异常。
如果所有的任务都成功完成,则将done标记为true,并返回futures;
否则,取消所有未完成的任务,并抛出InterruptedException异常。
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)throws InterruptedException {// 判断任务是否为空,如果空,则抛出NullPointerException异常;if (tasks == null)throw new NullPointerException();ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());boolean done = false;try {// 遍历参数tasks中的每个Callable任务for (Callable<T> t : tasks) {// 将任务转换成一个RunnableFuture对象RunnableFuture<T> f = newTaskFor(t);futures.add(f);// 调用execute方法执行任务execute(f);}// 循环遍历futures中的每个Future对象for (int i = 0, size = futures.size(); i < size; i++) {Future<T> f = futures.get(i);// 如果某个Future对象的任务还没有完成if (!f.isDone()) {try {// 调用get()方法等待任务完成,如果任务抛出异常,则忽略异常。f.get();} catch (CancellationException ignore) {} catch (ExecutionException ignore) {}}}// 如果所有的任务都成功完成,则将done标记为true,并返回futures;done = true;return futures;} finally {if (!done)// 如果所有的任务没有全部成功完成,取消所有未完成的任务for (int i = 0, size = futures.size(); i < size; i++)futures.get(i).cancel(true);}
}
相关文章:
并发编程(六)—AbstractExecutorService源码分析
一、AbstractExecutorService简介AbstractExecutorService是一个抽象类,实现了ExecutorService接口,提供了线程池的基本实现。它是Java Executor框架的核心类,提供了线程池的基本操作,如提交任务、管理线程池、执行任务等。自定义…...
015行为型-职责链模式
目录定义标准模式实现:职责链变体使用链表实现使用数组实现应用场景日志输出spring过滤器spirng 拦截器mybatis动态sql定义 责链模式是一种设计模式,其目的是使多个对象能够处理同一请求,但是并不知道下一个处理请求的对象是谁。它能够解耦请…...
python例程:五子棋(控制台版)程序
目录《五子棋(控制台版)》程序使用说明程序示例代码可执行程序及源码下载路径《五子棋(控制台版)》程序使用说明 在PyCharm中运行《五子棋(控制台版)》即可进入如图1所示的系统主界面。 图1 游戏主界面 具…...
leveldb的Compaction线程
个人随笔 (Owed by: 春夜喜雨 http://blog.csdn.net/chunyexiyu) 1. leveldb的Compaction全局线程 在leveldb中,有一个全局的后台线程BGThread,用于数据库的MinorCompact与MajorCompact。 重点关注“全局线程”: 这个标识着无论一个进程打开…...
邪恶的想法冒出,立马启动python实现美女通通下
前言 嗨喽~大家好呀,这里是魔王呐 ❤ ~! 完整源码、python资料: 点击此处跳转文末名片获取 当我在首页刷到这些的时候~ 我的心里逐渐浮现一个邪念:我把这些小姐姐全都采集,可以嘛? 答案当然是可以的~毕竟就我这技术,…...
蓝桥杯刷题冲刺 | 倒计时18天
作者:指针不指南吗 专栏:蓝桥杯倒计时冲刺 🐾马上就要蓝桥杯了,最后的这几天尤为重要,不可懈怠哦🐾 文章目录0.知识点1.乳草的入侵今天写 搜索题 0.知识点 DFS 设计步骤 确定该题目的状态(包括边…...
经典算法面试题——Java篇-附带赠书活动,评论区随机选取一人赠书
目录 一.图书推荐 二.说一下什么是二分法?使用二分法时需要注意什么?如何用代码实现? 三.什么是插入排序?用代码如何实现? 四.什么是冒泡排序?用代码如何实现? 五.什么是斐波那契数列&#…...
支持RT-Thread最新版本的瑞萨RA2E1开发板终于要大展身手了
支持RT-Thread最新版本的瑞萨RA2E1开发板终于要大展身手了 熟悉RT-Thread和瑞萨MCU的朋友都知道,当前RT-Thread仓库的主线代码是不支持RA2E1这个BSP的。刚好,最近我在联合瑞萨推广一个叫《致敬未来的攻城狮计划》,使用的就是RA2E1开发板&…...
【C语言进阶】 12. 假期测评①
day01 1. 转义字符的判断 以下不正确的定义语句是( ) A: double x[5] {2.0, 4.0, 6.0, 8.0, 10.0}; B: char c2[] {‘\x10’, ‘\xa’, ‘\8’}; C: char c1[] {‘1’,‘2’,‘3’,‘4’,‘5’}; D: int y[53]{0, 1, 3, 5, 7, 9}; 【答案解析】 B 本…...
给程序加个进度条吧,1行Python代码,快速添加~
大家好,这里是程序员晚枫。 你在写代码的过程中,有没有遇到过以下问题? 已经写好的程序,想看看程序执行的进度? 在写代码批量处理文件的时候,如何显示现在处理到第几个文件了? 👆…...
常见的Keil5编译报错及其原因和解决方法
以下是几种常见的Keil5编译报错及其原因和解决方法: "Error: L6218E: Undefined symbol"(未定义符号错误) 这通常是由于缺少对应的库文件或者代码中有未声明的变量或函数引起的。解决方法是检查相应的库文件是否已正确添加到工程中…...
Django 实现瀑布流
需求分析 现在是 "图片为王"的时代,在浏览一些网站时,经常会看到类似于这种满屏都是图片。图片大小不一,却按空间排列,就这是瀑布流布局。 以瀑布流形式布局,从数据库中取出图片每次取出等量(7 …...
传输层协议----UDP/TCP
文章目录前言一、再谈端口号端口号的划分认识知名端口号(Well-Know Port Number)两个问题nestatpidof二、UDP协议UDP协议端格式UDP的特点面向数据报UDP的缓冲区UDP使用注意事项基于UDP的应用层协议二、TCP协议TCP协议段格式可靠性问题确认应答(ACK)机制流量控制六个标志位PSHUG…...
教你如何快速在Linux中找到某个目录中最大的文件
工作中经常会有查看某个目录下最大的文件的需求,比如在运维工作中,发现某个系统或功能不工作了,经排查发现是服务器空间满了…那么接下来就需要清理一下临时文件或者日志文件,或者其他不需要的文件,那么就会想要查看一…...
Java二叉树面试题讲解
Java二叉树面试题讲解🚗1.检查两颗树是否相同🚕2.另一颗树的子树🚙3.二叉树最大深度🚌4.判断一颗二叉树是否是平衡二叉树🚎5.对称二叉树🚓6.获取树中结点个数🚑7.判断一个树是不是完全二叉树&am…...
rancher2.6进阶之nfs动态创建pv配置
添加NFS client provisioner 动态提供K8s后端存储卷 1.1.前提说明 1.1.1.说明 NFS client provisioner 利用 NFS Server 给 Kubernetes 作为持久存储的后端,并且动态提供PV。 默认 rancher 2 的存储类中的提供者不包含NFS,需要手动添加;添加方式有两种: 1)从应用商店直接安…...
快速上手vue elementUI好看的登录界面
这是一个非常非常适合新手的vue登录界面,总体来说美观大气,axios那部分没有发,有需要的大家可以自己进行二次开发,继续编写。 用到了技术栈有 vue/cli 5.07 element-ui 2.15.9 适合入门级新手,展示下页面 emmm验证码…...
Vue趣味【Vue3+Element Plus+Canvas实现一个简易画板;支持导出为图片】
目录🌟前言🌟粉丝先看🌟创建Vue3项目🌟引入Element Plus🌟实现代码(详细注释)🌟写在最后🌟JSON包里写函数,关注博主不迷路🌟前言 哈喽小伙伴们&a…...
【Spring Cloud Alibaba】2.服务注册与发现(Nacos安装)
文章目录环境要求简介安装Nacos源码安装Docker安装数据库配置访问服务我们要搭建一个Spring Cloud Alibaba项目就绕不开Nacos,阿里巴巴提供的Nacos组件,可以提供服务注册与发现和分布式配置服务,拥有着淘宝双十一十几年的流量经验,…...
深度学习 Day28——利用Pytorch实现好莱坞明星识别
深度学习 Day28——利用Pytorch实现好莱坞明星识别 文章目录深度学习 Day28——利用Pytorch实现好莱坞明星识别一、前言二、我的环境三、前期工作1、导入依赖项设置GPU2、导入数据集3、划分数据集四、调用官方的VGG16模型五、训练模型1、编写训练函数2、编写测试函数3、设置动态…...
曼德勃罗集的 Three.js 实现
效果预览 经典的曼德勃罗集(Mandelbrot Set)分形渲染,配合动态缩放动画探索分形边界的无限细节。使用线性插值平滑着色,呈现出彩虹般的色彩过渡。 👉 点击查看《曼德勃罗集的》完整源码与效果演示 Shader 实现原理…...
超全 PS 快捷键汇总!新手一键收藏终身受用
对于经常使用Photoshop修图、做设计的小伙伴来说,最影响效率的从来不是创意不足,而是频繁点击菜单栏找功能。明明几秒就能完成的操作,却因为不熟悉工具,反复查找按钮、低效操作,大大拖慢修图节奏。熟练掌握PS快捷键&am…...
Ubuntu18.04 配置SNPE并将ONNX模型转为DLC
文章目录0.前提条件1.ONNX下载安装2.SNPE下载3.安装SNPE相关依赖4.设置环境变量5.将ONNX模型转为DLC0.前提条件 已安装好Anaconda和Python3.10 1.ONNX下载安装 ONNX官方链接: https://github.com/onnx/onnx#installation 根据官方指导,使用Conda进行安…...
银行借记卡月月有礼活动汇总(立减金达标锦囊) 解除微信支付账户限制
文章目录 引言 I 银行借记卡月月有礼活动 打开交通银行App搜索“月月有礼”参加活动 招行储蓄卡专享-月月支付抽锦鲤 浦发银行APP-“惠支付”专区-月月享十惠 II 微信支付账户限制通知 限制原因: 快进快出 提交资金来源举证资料 注意事项 III 云闪付发票抽奖 引言 【银行活动…...
数据史话|Dashboard 仪表板的进化史:从马车挡泥板,到企业战略工具(海外见解版)
今天我们来聊聊仪表板(Dashboard)的奇妙进化史。想象一下:马车前挡泥的木板,和你浏览器里满是 KPI、迷你图表、筛选器的仪表盘 —— 它们用的是同一个词,同一个核心使命,只是再也没有泥点子了。这就是仪表盘…...
同事悄悄告诉我,他月薪比我高1.8万,岗位一模一样。我去问HR,HR说,薪资保密。我才明白,保密的从来不是他的,是我的
最近看到一个帖子,有人说,他在公司干了三年,一直以为自己的薪资还算正常,直到有一天,关系不错的同事喝多了,把工资条拍给他看。两个人同一天入职,同一个岗位,同一个绩效评级。差了1.…...
Stacking集成在脑瘤影像分类中的临床价值与实操要点
1. 项目概述:为什么 stacking 不是“堆叠玩具”,而是脑瘤分类里最值得细嚼的那块硬骨头在医学影像AI落地的真实战场上,单模型准确率卡在92%就再也上不去,不是因为数据不够多,也不是因为GPU不够猛,而是因为不…...
Oracle替代之路:企业去O过程中常见的坑与避坑指南
📌 关键词:Oracle替代、国产数据库、去O、数据库迁移、信创、兼容性、高可用大家好!我是数据库小学妹 👋 最近发现一个有意思的现象:不管是金融、运营商还是政务单位,聊到数据库规划,三句话不离…...
Unity科幻隧道系统:模块化语法与三层材质架构
1. 这不是“贴图模型”的资源包,而是一套可复用的科幻空间语法系统你有没有试过在Unity里搭一个像《银翼杀手2049》里那种泛着冷光、布满管线与全息界面的通道?我去年做一款赛博朋克风格的VR导览项目时,就卡在这一步——买了三套号称“科幻隧…...
Taotoken多模型聚合能力在内容生成场景中的灵活应用
🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 Taotoken多模型聚合能力在内容生成场景中的灵活应用 对于新媒体运营和内容创作者而言,内容生成是核心工作之一。不同的…...
