【学习日记2023.4.12】之Thread的实现方式和解决线程安全问题的三种办法以及线程通讯问题和Thread的生命周期

本文主要是介绍【学习日记2023.4.12】之Thread的实现方式和解决线程安全问题的三种办法以及线程通讯问题和Thread的生命周期,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 1. 多线程相关概念
  • 2. 多线程的实现方式
    • 2.1 继承Thread
      • 步骤
      • 注意事项和优缺点
      • 内存原理
    • 2.2 实现Runnable接口
      • 步骤
      • 优缺点
    • 2.3 实现Runnable接口(匿名内部类)
    • 2.4 实现Callable接口
      • 问题
      • 步骤
      • 注意事项和优缺点
    • 2.5 三种方式的使用选择
  • 3. Thread常用方法
  • 4. 线程安全问题
    • 4.1 案例
    • 4.2 问题
    • 4.3 如何理解共享数据
  • 5. 解决线程安全问题
    • 5.1 同步代码块
      • 格式
      • 作用
      • 锁对象唯一
    • 5.2 同步方法
    • 5.3 Lock
    • 5.4 线程安全问题小结
  • 6. 线程间通讯
    • 6.1 线程间通讯
      • 线程通讯的方法(等待,唤醒)
    • 6.2 线程通讯的常见模型
      • 生产者消费模型
      • 案例
  • 7. 线程的生命周期
  • 8. 总结

1. 多线程相关概念

  • 并发和并行
    • 并发:在同一时刻,有多个指令在CPU单个核心上 交替 执行
    • 并行:在同一时刻,有多个指令在CPU多个核心上 同时 执行
  • 进程和线程
    • 进程:操作系统中正在运行的一个应用程序。
    • 线程:应用程序中做的事情。比如:360软件中的杀毒,扫描木马,清理垃圾
  • 线程
    • 单线程:程序中如果只有一条执行流程,那这个程序就是单线程的程序。
    • 多线程:程序中如果有多条执行流程,那这个程序就是多线程的程序。
  • 好处和应用场景
    • 好处
      • 提高CPU的利用率,和处理任务的能力
    • 应用场景
      • 桌面应用程序
      • web服务器应用程序
        请添加图片描述

2. 多线程的实现方式

2.1 继承Thread

步骤

  • 定义一个类,继承Thread类
  • 重写run方法
  • 创建对象
  • 调用start()方法,启动线程
/*演示Java语言创建多线程方式一:继承Thread类注意事项:直接调用run方法,不会启动线程start():1.启动线程2.在新启动的线程中,调用run方法继承Thread类的优缺点:优点:代码书写简单缺点:已经继承了一个类,无法再继承其他类,降低了程序的扩展性*/
public class Demo1 {public static void main(String[] args) {//3.创建线程对象final MyThread myThread = new MyThread();//4.调用start方法,启动新的线程myThread.start();for (int i = 1; i <= 5; i++) {System.out.println("主方法中的代码执行了" + i);}}
}//1.定义类继承Thread类
class MyThread extends Thread{//2.重写run方法@Overridepublic void run() {//run方法中的内容,启动线程后,执行的任务for (int i = 1; i <= 5; i++) {System.out.println("新县城执行了" + i);}}
}

注意事项和优缺点

  • 注意事项
    • 启动线程必须是调用start方法,而不是直接调用run方法
      • 直接调用run方法,不会启动线程
      • start方法的作用
        • 启动线程
        • 并执行run方法
  • 优点: 代码书写简单
  • 缺点: 自定义类继承Thread,无法继续继承其他类,不利于功能的扩展

内存原理

  • 官网:https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-2.html#jvms-2.5.2
  • 每个Java虚拟机线程都有一个与线程同时创建的专用Java虚拟机栈。
  • 内存图
    请添加图片描述

2.2 实现Runnable接口

步骤

  • 定义任务类,实现Runnable接口
  • 重写run方法
  • 创建任务对象
  • 把任务对象交给一个线程对象处理
public class Demo2 {public static void main(String[] args) {//3.创建线程对象MyRunnable r = new MyRunnable();//4.把任务类的对象,交给线程类Thread t = new Thread(r);//5.通过线程对象(Thread),调用start方法,启动新的线程t.start();for (int i = 1; i <= 5; i++) {System.out.println("主方法中的代码执行了" + i);}}
}//1.自定义类(任务类)实现Runnable接口
class MyRunnable implements Runnable{//2.重写run方法@Overridepublic void run() {//线程启动后要执行的任务for (int i = 1; i <= 5; i++) {System.out.println("新线程执行了" + i);}}
}

优缺点

