java線程的基礎(chǔ)實(shí)例解析
一、線程初步認(rèn)識(shí)
1、什么是線程
操作系統(tǒng)運(yùn)行一個(gè)程序會(huì)為其啟動(dòng)一個(gè)進(jìn)程。例如,啟動(dòng)一個(gè)Java程序會(huì)創(chuàng)建一個(gè)Java進(jìn)程?,F(xiàn)代操作系統(tǒng)調(diào)度的最小單元是線程,線程也稱為輕量級(jí)進(jìn)程(Light Weight Process),一個(gè)進(jìn)程中可以創(chuàng)建一個(gè)到多個(gè)線程,線程擁有自己的計(jì)數(shù)器、堆棧和局部變量等屬性,并且能訪問(wèn)共享的內(nèi)存變量。處理器會(huì)通過(guò)快速切換這些線程,來(lái)執(zhí)行程序。
2、Java本身就是多線程
示例代碼:
package com.lizba.p2;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.Arrays;
/**
*
* @Author: Liziba
* @Date: 2021/6/13 23:03
*/
public class MultiThread {
public static void main(String[] args) {
// 獲取Java線程管理MXBean
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
// 獲取線程和線程堆棧信息;
// boolean lockedMonitors = false 不需要獲取同步的monitor信息;
// boolean lockedSynchronizers = false 不需要獲取同步的synchronizer信息
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
// 打印線程ID和線程name
Arrays.stream(threadInfos).forEach(threadInfo -> {
System.out.println("[" + threadInfo.getThreadId() + "]" + threadInfo.getThreadName());
});
}
}
輸出結(jié)果(不一定一致):
[6]Monitor Ctrl-Break // idea中特有的線程(不用管)
[5]Attach Listener // JVM進(jìn)程間的通信線程
[4]Signal Dispatcher // 分發(fā)處理發(fā)送給JVM信號(hào)的線程
[3]Finalizer // 調(diào)用對(duì)象的finalizer線程
[2]Reference Handler // 清楚Reference的線程
[1]main // main線程,用戶程序入口
總結(jié):從輸出結(jié)果不難看出,Java程序本身就是多線程的。它不僅僅只有一個(gè)main線程在運(yùn)行,而是main線程和其他多個(gè)線程在同時(shí)運(yùn)行。
3、為什么要使用多線程
使用多線程的好處如下:
1.更多處理器核心
計(jì)算機(jī)處理器核心數(shù)增多,由以前的高主頻向多核心技術(shù)發(fā)展,現(xiàn)在的計(jì)算機(jī)更擅長(zhǎng)于并行計(jì)算,因此如何充分利用多核心處理器是現(xiàn)在的主要問(wèn)題。線程是操作系統(tǒng)調(diào)度的最小單元,一個(gè)程序作為一個(gè)進(jìn)程來(lái)運(yùn)行,它會(huì)創(chuàng)建多個(gè)線程,而一個(gè)線程在同一時(shí)刻只能運(yùn)行在一個(gè)處理器上。因此一個(gè)進(jìn)程如果能使用多線程計(jì)算,將其計(jì)算邏輯分配到多個(gè)處理器核心上,那么相比單線程運(yùn)行將會(huì)有更顯著的性能提升。
2.更快響應(yīng)時(shí)間
在復(fù)雜業(yè)務(wù)場(chǎng)景中,我們可以將非強(qiáng)一致性關(guān)聯(lián)的業(yè)務(wù)派發(fā)給其他線程處理(或者使用消息隊(duì)列)。這樣可以減少應(yīng)用響應(yīng)用戶請(qǐng)求的時(shí)間
3.更好的編程模型
合理使用Java的提供的多線程編程模型,能使得程序員更好的解決問(wèn)題,而不需要過(guò)于復(fù)雜的考慮如何將其多線程化。
4、線程的優(yōu)先級(jí)
現(xiàn)代操作系統(tǒng)基本采用的是時(shí)間片分配的方式來(lái)調(diào)度線程,也就是操作系統(tǒng)將CPU的運(yùn)行分為一個(gè)個(gè)時(shí)間片,線程會(huì)分配的若干時(shí)間片,當(dāng)線程時(shí)間片用完了,就會(huì)發(fā)生線程調(diào)度等待下次時(shí)間片的分配。線程在一次CPU調(diào)度中能執(zhí)行多久,取決于所分時(shí)間片的多少,而線程優(yōu)先級(jí)就是決定線程需要多或者少分配一些處理器資源的線程屬性。
在Java線程中,線程的優(yōu)先級(jí)的可設(shè)置范圍是1-10,默認(rèn)優(yōu)先級(jí)是5,理論上優(yōu)先級(jí)高的線程分配時(shí)間片數(shù)量要優(yōu)先于低的線程(部分操作系統(tǒng)這個(gè)設(shè)置是不生效的);
示例代碼:
package com.lizba.p2;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* <p>
* 線程優(yōu)先級(jí)設(shè)置
* </p>
*
* @Author: Liziba
* @Date: 2021/6/14 12:03
*/
public class Priority {
/** 線程執(zhí)行流程控制開(kāi)關(guān) */
private static volatile boolean notStart = true;
/** 線程執(zhí)行流程控制開(kāi)關(guān) */
private static volatile boolean notEnd = true;
public static void main(String[] args) throws InterruptedException {
List<Job> jobs = new ArrayList<>();
// 設(shè)置5個(gè)優(yōu)先級(jí)為1的線程,設(shè)置5個(gè)優(yōu)先級(jí)為10的線程
for (int i = 0; i < 10; i++) {
int priority = i < 5 ? Thread.MIN_PRIORITY : Thread.MAX_PRIORITY;
Job job = new Job(priority);
jobs.add(job);
Thread thread = new Thread(job, "Thread:" + i);
thread.setPriority(priority);
thread.start();
}
notStart = false;
TimeUnit.SECONDS.sleep(10);
notEnd = false;
jobs.forEach(
job -> System.out.println("Job priority : " + job.priority + ", Count : " + job.jobCount)
);
}
/**
* 通過(guò)Job來(lái)記錄線程的執(zhí)行次數(shù)和優(yōu)先級(jí)
*/
static class Job implements Runnable {
private int priority;
private long jobCount;
public Job(int priority) {
this.priority = priority;
}
@Override
public void run() {
while (notStart) {
// 讓出CPU時(shí)間片,等待下次調(diào)度
Thread.yield();
}
while (notEnd) {
// 讓出CPU時(shí)間片,等待下次調(diào)度
Thread.yield();
jobCount++;
}
}
}
}
執(zhí)行結(jié)果:

