Thread类的基本操作(JAVA多线程)
线程是操作系统中的概念,操作系统内核实现了线程这样的机制,并提供了一些API供外部使用。
JAVA中 Thread类 将系统提供的API又近一步进行了抽象和封装,所以如果想要使用多线程就离不开 Thread 这个类。
线程的创建(Thread类)
在JAVA中 创建线程可以有多种方法,这里简单介绍几种。
方法一:我们自己编写一个类使这个类继承自Thread类,然后重写里面的 run() 方法。
class MeThread extends Thread {//必须要实现这个方法,此方法是新线程执行的入口方法(告诉线程应该做什么)@Overridepublic void run() {System.out.println("这是新线程执行的任务");}}public static void main(String[] args) {//创建一个线程对象MeThread t = new MeThread();//调用系统API启动线程t.start();}
当我们运行程序之后就会执行 run() 方法中的打印操作。
new 一个Thread类只是创建出了一个线程,并不会调用系统API创建线程,只有当调用了start() 方法之后才会调用系统API在系统中创建出线程并启动。(此时不理解可以,因为在介绍isAlive()方法时会验证)
这个重写的 run() 方法可以理解为一个任务,这个方法会在线程启动时自动被调用执行,当线程执行完这个方法中的内容时该线程就会被销毁并且无法再次使用start()方法唤醒。
此时我们为了可以更好的呈现多线程并发编程的效果对上述代码进行了一些细微修改。
class MeThread extends Thread {//必须要重写这个方法,此方法是新线程执行的入口(告诉线程应该做什么)@Overridepublic void run() {//此处让这个新线程每隔0.1s执行一次打印操作while (true) {try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("这是创建的新线程执行的任务");}}}public static void main(String[] args) {MeThread t = new MeThread();//调用系统API启动线程t.start();//让主线程每隔0.1s执行一次打印操作while (true) {try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("主线程");}}
此时可以看到两个while循环是“同时执行的” ,每个线程都是一个独立的执行流。
代码执行之后可以看到程序在无规律的进行打印,其主要原因是系统对线程的调度是随机的。
方法二:我们自己编写一个类使实现 Runnable接口 然后重写里面的 run() 方法。
- Thread类 实现了 Runnable接口;
- Thread类 中的 run() 方法也是重写的 Runnable接口中的;
-
因为在 Thread类 中提供了一个这样的构造方法:
class MeRunnable implements Runnable{//必须要重写这个方法,此方法是新线程执行的入口@Overridepublic void run() {System.out.println("这是一个新线程执行的任务");}}public static void main(String[] args) {MeRunnable runnable = new MeRunnable();Thread t = new Thread(runnable);//调用系统API启动线程t.start();}
使用 Runnable 接口和直接继承 Thread 的区别就是:可以帮我们降低代码的耦合性也就是“解耦合”。
Runnable 它表示一个可以执行的任务 而它并不关心这个任务是啥,在哪里执行;这个任务也不一定和线程强相关,因为这个代码可能使用单线程、多线程还是不使用线程或者是用其他方法(例:线程池,协程……)执行都没有任何区别。
而此时使用 Runnable 就可以将这个任务单独的提取出来,这样就可以随时改变这个任务是使用什么方法进行执行(例如:后面如果不想用线程了就可以直接在main方法中进行调用)例:
class MeRunnable implements Runnable{@Overridepublic void run() {System.out.println("这是一个任务");}}public static void main(String[] args) {MeRunnable runnable = new MeRunnable();//此时不想使用线程执行这个任务runnable.run();}
方法三:使用匿名内部类。
public static void main(String[] args) {Thread t = new Thread(){@Overridepublic void run() {System.out.println("这是创建的新线程执行的任务");}};//调用系统API启动线程t.start();}
因为在 Thread类 中提供了一个这样的构造方法:
所以我们可以写成过这样:
public static void main(String[] args) {Thread t = new Thread(new Runnable(){@Overridepublic void run() {System.out.println("这是一个新线程执行的任务");}});//调用系统API启动线程t.start();}
方法四:因为Runnable接口是一个函数式接口,所以可以利用 Lambda表达式 来创建线程。
public static void main(String[] args) {Thread t = new Thread(() -> System.out.println("这是创建的新线程执行的任务"));//调用系统API启动线程t.start();}
除了观看控制台的输出结果来观察多线程之外,还可以使用JDK中带有的工具 jconsole 来更形象的观测:具体方法可以跳转这里http://t.csdnimg.cn/b8Wca
Thread类的一些常见构造方法
方法 | 说明 |
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用 Runnable 对象创建线程对象 |
Thread(String name) | 创建线程对象,并命名 |
Thread(Runnable target, String name) | 使用 Runnable 对象创建线程对象,并命名 |
Thread(ThreadGroup group,Runnable target) | 线程可以被用来分组管理,分好的组即为线程组,这 个目前我们了解即可 |
Thread类中的一些常见属性:
属性 | 获取方法 |
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
getId():
获取当前线程的 id
id 是线程的唯一身份标识,这个 id 是 JAVA 为这个线程分配的,并不是系统 API 分配的 ID ,更不是 PCB 的 ID 。
public static void main(String[] args) {Thread t = new Thread(() -> {while (true) {try {Thread.sleep(100);} catch (InterruptedException e) {//打印异常e.printStackTrace();}System.out.println("这是创建的新线程执行的任务");}});Thread t1 = new Thread(() -> {while (true) {try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("这是创建的新线程执行的任务");}});t.start();t1.start();System.out.println(t.getId());//获取并打印当前线程的IDSystem.out.println(t1.getId());//获取并打印当前线程的ID}
isDaemon()
判断当前线程是否为后台线程。
线程默认都是前台线程。
- 前台线程:只要该进程中还有前台线程未执行完,那么该进程就不会结束(前台线程会影响进程的结束与否);
- 后台线程:只要该进程中的前台线程都执行完毕,那么此时无论是否有未执行完的后台线程进程都会结束(后台线程不会影响进程的结束与否)。
public static void main(String[] args) {Thread t = new Thread(() -> {while (true) {try {Thread.sleep(100);} catch (InterruptedException e) {//打印异常e.printStackTrace();}System.out.println("这是创建的新线程执行的任务");}});t.start();System.out.println(t.isDaemon());//获取并打印当前线程是否为后台线程}
此时将上述代码改成后台线程。
public static void main(String[] args) {Thread t = new Thread(() -> {while (true) {try {Thread.sleep(100);} catch (InterruptedException e) {//打印异常e.printStackTrace();}System.out.println("这是创建的新线程执行的任务");}});//将当前线程设置为后台线程t.setDaemon(true);t.start();System.out.println(t.isDaemon());}
此时因为主线程(main线程)飞快的执行完了所以没有任何打印。
isAlive()
分别在线程启动前后打印判断线程是否存活(注意:线程对象存活时线程并不一定会存活)
public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {try {Thread.sleep(1000);} catch (InterruptedException e) {//打印异常e.printStackTrace();}System.out.println("新线程执行完毕");});System.out.println("线程启动前");System.out.println("线程是否存活"+t.isAlive());t.start();System.out.println("线程已启动");System.out.println("线程是否存活"+t.isAlive());Thread.sleep(2000);System.out.println("线程是否存活"+t.isAlive());}
根据结论可以得知当我们创建线程对象之后并不会调用系统API创建线程,只有当调用了start() 方法之后才会调用系统API在系统中创建出线程并启动。
打断线程
在JAVA中打断线程的方法是比较唯一的:本质上都是让run()方法尽快执行结束;而在C++中是有办法可以在线程执行过程中直接销毁该线程,但是这样做有个坏处比如这个线程在写文章时突然中断了那就会令这篇文章有头无尾,而在JAVA中就可以允许在此处进行一些收尾工作。
而现实中令run()方法迟迟无法结束的原因一般都是应为循环,所以只要 结束循环就可以让线程尽快执行完run()方法,从而达到打断线程的效果。
此处介绍两种方法:
第一种方法:
可以手动创建出一个标志位,用来控制 run() 方法中循环的终止条件。
//创建一个成员变量用来控制循环的终止条件,默认值为 false;private static boolean isQuit;public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {while(!isQuit) {System.out.println("新线程正在工作");try {Thread.sleep(100);} catch (InterruptedException e) {//打印异常e.printStackTrace(); }}System.out.println("新线程执行完毕");});t.start();Thread.sleep(500);isQuit = true;System.out.println("打断新线程");}
但是有以下两个问题:
- 需要手动创建标志位;
- 如果循环正处在sleep状态程序将不能进行及时的响应。
第二种方法:
在JAVA中默认就有一个标志位,我们可以利用JAVA中默认的标志位来进行快速结束run()方法的操作。
好处是这样我们就不用再单独创建一个变量,不用再思考变量捕获的问题了。
- interrupt() :该方法可以将线程中默认的标志位设置为true
- isInterrupt():判断对象关联的线程的标志位是否被设置,调用后不清除标志位。还可以使sleep()方法抛出InterruptedException异常,来强行中断sleep()方法。
利用这两个方法就可以实现线程的打断
public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {//Thread.currentThread()该方法是用来得到该线程的实例也就是t(哪个线程调用该方法就返回哪个线程的实例)//因为此时t还没有被创建所以不能写为t.isInterrupted()while(!Thread.currentThread().isInterrupted()) {System.out.println("新线程正在工作");try {Thread.sleep(1000);} catch (InterruptedException e) {//打印异常e.printStackTrace();}}System.out.println("新线程执行完毕");});t.start();Thread.sleep(5000);//设置标志位为truet.interrupt();System.out.println("打断新线程");}
结果可以看出来此时标志位确实被设置了,sleep()方法也抛出了异常,可是循环并没有被终止。
原因是sleep()方法在抛出异常之后会自动将标志位清除,而此引起的结果就和没有设置标志位是相同的。
而JAVA如此设计的原因其实就是扩大程序员的可操作空间,可以再sleep()方法抛出异常之后进行一些收尾工作。
public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {//Thread.currentThread()该方法是用来得到该线程的实例也就是t(哪个线程调用该方法就返回哪个线程的实例)//因为此时t还没有被创建所以不能写为t.isInterrupted()while(!Thread.currentThread().isInterrupted()) {System.out.println("新线程正在工作");try {Thread.sleep(1000);} catch (InterruptedException e) {//此处可以写一些收尾工作的代码break;}}System.out.println("新线程执行完毕");});t.start();Thread.sleep(500);//设置标志位为truet.interrupt();System.out.println("打断新线程");}
线程等待
再多线程的代码中由于线程的调度是随机的,所以也就会导致每个线程的结束时间也是无法预测的,而这种情况下就会使得在有些场景下代码出现BUG。
而线程等待就是让一个线程来等待另一个线程执行结束,本质上就是来控制线程结束的顺序。
join()
实现线程等待的效果,让一个线程阻塞等待另一个线程执行结束之后再执行。
- 等的线程:在哪个线程中调用 join 方法,哪个线程就阻塞等待;
- 被等的线程:调用的哪个线程对象的 join 方法,哪个线程就是被等的线程,当这个线程执行完毕等的线程才会执行。
我们创建一个线程 t 让这个线程每隔一秒打印一次数据,让主线程等待该线程。
Thread t = new Thread(()->{for (int i = 0; i < 4; i++) {System.out.println("t线程执行中");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}
});
t.start();
System.out.println("等待开始");
t.join();
System.out.println("等待结束");
注意:如果 t 线程已经执行结束,此时再调用 join 就会直接返回执行,不会发生阻塞等待。
join(等待时间)
上面的 join() 方法是一种“死等”的方法,只要被等待的线程不结束,那么就会一直等待下去。
但是一般情况下我们并不会死等而是等待超过了一定时间之后就不会再继续等待了,因为没有意义。
- join(long millis) 最多等待 millis 毫秒
- join(long millis, int nanos) 和上面的方法一样 就是时间精度更高,精确到了纳秒
休眠线程
- sleep(long millis) 让线程休眠 millis 毫秒;
- sleep(long millis,int nanos) 和上面的功能一样,就是精度更高。
但是下面的这一种其实意义不大,因为 sleep() 本身就存在一定的误差,并不是你写 sleep(1000) 就真的刚好等精确 1000 ms ,它还有一个调度的开销。系统会按照 1000 这个时间来休眠线程,当时间到了之后,系统会唤醒该线程(阻塞 -> 就绪),而且并不是线程进入就绪状态就能立即进入CPU执行。
//获取系统当前时间戳
long a = System.currentTimeMillis();
Thread.sleep(1000);
//获取系统当前时间戳
long b = System.currentTimeMillis();
System.out.println("时间:"+(b - a)+" ms");
而且每次运行之后结果都是不同的。
线程的状态
JAVA中线程的所有状态都存储在一个枚举类型中,Thread.State :
for (Thread.State str:Thread.State.values()) {System.out.println(str);
}
可以通过上述代码来打印所有的线程状态
通过 getState() 方法可以获取线程的状态
- NEW:Thread对象已经有了,但还没有调用 start() 方法;
Thread t = new Thread(()->{});
System.out.println(t.getState());
- RUNNABLE:就绪状态(线程已经在CPU上执行了或者排队准备执行)
Thread t = new Thread(()->{while(true) {}
});
t.start();
System.out.println(t.getState());
- BLOCKED:阻塞,由于锁竞争导致的阻塞
Object lock1 = new Object();
Object lock2 = new Object();Thread t1 = new Thread(()->{synchronized(lock1) {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized(lock2) {}}
});
Thread t2 = new Thread(()->{synchronized(lock2) {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized(lock1) {}}
});
t1.start();
t2.start();
//让主线程等待 2 秒
Thread.sleep(2000);
//此时t1和t2两个线程会因为互相争对方的锁,而导致死锁
System.out.println(t1.getState());
System.out.println(t2.getState());
- WAITING:阻塞由 wait 这种不固定时间的方式引起的阻塞
Object lock1 = new Object();Thread t1 = new Thread(()->{synchronized(lock1) {try {//调用wait方法让线程阻塞lock1.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}
});t1.start();
Thread.sleep(1000);
System.out.println(t1.getState());
- TIMED_WAITING:由 sleep 这种固定时间限制的方式引起的阻塞
Thread t1 = new Thread(()->{try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}
});t1.start();
Thread.sleep(1000);
System.out.println(t1.getState());
- TERMINATED:Thread对象还在,可是线程已经没了
Thread t1 = new Thread(()->{});t1.start();
Thread.sleep(1000);
System.out.println(t1.getState());
相关文章:

Thread类的基本操作(JAVA多线程)
线程是操作系统中的概念,操作系统内核实现了线程这样的机制,并提供了一些API供外部使用。 JAVA中 Thread类 将系统提供的API又近一步进行了抽象和封装,所以如果想要使用多线程就离不开 Thread 这个类。 线程的创建(Thread类) 在JAVA中 创建…...
Redis 的三种部署模式
提前叠个 buff:这个文章不涉及图(画起来比较麻烦),只是记录我的胡思乱想。 redis 从单点 -> 集群总共有三个部署模式:单机模式,主从模式,哨兵模式,集群模式 单机模式 新手入门模…...
【ArcGIS Pro二次开发】(73):使用NPOI库操作Excel
NPOI是一个开源的C#读写Excel、WORD等微软OLE2组件文档的项目。 NPOI可以在没有安装Office的情况下对Word或Excel文档进行读写操作。 相较于之前使用的Microsoft.Office.Interop.Excel,已经感觉到的优势,一是读写速度较快,虽然小数据量的读…...

python获取电脑所连接的wifi密码
电脑连接wifi后,很难直观地看到当前连接wifi的密码,需要借助命令行公管局才可以查看到相关信息。 CMD命令 查看所有已保存的wifi配置信息 netsh wlan show profiles查看某一个wifi的详细信息,需要输入wifi名称来查询 netsh wlan show pro…...

动态壁纸软件Live Wallpaper HD mac中文版功能特色
Live Wallpaper HD mac提供了一系列美丽的主题场景,将为您的桌面增添活力。从城市景观、日落到遥远的星系,每个屏幕都有特别的触感,可以定制您的天气小部件和时钟样式,并使用您喜爱的图片创建您自己的个性化壁纸。 Living Wallpap…...
Spring Boot 配置主从数据库实现读写分离
一、前言 现在的 Web 应用大都是读多写少。除了缓存以外还可以通过数据库 “主从复制” 架构,把读请求路由到从数据库节点上,实现读写分离,从而大大提高应用的吞吐量。 通常,我们在 Spring Boot 中只会用到一个数据源࿰…...

【MongoDB】索引 - 单字段索引
MongoDB支持在集合文档中的任意字段上创建索引,默认情况下所有的集合都有一个_id字段的索引,用户和应用可以新增索引用于查询和操作。 一、准备工作 这里准备一些学生数据 db.students.insertMany([{ _id: 1, name: "张三", age: 20, clas…...
评估大型语言模型:综述
论文地址:https://arxiv.org/pdf/2310.19736v2.pdf github: tjunlp-lab/awesome-llms-evaluation-… 发表团队:Tianjin University 摘要 将LLM评估划分三点:知识和能力评估、一致性评估和安全性评估。特定领域化评估benchmark评…...

设计模式之工厂模式(Factory)
任何可以产生对象的方法或类,都可以称为工厂。 下面的代码定义了Car这种交通工具: public class Car {public void go() {System.out.println("Car go wuwuwuwuw....");} }然后在main函数里面想要调用调用Car的go方法,就需要new一个car对象&…...

2023/11/6 JAVA学习
处理编译异常的两种方法 第一种 第二种 问题比较容易犯,编译时异常...

Android-JobService
JobService 这里写目录标题 JobService一、API详解1 onStartJob2 onStopJob 二、onStartJob | onStopJob 返回值case 1case 2case 3 ref: 深入理解JobScheduler与JobService的使用 - 掘金 (juejin.cn) (28条消息) JobService的使用介绍_TechMerger的博客-CSDN博客 (28条消息) J…...

GraphQL入门与开源的GraphQL引擎Hasura体验
背景 Hasura 是一个开源的 GraphQL 引擎,它可以帮助开发人员快速构建和部署现代应用程序的后端。它提供了一个自动化的 GraphQL API ,可以直接连接到现有的数据库,并提供实时数据推送和订阅功能。 Hasura 团队总部位于印度。 下载安装 脚本…...

Javascript知识点详解:this关键字的指向问题
目录 this 关键字 涵义 实质 使用场合 使用注意点 避免多层 this 避免数组处理方法中的 this 避免回调函数中的 this 绑定 this 的方法 Function.prototype.call() Function.prototype.apply() Function.prototype.bind() 箭头函数中的 this this 关键字 涵义 t…...

数据库的备份和恢复
备份:完全备份,增量备份 完全备份:将整个数据库完整的进行备份 增量备份:在完全备份基础的之上,对后续新增的内容进行备份 备份的需求 1生产环境中,数据的安全性至关重要,任何数据都可能产生非…...
DS图—图非0面积/bfs【数据结构】
DS图—图非0面积 题目描述 编程计算由"1"围成的下列图形的面积。面积计算方法是统计"1"所围成的闭合曲线中"0"点的数目。如图所示,在10*10的二维数组中,"1"围住了15个点,因此面积为15。 提示&…...

Wnmp服务安装并结合内网穿透实现公网远程访问——“cpolar内网穿透”
文章目录 前言1.Wnmp下载安装2.Wnmp设置3.安装cpolar内网穿透3.1 注册账号3.2 下载cpolar客户端3.3 登录cpolar web ui管理界面3.4 创建公网地址 4.固定公网地址访问 前言 WNMP是Windows系统下的绿色NginxMysqlPHP环境集成套件包,安装完成后即可得到一个Nginx MyS…...

2023版Pycharm关闭一直显示closing project,正在关闭项目
点击 帮助 下的 查找操作 英文版为 Help 下的 Find Action 输入 Registry 禁用 ide.await.scope.completion 即可 PS:按 Ctrl F 输入可以快速检索...

Gradle笔记 二 Gradle的基础Groovy
学习Groovy的必要性 首先Gradle是由Groovy写成的,而且构建脚本的语法都遵循Groovy的语法,所以要学好Gradle的前提是要基本了解Groovy的语法。 Groovy 简介 在某种程度上,Groovy可以被视为Java的一种脚本化改良版,Groovy也是运行在JVM上&am…...

浅谈剩余电流动作继电器在电动伸缩门的应用
摘 要:随着时代的发展,越来越多的小区、厂区、园区和学校等场所的大门安装了电动伸缩门,几乎可以说随处可见。电动伸缩门是一种长期在户外使用的设备,工作电压为220 V(过去也有380 V),其电机是处…...
stable diffusion安装踩坑之clip安装、git报错
clip本地安装环境链接问题 本节主要记录一下在windows安装stable diffusion时,clip脚本安装不上,本地安装时如何链接到当前库的问题 首先,在脚本安装clip不成功时,脚本会输出一个commend指令,复制到浏览器就可以很快…...

UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...
CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型
CVPR 2025 | MIMO:支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题:MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者:Yanyuan Chen, Dexuan Xu, Yu Hu…...

聊聊 Pulsar:Producer 源码解析
一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台,以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中,Producer(生产者) 是连接客户端应用与消息队列的第一步。生产者…...

如何在最短时间内提升打ctf(web)的水平?
刚刚刷完2遍 bugku 的 web 题,前来答题。 每个人对刷题理解是不同,有的人是看了writeup就等于刷了,有的人是收藏了writeup就等于刷了,有的人是跟着writeup做了一遍就等于刷了,还有的人是独立思考做了一遍就等于刷了。…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...

技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...

Yolov8 目标检测蒸馏学习记录
yolov8系列模型蒸馏基本流程,代码下载:这里本人提交了一个demo:djdll/Yolov8_Distillation: Yolov8轻量化_蒸馏代码实现 在轻量化模型设计中,**知识蒸馏(Knowledge Distillation)**被广泛应用,作为提升模型…...

[大语言模型]在个人电脑上部署ollama 并进行管理,最后配置AI程序开发助手.
ollama官网: 下载 https://ollama.com/ 安装 查看可以使用的模型 https://ollama.com/search 例如 https://ollama.com/library/deepseek-r1/tags # deepseek-r1:7bollama pull deepseek-r1:7b改token数量为409622 16384 ollama命令说明 ollama serve #:…...
关于uniapp展示PDF的解决方案
在 UniApp 的 H5 环境中使用 pdf-vue3 组件可以实现完整的 PDF 预览功能。以下是详细实现步骤和注意事项: 一、安装依赖 安装 pdf-vue3 和 PDF.js 核心库: npm install pdf-vue3 pdfjs-dist二、基本使用示例 <template><view class"con…...

android RelativeLayout布局
<?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"android:gravity&…...