  • 优点: 任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强
  • 缺点:需要多一个任务对象

2.3 实现Runnable接口(匿名内部类)

public class Demo3 {public static void main(String[] args) {//匿名内部类的实现方式Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {//线程启动后要执行的任务for (int i = 1; i <= 5; i++) {System.out.println("新线程一执行了" + i);}}});t1.start();//lambda表达式  前提:函数式接口(接口中只有一个抽象方法)Thread t2 = new Thread(() ->{//线程启动后要执行的任务for (int i = 1; i <= 5; i++) {System.out.println("新线程二执行了" + i);}});t2.start();for (int i = 1; i <= 5; i++) {System.out.println("主方法中的代码执行了" + i);}}
}

2.4 实现Callable接口

问题

  • 问题: 假如线程执行完毕后有一些数据需要返回,前两种方式重写的run方法均不能返回结果
  • 解决: JDK5.0提供了Callable接口和FutureTask类来实现(多线程的第三种创建方式)
  • 第三种创建方式的优点: 可以返回线程执行完毕后的结果

步骤

  • 创建任务对象
    • 定义一个类实现Callable接口,重写call方法(call方法中的内容,就是线程开启后要执行的任务,并且该方法有返回值)
    • 把Callable类型的对象交给FutureTask
FutureTask构造方法说明
public FutureTask<>(Callable call)把Callable对象交给成FutureTask
  • FutureTask交给交给Thread
  • 调用Thread对象的start方法启动线程
  • 线程执行完毕后,通过FutureTask对象的get方法获取线程任务的执行结果
FutureTask提供的方法说明
public V get() throws Exception获取线程执行call方法返回的结果
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/*注意事项及其优缺点:get获取线程执行完成结束后的结果如果线程没结束,死等(阻塞),所以get方法一定要写在start()之后优点:能够获取线程结束后的结果缺点:编码复杂*/
public class Demo1 {public static void main(String[] args) throws ExecutionException, InterruptedException {//3.创建Callable实现类对象MyCallable c = new MyCallable();//4.Callable实现类对象 交给FutureTaskFutureTask<String> f1 = new FutureTask<>(c);//lambda表达式FutureTask<String> f2 = new FutureTask(()->{for (int i = 1; i <= 50; i++) {System.out.println("B向女孩表白的第" + i + "次");}return "喔同意了!";});//5.FutureTask交给ThreadThread t1 = new Thread(f1);Thread t2 = new Thread(f2);t1.start();//开启线程t2.start();//开启线程//6.FutureTask调用get方法获取call方法的返回值final String rusult1 = f1.get();//等第一个线程结束后的结果final String rusult2 = f2.get();//等第二个线程结束后的结果System.out.println("女孩回应A:" + rusult1);System.out.println("女孩回应B:" + rusult2);}
}//1.定义类实现Callable接口
//Callable接口中的泛型,和线程结束后得到结果的类型一致
class MyCallable implements Callable{//2.重写call方法@Overridepublic String call() throws Exception {//是线程开启后执行的任务,并且带有返回值的//模拟向女孩表白for (int i = 1; i <= 50; i++) {System.out.println("A向女孩表白的第" + i + "次");}return "其实你是个好人!";}
}

注意事项和优缺点

  • 注意事项: FutureTask中的get方法会等待线程运行结束,再获取结果.所以不能写到线程开启start方法之前
  • 优点: 实现接口,可以继续继承其他类、实现其他接口,扩展性强;可以在线程执行完毕后去获取线程执行的结果
  • 缺点: 编码复杂

2.5 三种方式的使用选择