從輸出結(jié)果上來(lái)看,優(yōu)先級(jí)為1的線程和優(yōu)先級(jí)為10的線程執(zhí)行的次數(shù)非常相近,因此這表明程序正確性是不能依賴線程的優(yōu)先級(jí)高低的。
5、線程的狀態(tài)
線程的生命周期如下:
| 狀態(tài)名稱 | 說(shuō)明 |
|---|---|
| NEW | 初始狀態(tài),線程被構(gòu)建,并未調(diào)用start()方法 |
| RUNNABLE | 運(yùn)行狀態(tài),Java線程將操作系統(tǒng)中的就緒和運(yùn)行兩種狀態(tài)統(tǒng)稱為“運(yùn)行中” |
| BLOCKED | 阻塞狀態(tài),線程阻塞于鎖 |
| WAITING | 等待狀態(tài),線程進(jìn)入等待狀態(tài),進(jìn)入該狀態(tài)表示當(dāng)前線程需要等待其他線程作出一些特定動(dòng)作(通知或中斷) |
| TIME_WAITING | 超時(shí)等待,先比WAITING可以在指定的時(shí)間內(nèi)自行返回 |
| TERMINATED | 終止?fàn)顟B(tài),表示當(dāng)前線程已經(jīng)執(zhí)行完畢 |
通過(guò)代碼來(lái)查看Java線程的狀態(tài)
代碼示例:
package com.lizba.p2;
import java.util.concurrent.TimeUnit;
/**
* <p>
* 睡眠指定時(shí)間工工具類(lèi)
* </p>
*
* @Author: Liziba
* @Date: 2021/6/14 13:27
*/
public class SleepUtil {
public static final void sleepSecond(long seconds) {
try {
TimeUnit.SECONDS.sleep(seconds);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package com.lizba.p2;
/**
* <p>
* 線程狀態(tài)示例代碼
* </p>
*
* @Author: Liziba
* @Date: 2021/6/14 13:25
*/
public class ThreadStateDemo {
public static void main(String[] args) {
// TimeWaiting
new Thread(new TimeWaiting(), "TimeWaitingThread").start();
// Waiting
new Thread(new Waiting(), "WaitingThread").start();
// Blocked1和Blocked2一個(gè)獲取鎖成功,一個(gè)獲取失敗
new Thread(new Blocked(), "Blocked1Thread").start();
new Thread(new Blocked(), "Blocked2Thread").start();
}
// 線程不斷的進(jìn)行睡眠
static class TimeWaiting implements Runnable {
@Override
public void run() {
while (true) {
SleepUtil.sleepSecond(100);
}
}
}
// 線程等待在Waiting.class實(shí)例上
static class Waiting implements Runnable {
@Override
public void run() {
while (true) {
synchronized (Waiting.class) {
try {
Waiting.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
// 該線程Blocked.class實(shí)例上加鎖,不會(huì)釋放該鎖
static class Blocked implements Runnable {
@Override
public void run() {
synchronized (Blocked.class) {
while (true) {
SleepUtil.sleepSecond(100);
}
}
}
}
}
使用JPS查看Java進(jìn)程:

查看示例代碼ThreadStateDemo進(jìn)程ID是2576,鍵入jstack 2576查看輸出:

整理輸出結(jié)果: 
| 線程名稱 | 線程狀態(tài) |
|---|---|
| Blocked2Thread | BLOCKED (on object monitor),阻塞在獲取Blocked.class的鎖上 |
| Blocked1Thread | TIMED_WAITING (sleeping) |
| WaitingThread | WAITING (on object monitor) |
| TimeWaitingThread | TIMED_WAITING (sleeping) |
總結(jié):
線程在自身生命周期中不是規(guī)定處于某一個(gè)狀態(tài),而是隨著代碼的執(zhí)行在不同的狀態(tài)之間進(jìn)行切換。
Java線程的狀態(tài)變化圖如下:

總結(jié):
- 線程創(chuàng)建后,調(diào)用start()方法開(kāi)始運(yùn)行
- 線程執(zhí)行wait()方法后,線程進(jìn)入等待狀態(tài),進(jìn)入等待的線程需要依靠其他線程才能夠返回到運(yùn)行狀態(tài)
- 超時(shí)等待相當(dāng)于在等待狀態(tài)的基礎(chǔ)上增加了超時(shí)限制,達(dá)到設(shè)置的超時(shí)時(shí)間后返回到運(yùn)行狀態(tài)
- 線程執(zhí)行同步方法或代碼塊時(shí),未獲取到鎖的線程,將會(huì)進(jìn)入到阻塞狀態(tài)。
- 線程執(zhí)行完Runnable的run()方法之后進(jìn)入到終止?fàn)顟B(tài)
- 阻塞在Java的concurrent包中Lock接口的線程是等待狀態(tài),因?yàn)長(zhǎng)ock接口阻塞的實(shí)現(xiàn)使用的是Daemon線程
6、Daemon線程
簡(jiǎn)介:
Daemon線程是一種支持型線程,它的主要作用是程序中后臺(tái)調(diào)度和支持性工作。當(dāng)一個(gè)Java虛擬機(jī)中不存在非Daemon線程的時(shí)候,Java虛擬機(jī)將會(huì)退出。Daemon線程需要在啟動(dòng)之前設(shè)置,不能在啟動(dòng)之后設(shè)置。
設(shè)置方式:
Thread.setDaemon(true)
需要特別注意的點(diǎn):
Daemon線程被用作支持性工作的完成,但是在Java虛擬機(jī)退出時(shí)Daemon線程的finally代碼塊不一定執(zhí)行。
示例代碼:
package com.lizba.p2;
/**
* <p>
* DaemonRunner線程
* </p>
*
* @Author: Liziba
* @Date: 2021/6/14 19:50
*/
public class DaemonRunner implements Runnable{
@Override
public void run() {
try {
SleepUtil.sleepSecond(100);
} finally {
System.out.println("DaemonRunner finally run ...");
}
}
}
測(cè)試:
package com.lizba.p2;
/**
* <p>
*
* </p>
*
* @Author: Liziba
* @Date: 2021/6/14 19:59
*/
public class DaemonTest {
public static void main(String[] args) {
Thread t = new Thread(new DaemonRunner(), "DaemonRunner");
t.setDaemon(true);
t.start();
}
}
輸出結(jié)果:

總結(jié):
不難發(fā)現(xiàn),DaemonRunner的run方法的finally代碼塊并沒(méi)有執(zhí)行,這是因?yàn)?,?dāng)Java虛擬機(jī)中已經(jīng)沒(méi)有非Daemon線程時(shí),虛擬機(jī)會(huì)立即退出,虛擬機(jī)中的所以daemon線程需要立即終止,所以線程DaemonRunner會(huì)被立即終止,finally并未執(zhí)行。
二、線程啟動(dòng)和終止
1、構(gòu)造線程
運(yùn)行線程之前需要構(gòu)造一個(gè)線程對(duì)象,線程對(duì)象在構(gòu)造的時(shí)候需要設(shè)置一些線程的屬性,這些屬性包括線程組、線程的優(yōu)先級(jí)、是否是daemon線程、線程名稱等信息。
代碼示例:
來(lái)自java.lang.Thread
/**
* Initializes a Thread.
*
* @param g the Thread group
* @param target the object whose run() method gets called
* @param name the name of the new Thread
* @param stackSize the desired stack size for the new thread, or
* zero to indicate that this parameter is to be ignored.
* @param acc the AccessControlContext to inherit, or
* AccessController.getContext() if null
* @param inheritThreadLocals if {@code true}, inherit initial values for
* inheritable thread-locals from the constructing thread
*/
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
// 設(shè)置線程名稱
this.name = name;
// 當(dāng)前線程設(shè)置為該線程的父線程
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
if (security != null) {
g = security.getThreadGroup();
}
if (g == null) {
g = parent.getThreadGroup();
}
}
g.checkAccess();
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
// 設(shè)置線程組
this.group = g;
// 將daemon屬性設(shè)置為父線程的對(duì)應(yīng)的屬性
this.daemon = parent.isDaemon();
// 將prority屬性設(shè)置為父線程的對(duì)應(yīng)的屬性
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);
// 復(fù)制父線程的InheritableThreadLocals屬性
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
// 設(shè)置一個(gè)線程id
tid = nextThreadID();
}
總結(jié):
在上述代碼中,一個(gè)新構(gòu)建的線程對(duì)象時(shí)由其parent線程來(lái)分配空間的,而child繼承了parent是否為Daemon、優(yōu)先級(jí)和加載資源的contextClassLoader以及可繼承的ThreadLocal,同時(shí)會(huì)分配一個(gè)唯一的ID來(lái)標(biāo)志線程。此時(shí)一個(gè)完整的能夠運(yùn)行的線程對(duì)象就初始化好了,在堆內(nèi)存中等待運(yùn)行。
2、什么是線程中斷
中斷可以理解為線程的一個(gè)標(biāo)識(shí)位屬性,它表示一個(gè)運(yùn)行中的線程是否被其他線程進(jìn)行了中斷操作。線程通過(guò)檢查自身是否被中斷來(lái)進(jìn)行響應(yīng),線程通過(guò)方法isInterrupted()來(lái)進(jìn)行判斷是否被中斷,也可以通過(guò)調(diào)用靜態(tài)方法Thread.interrupted()對(duì)當(dāng)前線程的中斷標(biāo)志位進(jìn)行復(fù)位。
如下情況不能準(zhǔn)確判斷線程是否被中斷過(guò):
1.線程已經(jīng)終止運(yùn)行,即使被中斷過(guò),isInterrupted()方法也會(huì)返回false
2.方法拋出InterruptedException異常,即使被中斷過(guò),調(diào)用isInterrupted()方法將會(huì)返回false,這是因?yàn)閽伋鯥nterruptedException之前會(huì)清除中斷標(biāo)志。
示例代碼:
package com.lizba.p2;
/**
* <p>
* 線程中斷示例代碼
* </p>
*
* @Author: Liziba
* @Date: 2021/6/14 20:36
*/
public class Interrupted {
public static void main(String[] args) {
// sleepThread不停的嘗試睡眠
Thread sleepThread = new Thread(new SleepRunner(), "sleepThread");
sleepThread.setDaemon(true);
// busyThread
Thread busyThread = new Thread(new BusyRunner(), "busyThread");
busyThread.setDaemon(true);
// 啟動(dòng)兩個(gè)線程
sleepThread.start();
busyThread.start();
// 休眠5秒,讓sleepThread和busyThread運(yùn)行充分
SleepUtil.sleepSecond(5);
// 中斷兩個(gè)線程
sleepThread.interrupt();
busyThread.interrupt();
System.out.println("SleepThread interrupted is " + sleepThread.isInterrupted());
System.out.println("BusyThread interrupted is " + busyThread.isInterrupted());
// 睡眠主線程,防止daemon線程退出
SleepUtil.sleepSecond(2);
}
static class SleepRunner implements Runnable {
@Override
public void run() {
while (true) {
SleepUtil.sleepSecond(10);
}
}
}
static class BusyRunner implements Runnable {
@Override
public void run() {
while (true) {}
}
}
}
查看運(yùn)行結(jié)果:

總結(jié):
拋出InterruptedException的是sleepThread線程,雖然兩者都被中斷過(guò),但是sleepThread線程的中斷標(biāo)志返回的是false,這是因?yàn)門(mén)imeUnit.SECONDS.sleep(seconds)會(huì)拋出InterruptedException異常,拋出異常之前,sleepThread線程的中斷標(biāo)志被清除了。但是,busyThread一直在運(yùn)行沒(méi)有拋出異常,中斷位沒(méi)有被清除。
3、suspend()、resume()和stop()
舉例:線程這三個(gè)方法,相當(dāng)于QQ音樂(lè)播放音樂(lè)時(shí)的暫停、恢復(fù)和停止操作。(注意這些方法已經(jīng)過(guò)期了,不建議使用。)
示例代碼:
package com.lizba.p2;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* <p>
* 線程過(guò)期方法示例
* </p>
*
* @Author: Liziba
* @Date: 2021/6/14 20:57
*/
public class Deprecated {
static DateFormat format = new SimpleDateFormat("HH:mm:ss");
public static void main(String[] args) {
Thread printThread = new Thread(new PrintThread(), "PrintThread");
printThread.start();
SleepUtil.sleepSecond(3);
// 暫停printThread輸出
printThread.suspend();
System.out.println("main suspend PrintThread at " + format.format(new Date()));
SleepUtil.sleepSecond(3);
// 恢復(fù)printThread輸出
printThread.resume();
System.out.println("main resume PrintThread at " + format.format(new Date()));
SleepUtil.sleepSecond(3);
// 終止printThread輸出
printThread.stop();
System.out.println("main stop PrintThread at " + format.format(new Date()));
SleepUtil.sleepSecond(3);
}
static class PrintThread implements Runnable {
@Override
public void run() {
while (true) {
System.out.println(Thread.currentThread().getName() + "Run at "
+ format.format(new Date()));
SleepUtil.sleepSecond(1);
}
}
}
}
輸出結(jié)果:

總結(jié):
上述代碼執(zhí)行輸出的結(jié)果,與API說(shuō)明和我們的預(yù)期完成一致,但是看似正確的代碼卻隱藏這很多問(wèn)題。
存在問(wèn)題:
- suspend()方法調(diào)用后不會(huì)釋放已占有的資源(比如鎖),可能會(huì)導(dǎo)致死鎖
- stop()方法在終結(jié)一個(gè)線程時(shí)不能保證資源的正常釋放,可能會(huì)導(dǎo)致程序處于不確定的工作狀態(tài)
4、正確的終止線程
- 調(diào)用線程的interrupt()方法
- 使用一個(gè)Boolean類(lèi)型的變量來(lái)控制是否停止任務(wù)并終止線程
示例代碼:
package com.lizba.p2;
/**
* <p>
* 標(biāo)志位終止線程示例代碼
* </p>
*
* @Author: Liziba
* @Date: 2021/6/14 21:17
*/
public class ShutDown {
public static void main(String[] args) {
Runner one = new Runner();
Thread t = new Thread(one, "CountThread");
t.start();
SleepUtil.sleepSecond(1);
t.interrupt();
Runner two = new Runner();
t = new Thread(two, "CountThread");
t.start();
SleepUtil.sleepSecond(1);
two.cancel();
}
private static class Runner implements Runnable {
private long i;
private volatile boolean on = true;
@Override
public void run() {
while (on && !Thread.currentThread().isInterrupted()) {
i++;
}
System.out.println("Count i = " +i);
}
/**
* 關(guān)閉
*/
public void cancel() {
on = false;
}
}
}
輸出結(jié)果:

總結(jié):
main線程通過(guò)中斷操作和cancel()方法均可使CountThread得以終止。這兩種方法終止線程的好處是能讓線程在終止時(shí)有機(jī)會(huì)去清理資源。做法更加安全和優(yōu)雅。
相關(guān)文章
java實(shí)現(xiàn)ftp上傳 如何創(chuàng)建文件夾
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)ftp上傳的相關(guān)資料,教大家如何創(chuàng)建文件夾?具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04
詳解JVM 運(yùn)行時(shí)內(nèi)存使用情況監(jiān)控
這篇文章主要介紹了詳解JVM 運(yùn)行時(shí)內(nèi)存使用情況監(jiān)控,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-09-09
Java實(shí)現(xiàn)求解一元n次多項(xiàng)式的方法示例
這篇文章主要介紹了Java實(shí)現(xiàn)求解一元n次多項(xiàng)式的方法,涉及java高斯消元法處理矩陣運(yùn)算解多項(xiàng)式的相關(guān)操作技巧,需要的朋友可以參考下2018-01-01
eclipse輸出Hello World的實(shí)現(xiàn)方法
這篇文章主要介紹了eclipse輸出Hello World的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
Java 圖文并茂講解主方法中的String[] args參數(shù)作用
很多老鐵不清楚JAVA主方法中main()里面的的參數(shù)是什么意思,以及有什么作用,接下來(lái)給大家用最通俗易懂的話來(lái)講解,還不清楚的朋友來(lái)看看吧2022-04-04
jvm運(yùn)行原理以及類(lèi)加載器實(shí)例詳解
這篇文章主要給大家介紹了關(guān)于jvm運(yùn)行原理以及類(lèi)加載器的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05
springCloud中的Sidecar多語(yǔ)言支持詳解
這篇文章主要介紹了springCloud中的Sidecar多語(yǔ)言支持詳解,Sidecar是將一組緊密結(jié)合的任務(wù)與主應(yīng)用程序共同放在一臺(tái)主機(jī)Host中,但會(huì)將它們部署在各自的進(jìn)程或容器中,需要的朋友可以參考下2024-01-01
Java精品項(xiàng)目瑞吉外賣(mài)之員工新增篇
這篇文章主要為大家詳細(xì)介紹了java精品項(xiàng)目-瑞吉外賣(mài)訂餐系統(tǒng),此項(xiàng)目過(guò)大,分為多章獨(dú)立講解,本篇內(nèi)容為新增員工功能的實(shí)現(xiàn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05
SpringBoot動(dòng)態(tài)修改yml配置文件的方法詳解
這篇文章主要為大家詳細(xì)介紹了SpringBoot動(dòng)態(tài)修改yml配置文件的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-03-03
如何實(shí)現(xiàn)java8 list按照元素的某個(gè)字段去重
這篇文章主要介紹了如何實(shí)現(xiàn)java8 list按照元素的某個(gè)字段去重,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,,需要的朋友可以參考下2019-06-06