  • 前两种方式,哪种方便用哪种
  • 如果需要获取线程执行后的返回结果,只能用第三种方式

3. Thread常用方法

Thread提供的常用方法说明
public void start()启动线程
public String getName()获取线程的名字,线程名字默认Thread-编号
public void setName(String name)为线程设置名字
public static Thread currentThread()获取当前执行的线程对象
public static void sleep(long time)让当前线程的线程休眠多少毫秒后,再继续执行
public final void join()让调用当前这个方法的线程先执行完
Thread提供的构造方法说明
public Thread(String name)指定线程名字
public Thread(Runnable target)封装Runnable对象成为线程对象
public Thread(Runnable target,String name)封装Runnable对象成为线程对象,并指定线程名字
public class Demo1 {public static void main(String[] args) throws ExecutionException, InterruptedException {//3.创建Callable实现类对象MyCallable c = new MyCallable();//4.Callable实现类对象 交给FutureTaskFutureTask<String> f1 = new FutureTask<>(c);//lambda表达式FutureTask<String> f2 = new FutureTask(()->{for (int i = 1; i <= 50; i++) {System.out.println(Thread.currentThread().getName() + "向女孩表白的第" + i + "次");}return "喔同意了!";});//5.FutureTask交给ThreadThread t1 = new Thread(f1,"男孩A");Thread t2 = new Thread(f2);t2.setName("男孩B");t1.start();//开启线程t1.join();//让此线程执行完,再执行其他线程t2.start();//开启线程//6.FutureTask调用get方法获取call方法的返回值final String rusult1 = f1.get();//等第一个线程结束后的结果final String rusult2 = f2.get();//等第二个线程结束后的结果System.out.println("女孩回应" + t1.getName() + ":" + rusult1);System.out.println("女孩回应" + t2.getName() + ":" + rusult2);}
}//1.定义类实现Callable接口
//Callable接口中的泛型,和线程结束后得到结果的类型一致
class MyCallable implements Callable{//2.重写call方法@Overridepublic String call() throws Exception {//是线程开启后执行的任务,并且带有返回值的//模拟向女孩表白for (int i = 1; i <= 50; i++) {System.out.println(Thread.currentThread().getName() + "向女孩表白的第" + i + "次");}return "其实你是个好人!";}
}

4. 线程安全问题

4.1 案例

  • 需求: 电影院,一共100张票,三个窗口【同时】售票,用多线程模拟电影院卖票
public class Demo1 {public static void main(String[] args) {MyThread t1 = new MyThread("窗口一");t1.start();MyThread t2 = new MyThread("窗口二");t2.start();MyThread t3 = new MyThread("窗口三");t3.start();}
}class MyThread extends Thread{private static int ticket = 100;//总票数public MyThread() {}public MyThread(String name) {super(name);}//如果父类(接口)中定义的方法声明上没有 throws//重写的方法就不能使用throws来处理异常,不能使用try…catch@Overridepublic void run() {//线程开启后执行的任务,卖票//不断的卖票,死循环while (true){if (ticket <= 0){break;}else {try {Thread.sleep(100);//模拟出票延迟时间} catch (InterruptedException e) {e.printStackTrace();}ticket--;System.out.println(getName() + "开始卖票了,还剩:【" + ticket + "】张票");}}}
}

4.2 问题

  • 模拟售票时间,卖票时 线程休眠100毫秒
  • 现象: 出现负数票,重复票
  • 原因: 多个线程操作了共享资源
  • 什么是线程安全问题: 多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题。

4.3 如何理解共享数据

  • 成员变量,多个线程操作同一个成员变量

5. 解决线程安全问题

5.1 同步代码块

格式

synchronized(锁对象) {
// 操作共享数据的代码
}

作用

  • 底层:线程自动获得锁,释放锁
  • 当有线程进入同步代码块时,锁住(该线程获得锁)
  • 当同步代码块中没有线程执行,锁打开(释放锁)
  • 作用:保证同时只有一个线程在操作共享数据
public class Demo1 {public static void main(String[] args) {MyThread t1 = new MyThread("窗口1");t1.start();MyThread t2 = new MyThread("窗口2");t2.start();MyThread t3 = new MyThread("窗口3");t3.start();}
}
class MyThread extends Thread{private static int ticket = 100;//总票数private static Object obj = new Object();//锁对象必须要求是唯一的public MyThread() {}public MyThread(String name) {super(name);}//如果父类(接口)中定义的方法声明上没有 throws//重写的方法就不能使用throws来处理异常,不能使用try…catch@Overridepublic void run() {//线程开启后执行的任务,卖票//不断的卖票,死循环while (true){synchronized (obj) {if (ticket <= 0){break;}else {ticket--;System.out.println(getName() + "开始卖票了,还剩:【" + ticket + "】张票");}}try {Thread.sleep(100);//模拟出票延迟时间} catch (InterruptedException e) {e.printStackTrace();}}}
}

锁对象唯一

  • 注意: 如果锁对象不唯一,相当于没有锁
private static Object obj = new Object();//锁对象必须要求是唯一的

5.2 同步方法

  • synchronized修饰普通方法:锁对象是this
public synchronized void method(){// 操作共享数据的代码
}
  • synchronized修饰静态方法:锁对象是类名.class
public static synchronized void method(){// 操作共享数据的代码
}
public class Demo2 {public static void main(String[] args) {//1.创建Runnable对象MyRunnable m = new MyRunnable();//只创建了一个实例对象,故MyRunnable类中成员ticket可不为static//2.创建三个线程对象Thread t1 = new Thread(m,"12306官网");t1.start();Thread t2 = new Thread(m,"携程官网");t2.start();Thread t3 = new Thread(m,"飞猪官网");t3.start();}
}class MyRunnable implements Runnable{private int ticket = 100;//总票数
//    private int count = 0;@Overridepublic void run() {while (true){boolean b = getTicketResult();if (b){break;}}}//如果改方法返回true,表示票卖完了//如果改方法返回false,表示票还有剩余public synchronized boolean getTicketResult(){Thread thread = Thread.currentThread();if (ticket == 0){return true;}else {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}ticket--;
//            count++;System.out.println(thread.getName() + "开始卖票了,还剩" + ticket + "张票");//"已经卖了" + count + "张票,还剩"return false;}}
}

5.3 Lock

  • JDK5提供的锁对象,显示的加锁和释放锁
    • Lock为接口
    • ReentrantLock为具体的实现类
  • 格式
Lock lock = new ReentrantLock();
lock.lock(); // 显式加锁
// 操作共享数据的代码
lock.unlock(); // 显式释放锁
  • 注意:
    • ReentrantLock对象要唯一
    • unlock() 要放到finally代码块中保证一定能释放锁
public class Demo3 {public static void main(String[] args) {MyThread3 t1 = new MyThread3("携程官网");t1.start();MyThread3 t2 = new MyThread3("同程官网");t2.start();MyThread3 t3 = new MyThread3("飞猪官网");t3.start();}
}class MyThread3 extends Thread{private static int ticket = 100;//总票数static Lock lock = new ReentrantLock();//static保证lock锁的唯一性private int count = 0;//各个窗口售票张数public MyThread3() {}public MyThread3(String name) {super(name);}//如果父类(接口)中定义的方法声明上没有 throws//重写的方法就不能使用throws来处理异常,不能使用try…catch@Overridepublic void run() {//线程开启后执行的任务,卖票//不断的卖票,死循环while (true){try {lock.lock();// 显示加锁,获得锁if (ticket == 0){break;}else {try {Thread.sleep(10);//模拟出票延迟时间} catch (InterruptedException e) {e.printStackTrace();}ticket--;count++;System.out.println(getName() + "已经卖了" + count + "张票,还剩:【" + ticket + "】张票");}} catch (Exception e) {e.printStackTrace();} finally {lock.unlock(); // 显示的释放锁}}}
}

5.4 线程安全问题小结

请添加图片描述

6. 线程间通讯

6.1 线程间通讯

  • 当多个线程共同操作共享资源时,线程间通过某种方式互相告知自己的状态,以相互协调,避免无效的资源竞争

线程通讯的方法(等待,唤醒)

  • 注意: 下列方法要使用当前同步锁对象进行调用。
Object中的方法说明
void wait()让当前线程等待并释放所占锁,直到其他线程调用notify()方法或者notifyAll()方法
void notify()唤醒正在等待的单个线程
void notifyAll()唤醒正在等待的所有线程
public class Demo1 {private static Object obj = new Object();public static void main(String[] args) {Thread t1 = new Thread(() ->{synchronized (obj) {Thread thread = Thread.currentThread();//获取当前线程对象System.out.println(thread.getName() + "开始执行了");try {obj.wait();//当前线程在这等待System.out.println(thread.getName() + "结束了");} catch (InterruptedException e) {e.printStackTrace();}}});t1.setName("线程1");t1.start();Thread t3 = new Thread(() ->{synchronized (obj) {Thread thread = Thread.currentThread();//获取当前线程对象System.out.println(thread.getName() + "开始执行了");try {obj.wait();//当前线程在这等待System.out.println(thread.getName() + "结束了");} catch (InterruptedException e) {e.printStackTrace();}}});t3.setName("线程3");t3.start();//try {Thread.sleep(1000);//睡眠确保线程2在线程1和3启动后才行,防止有没有启动导致无法唤醒睡眠} catch (InterruptedException e) {e.printStackTrace();}Thread t2 = new Thread(() ->{synchronized (obj) {Thread thread = Thread.currentThread();//获取当前线程对象System.out.println(thread.getName() + "开始执行了");
//                obj.notify();//唤醒任意等待的一个线程obj.notifyAll();//唤醒所有线程System.out.println(thread.getName() + "结束了");}});t2.setName("线程2");t2.start();}
}

6.2 线程通讯的常见模型

生产者消费模型

  • 生产者线程负责生产数据
  • 消费者线程负责消费生产者产生的数据

案例

  • 厨师线程(生产者)不断的做包子:
    • 当盘子装不下了,唤醒吃货,自己等待
  • 吃货线程(消费者)不断的吃包子
    • 当盘子空了,唤醒厨师,自己等待
  • 桌子上放有盘子(容器)
public class Desk {//盘子容器List<String> list = new ArrayList<>();//向容器中添加包子public synchronized void put() {//判断容器是不是满了if (list.size() == 10){//满了//wait notify notifyAllthis.notifyAll();//要先唤醒其他线程try {this.wait();//再等待} catch (InterruptedException e) {e.printStackTrace();}}else {//没有满Thread thread = Thread.currentThread();list.add("包子");System.out.println(thread.getName() + "做了一个包子,目前包子数为:" + list.size());}}//从容器中取包子public synchronized void gut() {//判断容器是否为空if (list.size() == 0){//空的this.notifyAll();try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}else {//有包子Thread thread = Thread.currentThread();list.remove(0);//删除集合中的包子System.out.println(thread.getName() + "吃了一个包子,目前包子数为:" + list.size());}}
}
public class Demo1 {public static void main(String[] args) {//0.创建桌子的对象Desk desk = new Desk();//1.创建厨师线程Thread chef1 = new Thread(() -> {while (true){desk.put();try {Thread.sleep(400);} catch (InterruptedException e) {e.printStackTrace();}}});chef1.setName("厨师1");chef1.start();Thread chef2 = new Thread(() -> {while (true){desk.put();try {Thread.sleep(700);} catch (InterruptedException e) {e.printStackTrace();}}});chef2.setName("厨师2");chef2.start();Thread chef3 = new Thread(() -> {while (true){desk.put();try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}}});chef3.setName("厨师3");chef3.start();//2.创建吃货线程Thread foodie1 = new Thread(() -> {while (true){desk.gut();try {Thread.sleep(600);} catch (InterruptedException e) {e.printStackTrace();}}});foodie1.setName("吃货1");foodie1.start();Thread foodie2 = new Thread(() -> {while (true){desk.gut();try {Thread.sleep(300);} catch (InterruptedException e) {e.printStackTrace();}}});foodie2.setName("吃货2");foodie2.start();}
}

7. 线程的生命周期

  • 线程的六种状态和相互转换
    请添加图片描述

  • Thread.State 内部枚举类

状态枚举项状态说明阶段
NEW新建线程刚被创建,但是并未启动。
RUNNABLE可运行线程已经调用了start()
BLOCKED阻塞线程在执行的时候未竞争到锁对象,则该线程进入Blocked状态;
WAITING无限等待一个线程进入Waiting状态,另一个线程调用notify或者notifyAll方法才能够唤醒
TIMED_WAITING计时等待同waiting状态,有几个方法(sleep,wait)有超时参数,调用他们将进入Timed Waiting状态
TERMINATED结束因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。
  • 线程对象调用getState()方法可以获取线程的状态
public class Demo {public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {System.out.println("线程创建了");try {Thread.sleep(1000);  // t线程睡1秒钟} catch (InterruptedException e) {e.printStackTrace();}});System.out.println(t1.getState()); // 什么状态?  NEW 新建状态t1.start();System.out.println(t1.getState()); // 什么状态?  RUNNABLE 可运行状态// 主线程睡500毫秒Thread.sleep(500);System.out.println(t1.getState()); // 什么状态?  TIMED_WAITING 计时等待}
}

8. 总结

请添加图片描述

这篇关于【学习日记2023.4.12】之Thread的实现方式和解决线程安全问题的三种办法以及线程通讯问题和Thread的生命周期的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/252766

相关文章

Linux线程之线程的创建、属性、回收、退出、取消方式

《Linux线程之线程的创建、属性、回收、退出、取消方式》文章总结了线程管理核心知识:线程号唯一、创建方式、属性设置(如分离状态与栈大小)、回收机制(join/detach)、退出方法(返回/pthr... 目录1. 线程号2. 线程的创建3. 线程属性4. 线程的回收5. 线程的退出6. 线程的取消7.

Linux下进程的CPU配置与线程绑定过程

《Linux下进程的CPU配置与线程绑定过程》本文介绍Linux系统中基于进程和线程的CPU配置方法,通过taskset命令和pthread库调整亲和力,将进程/线程绑定到特定CPU核心以优化资源分配... 目录1 基于进程的CPU配置1.1 对CPU亲和力的配置1.2 绑定进程到指定CPU核上运行2 基于

golang程序打包成脚本部署到Linux系统方式

《golang程序打包成脚本部署到Linux系统方式》Golang程序通过本地编译(设置GOOS为linux生成无后缀二进制文件),上传至Linux服务器后赋权执行,使用nohup命令实现后台运行,完... 目录本地编译golang程序上传Golang二进制文件到linux服务器总结本地编译Golang程序

Linux下删除乱码文件和目录的实现方式

《Linux下删除乱码文件和目录的实现方式》:本文主要介绍Linux下删除乱码文件和目录的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux下删除乱码文件和目录方法1方法2总结Linux下删除乱码文件和目录方法1使用ls -i命令找到文件或目录

SpringBoot+EasyExcel实现自定义复杂样式导入导出

《SpringBoot+EasyExcel实现自定义复杂样式导入导出》这篇文章主要为大家详细介绍了SpringBoot如何结果EasyExcel实现自定义复杂样式导入导出功能,文中的示例代码讲解详细,... 目录安装处理自定义导出复杂场景1、列不固定,动态列2、动态下拉3、自定义锁定行/列,添加密码4、合并

mybatis执行insert返回id实现详解

《mybatis执行insert返回id实现详解》MyBatis插入操作默认返回受影响行数,需通过useGeneratedKeys+keyProperty或selectKey获取主键ID,确保主键为自... 目录 两种方式获取自增 ID:1. ​​useGeneratedKeys+keyProperty(推

Spring Boot集成Druid实现数据源管理与监控的详细步骤

《SpringBoot集成Druid实现数据源管理与监控的详细步骤》本文介绍如何在SpringBoot项目中集成Druid数据库连接池,包括环境搭建、Maven依赖配置、SpringBoot配置文件... 目录1. 引言1.1 环境准备1.2 Druid介绍2. 配置Druid连接池3. 查看Druid监控

Linux在线解压jar包的实现方式

《Linux在线解压jar包的实现方式》:本文主要介绍Linux在线解压jar包的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux在线解压jar包解压 jar包的步骤总结Linux在线解压jar包在 Centos 中解压 jar 包可以使用 u

Java中读取YAML文件配置信息常见问题及解决方法

《Java中读取YAML文件配置信息常见问题及解决方法》:本文主要介绍Java中读取YAML文件配置信息常见问题及解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要... 目录1 使用Spring Boot的@ConfigurationProperties2. 使用@Valu

Jenkins分布式集群配置方式

《Jenkins分布式集群配置方式》:本文主要介绍Jenkins分布式集群配置方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1.安装jenkins2.配置集群总结Jenkins是一个开源项目,它提供了一个容易使用的持续集成系统,并且提供了大量的plugin满