java多執行緒
行程、執行緒與多執行緒
-
行程是執行程式的一次執行程序,是一個動態的概念,是系統支援分配的單位
-
通常一個行程可以包含一個或多個執行緒,執行緒是CPU調度和執行的單位
-
執行緒就是獨立執行的路徑,由cpu調度
-
執行緒會帶來額外的開銷,如cpu調度時間,并發控制開銷
-
每個執行緒在自己的作業記憶體中互動,記憶體控制不當會造成資料的不一致
-
main()稱之為主執行緒,為系統的入口,用于執行整個程式
-
程式運行時,即使沒有自己創建執行緒,后臺也會有多個執行緒如主執行緒,gc執行緒
-
很多執行緒是模擬出來的,真正的多執行緒是指有多個cpu,即多核,如服務器,如果是模擬出來的多執行緒,即只有一個cpu的情況下,在同一個時間點,cpu只能執行一個代碼,因為切換的很快,所以有了同時執行的錯覺
執行緒的三種創建方式
- Thread class:繼承Thread類
- Runnable介面:實作Runnable介面
- Callable介面:實作Callable介面
通過繼承Thread創建執行緒
? 注意:Thread類實作了Runnable介面
流程:
1. 自定義執行緒類繼承Thread類
2. 重寫其run()方法,撰寫程式執行體
3. 創建執行緒物件,呼叫start()方法啟動執行緒
public class MyThread extends Thread {
@Override
public void run() {
//run方法執行緒體
for (int i = 0; i < 5; i++) {
System.out.println("run方法執行緒--"+i);
}
}
public static void main(String[] args) {
//run方法執行緒
MyThread myThread = new MyThread();
//啟動執行緒,另外執行緒只能啟動一次死亡之后不能重新啟動
myThread.start();
//主執行緒
for (int i = 0; i < 5; i++) {
System.out.println("主執行緒--"+i);
}
}
}
/*out:
主執行緒--0
run方法執行緒--0
主執行緒--1
run方法執行緒--1
主執行緒--2
run方法執行緒--2
主執行緒--3
run方法執行緒--3
主執行緒--4
run方法執行緒--4
注意:輸出結果不唯一,每次執行結果不一樣
執行緒開啟不一定立即執行,是由cpu調度執行
實作Runnable介面創建執行緒
1. 自定義執行緒類實作Runnable介面
2. 重寫其run()方法,撰寫程式執行體
3. 創建該實作類物件,創建Thread執行緒物件(或者創建Thread物件),并且向Thread物件丟入Runnable實作類(代理方法)
public class MyThread implements Runnable {
@Override
public void run() {
//run方法執行緒體
for (int i = 0; i < 5; i++) {
System.out.println("run方法執行緒--"+i);
}
}
public static void main(String[] args) {
//run方法執行緒
MyThread myThread = new MyThread();
//靜態代理
new Thread(myThread).start();
//主執行緒
for (int i = 0; i < 5; i++) {
System.out.println("主執行緒--"+i);
}
}
}
/*
主執行緒--0
run方法執行緒--0
主執行緒--1
run方法執行緒--1
主執行緒--2
主執行緒--3
run方法執行緒--2
主執行緒--4
run方法執行緒--3
run方法執行緒--4
相比于前一種更推薦本方法:避免單繼承局限,靈活方便,方便一個物件被多個執行緒使用
例如:
public class MyThread implements Runnable {
private int number=10;
@Override
public void run() {
while (true){
if(number<=0){
break;
}
//執行緒休眠,防止cpu速度太快瞬間把票搶完
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//當前執行緒名字
System.out.println(Thread.currentThread().getName()+"--"+number--);
}
}
public static void main(String[] args) {
//run方法執行緒
MyThread myThread = new MyThread();
//第二個引數為執行緒名字
new Thread(myThread,"李").start();
new Thread(myThread,"馬").start();
new Thread(myThread,"王").start();
}
}
/*
馬--9
王--10
李--10
馬--8
李--8
王--8
馬--7
王--6
李--6
王--5
馬--4
李--3
李--0
馬--2
王--1
注意:至于為什么出現兩個人得到同一個數字,或者出現0,則是一些經典的執行緒同步問題
實作Callable介面創建執行緒
步驟:
- 實作Callable介面,需要回傳值型別
- 重寫call方法,需要拋出例外
- 創建目標物件
- 創建執行服務
- 提交執行
- 獲取結果
- 關閉服務
//<>中填寫回傳值型別(下面call方法的回傳值)
public class MyThread implements Callable<Boolean> {
//重寫call方法,回傳值保持和<>中的一樣
@Override
public Boolean call() throws Exception {\
//具體執行緒操作寫在這里
return false;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
MyThread myThread3 = new MyThread();
//創建執行服務:newFixedThreadPool執行緒池,引數為執行緒池大小
ExecutorService ser = Executors.newFixedThreadPool(3);
//提交執行:<>中型別就是call的回傳型別
Future<Boolean> r1 = ser.submit(myThread1);
Future<Boolean> r2 = ser.submit(myThread2);
Future<Boolean> r3 = ser.submit(myThread3);
//獲取結果:call()回傳值
Boolean rs1 = r1.get();
Boolean rs2 = r2.get();
Boolean rs3 = r3.get();
//關閉服務
ser.shutdownNow();
}
}
好處:
-
可以定義回傳值
-
可以拋出例外
靜態代理
代理就是兩個物件實作同一個介面,一個是代理物件一個是真實物件,通過代理物件執行真實物件方法,具體方法:代理物件和真實物件實作同一個介面,創建代理物件同時向其中傳入一個真實物件,在代理物件中操作真實物件,在外部呼叫操作代理物件就能夠操作真實物件,
例如前面通過實作Runnable介面創建執行緒就運用了靜態代理
//MyThread是Runnable介面的實作類
MyThread myThread = new MyThread();
//靜態代理
new Thread(myThread).start();
好處:
- 代理物件可以在真實物件基礎上做很多拓展
- 真實物件專注于自己,專注于核心方法
Lambda運算式
為什么使用它?
- 避免匿名內部類過多
- 讓代碼看起來更加簡潔
- 去掉無意義代碼留下核心邏輯
函式式介面
- 理解函式式介面是學習Lambda運算式的關機
- 函式式介面的定義:
- 任何介面,如果只包含唯一一個抽象方法,那么它就是函式式介面
- 對于函式式介面,我們可以通過lamda運算式來創建該介面的物件
怎么使用?
public interface MyInterface {
void lambda();
}
class main {
public static void main(String[] args) {
// 無參且一行
MyInterface myInterface =() -> System.out.println("hello world");
// 有引數a
// MyInterface myInterface =a -> System.out.println("hello world");
// 多行
// MyInterface myInterface1 = ()->{
// System.out.println();
// System.out.println()
// };
// 上述代碼完全類似下面的匿名內部類
// MyInterface myInterface1 = new MyInterface() {
// @Override
// public void lambda() {
// System.out.println("hello world");
// }
// };
}
}
//out:hello world
執行緒的五大狀態
執行緒方法
停止執行緒
- 可以但不推薦使用JDK提供的stop()、destroy()方法,已經廢棄
- 推薦執行緒自己停下來
- 建議使用一個標志位進行終止變數,當flag=false終止執行緒運行
public class MyThread implements Runnable {
private boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag){
System.out.println("run........Thread"+i++);
}
}
public void stop(){
this.flag = false;
System.out.println("執行緒已經停止");
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
new Thread(myThread).start();
for (int i = 0; i < 10; i++) {
//不加這一句的話回圈速度太快以至于執行緒還沒運行
System.out.println("main---"+i);
if(i==5){
//呼叫自己寫的stop方法
myThread.stop();
}
}
}
}
/*
main---0
run........Thread0
main---1
run........Thread1
main---2
run........Thread2
main---3
run........Thread3
main---4
run........Thread4
main---5
run........Thread5
執行緒已經停止
main---6
main---7
main---8
main---9
執行緒休眠
- sleep()指定當前執行緒阻塞的毫秒數
- 運行狀態轉為阻塞狀態
- sleep存在例外InterruptedException
- sleep時間到達后進入就緒狀態
- 其可以模擬網路延時,倒計時等
- 每一個物件都有一個鎖,sleep不會釋放鎖
沒啥可說的固定寫法:Thread.sleep()
執行緒禮讓
- 即讓當前正在執行的執行緒暫停,但不阻塞
- 把執行緒從運行轉為就緒狀態
- 讓cpu重新調度,禮讓不一定成功,看cpu心情
- 固定寫法:Thread.yield();
public class MyThread implements Runnable {
private boolean flag = true;
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"開始執行");
Thread.yield();
System.out.println(Thread.currentThread().getName()+"完成執行");
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
MyThread myThread1 = new MyThread();
new Thread(myThread,"a").start();
new Thread(myThread1,"b").start();
}
}
/*
a開始執行
b開始執行
b完成執行
a完成執行
執行緒強制執行
- Join合并執行緒,待此執行緒完成后,才去執行其他執行緒
- 可以看作插隊
public class MyThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("VIP駕到通通閃開!"+i);
}
}
public static void main(String[] args) throws InterruptedException {
MyThread myThread1 = new MyThread();
Thread thread1 = new Thread(myThread1, "vip");
//主執行緒
for (int i = 0; i < 10; i++) {
if(i==5){
thread1.start();
thread1.join();
}
System.out.println("我是一個主執行緒"+i);
}
}
}
/*
我是一個主執行緒0
我是一個主執行緒1
我是一個主執行緒2
我是一個主執行緒3
我是一個主執行緒4
VIP駕到通通閃開!0
VIP駕到通通閃開!1
VIP駕到通通閃開!2
VIP駕到通通閃開!3
VIP駕到通通閃開!4
我是一個主執行緒5
我是一個主執行緒6
我是一個主執行緒7
我是一個主執行緒8
我是一個主執行緒9
執行緒狀態觀測
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("////////");
});
//觀測狀態
Thread.State state = thread.getState();
System.out.println(state);
// 啟動執行緒
thread.start();
state = thread.getState();
System.out.println(state);
// 只要執行緒不終止一直輸出狀態
while (state != Thread.State.TERMINATED){
Thread.sleep(1000);
state = thread.getState();
System.out.println(state);
}
}
/*
NEW
RUNNABLE
RUNNABLE
TIMED_WAITING
TIMED_WAITING
RUNNABLE
TIMED_WAITING
////////
TERMINATED
執行緒優先級
-
執行緒優先級用數字表示:Thread.MIN_PRIORITY=1;
? Thread.MAX_PRIORITY = 10;
? Thread.NORM_PRIORITY = 5;
上面為最小最大正常優先級,均為常量
-
可以使用以下方式改變或者獲取優先級:
1. .getPrioriyt
1. .setPriority(int xxx) -
另外,優先級高并不是一定先執行,而是相對來說權重更高,到底先還是后看cpu調度
public class MyThread implements Runnable {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) throws InterruptedException {
//主執行緒優先級:主執行緒優先級無法修改
System.out.println(Thread.currentThread().getName()+"-----"+Thread.currentThread().getPriority());
MyThread myThread = new MyThread();
Thread t1 = new Thread(myThread,"t1");
Thread t2 = new Thread(myThread,"t2");
Thread t3 = new Thread(myThread,"t3");
Thread t4 = new Thread(myThread,"t4");
Thread t5 = new Thread(myThread,"t5");
// 一定要注意:先設定優先級再啟動否則沒用
t1.start();
t2.setPriority(1);
t2.start();
//設定為最大優先級
t3.setPriority(Thread.MAX_PRIORITY);
t3.start();
}
}
/*
main-----5
t3
t2
t1
守護執行緒
- daemon
- 執行緒分為用戶執行緒和守護執行緒
- 虛擬機必須確保用戶執行緒執行完畢
- 虛擬機不用等待守護執行緒完畢
- 守護執行緒如后臺記錄操作日志、監控記憶體、垃圾回收等待
- 守護執行緒:字面意思守護用戶執行緒,虛擬機無視是否還有守護執行緒存在,當用戶執行緒結束時虛擬機關閉
Thread thread = new Thread();
//默認是false代表用戶執行緒,設定為true表示守護執行緒
thread.setDaemon(true);
執行緒同步
-
就是多個執行緒操作同一個資源
-
執行緒同步其實就是一個等待機制,多個需要同時訪問物件的執行緒進入物件等待池形成佇列
-
并發:同一個物件被多個執行緒操作,如買票
-
佇列和鎖保障執行緒安全性
-
為了保證資料在方法中被訪問時的正確性,在訪問時加入鎖機制synchronized
-
與此同時當一個執行緒獲得物件的排它鎖,獨占資源,其他執行緒必須等待使用后釋放鎖即可,可能存在以下問題:
執行緒同步操作多種不安全問題,具體可以看上面實作Runnable介面創建執行緒的例子
synchronized(鎖)
-
針對上面所說的問題:我們可以用鎖來解決,即synchronized關鍵字,它包括方法和塊兩大用法
-
同步方法,synchronized方法控制對“物件”的訪問,每個物件對應一把鎖,每個synchronized方法都必須獲得呼叫該方法的物件的鎖才能執行,否則阻塞,方法一旦執行就會獨占該鎖,直到該方法回傳,缺陷:若將一個大方法申明為synchronized將會影響效率
-
同步塊:synchronized(obj){} obj稱之為同步監視器,其可以是任何物件,但是推薦共享資源作為同步監視器,另外同步方法不用同步監視器因為其同步監視器就是this,就是這個物件本身,或者class
-
同步監視器執行程序:
1. 第一個執行緒訪問,鎖定同步監視器
2. 第二個執行緒訪問,發現已經被鎖定,無法訪問
3. 第一個執行緒訪問完畢,解鎖同步監視器
4. 第二個執行緒訪問,發現同步監視器沒有鎖,然后鎖定并且訪問
使用同步方法解決問題:
public class MyThread implements Runnable {
private int number=10;
private boolean flag = true;
@Override
public void run() {
while (flag) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
buy();
}
}
//上鎖,run方法也可以鎖
public synchronized void buy() {
if (number < 1) {
flag=false;
return;
}
System.out.println(Thread.currentThread().getName() + "--" + number--);
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
new Thread(myThread,"李").start();
new Thread(myThread,"馬").start();
new Thread(myThread,"王").start();
}
}
/*
李--10
王--9
馬--8
馬--7
李--6
王--5
馬--4
李--3
王--2
馬--1
鎖塊:
public class MyThread implements Runnable {
private int number=10;
private boolean flag = true;
@Override
public void run() {
while (flag) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//同步塊,()填變化的量必須是參考型別,鎖定的就是傳入引數
synchronized (Integer.valueOf(number)){
if (number < 1) {
flag=false;
}
else {
System.out.println(Thread.currentThread().getName() + "--" + number--);
}
}
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
new Thread(myThread,"李").start();
new Thread(myThread,"馬").start();
new Thread(myThread,"王").start();
}
}
/*
馬--10
王--9
李--8
李--7
王--6
馬--5
馬--4
李--3
王--2
馬--1
CopyOnWriteArrayList
//執行緒安全的arraylist
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 1000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
Thread.sleep(3000);
System.out.println(list.size());
//1000
死鎖
多個執行緒各自占有一些共享資源,同時互相等待對方占有資源,從而導致兩個或多個執行緒都在等待對方釋放資源停止執行的情況,
某一個同步塊同時擁有兩個以上物件的鎖,就可能出現死鎖
//當mirror和lipstick都只有一份時
//獲得mirror
synchronized (mirror){
// 獲得了mirror,此時lipstick被另外一個執行緒占據等待
// 其釋放,與此同時占據lipstick的執行緒也在等待它釋放mirror
// 于是死鎖發生了
synchronized (lipstick){
}
}
產生死鎖的必要條件
Lock(鎖)
- JDK5.0開始,java提供更加強大的執行緒同步機制--通過顯式定義同步鎖物件來實作同步,同步鎖使用Lock充當
- 每次只能有一個執行緒對Lock物件加鎖,執行緒開始訪問共享資源之前應當先獲得Lock鎖
- ReentranLock類實作了Lock,它擁有與synchronized相同的并發性和記憶體語意,在實作執行緒安全的控制中,比較常用的是ReentrantLock,可以顯式加鎖、釋放鎖
public class MyThread implements Runnable {
private static Integer number=10;
private boolean flag = true;
//定義lock鎖
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (flag) {
try {
Thread.sleep(100);
lock.lock();//加鎖,上鎖區域就是lock到unlock區域
if (number < 1) {
flag=false;
}
else {
System.out.println(Thread.currentThread().getName() + "--" +number--);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//解鎖
lock.unlock();
}
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
new Thread(myThread,"李").start();
new Thread(myThread,"馬").start();
new Thread(myThread,"王").start();
}
}
/*
馬--10
王--9
李--8
李--7
王--6
馬--5
王--4
李--3
馬--2
馬--1
執行緒協作--生產者消費者問題
生產者消費者問題:生產者消費者共享一個資源,并且生產者和消費者之間相互依賴,互為條件,作業系統學過的不想多說具體如下:
下面兩個解決辦法后面有具體演示
解決方法1:
解決方法二:信號燈法
java提供了幾個方法解決執行緒之間的通信問題:
注意:以下均是Object類的方法,都只能在同步方法或者同步代碼塊中使用,否則會拋出例外,另外sleep不會釋放鎖,wait會釋放鎖
解決方法一:管程法
public class Test {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Producer(container).start();
new Consumer().start();
}
}
//生產者
class Producer extends Thread{
//緩沖區
static SynContainer container;
public Producer(SynContainer container) {
this.container = container;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
container.push(new Chicken(i));
System.out.println("生產了第"+i+"只雞");
}
}
}
//消費者
class Consumer extends Thread{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("消費了第"+ Producer.container.pop().id+"只雞");
}
}
}
//產品
class Chicken{
int id;
public Chicken(int id) {
this.id = id;
}
}
//緩沖區
class SynContainer{
//需要一個容器大小
Chicken[] chickens = new Chicken[10];
// 容器計數器
int count = 0;
//生產者放入產品
public synchronized void push(Chicken chicken){
//如果容器滿了,等待消費者消費
if(count==chickens.length){
//通知消費者消費,生成等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
//如果沒有滿,我們就需要丟入產品
chickens[count] = chicken;
count++;
}
// 通知消費者可以消費了
this.notifyAll();
}
//消費者消費產品
public synchronized Chicken pop() {
//判斷是否有產品
if(count==0){
// 等待生產者生產
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 存在產品開始消費
count--;
Chicken chicken = chickens[count];
// 吃完了通知生產者生產
this.notifyAll();
return chicken;
}
}
/*
生產了第0只雞
消費了第0只雞
生產了第1只雞
生產了第2只雞
消費了第1只雞
生產了第3只雞
消費了第3只雞
生產了第4只雞
消費了第4只雞
消費了第2只雞
上面實作的存在一定問題,但是要傳達的思想就這樣
解決方法二:信號燈法
public class Test {
public static void main(String[] args) {
TV tv =new TV();
new Player(tv).start();
new Watcher(tv).start();
}
}
//生產者
class Player extends Thread{
TV tv;
public Player(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
if(i%2==0){
this.tv.play("x節目播放中");
}
else {
this.tv.play("y節目播放中");
}
}
}
}
//消費者
class Watcher extends Thread{
TV tv;
public Watcher(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
tv.watch();
}
}
}
//產品
class TV{
//演員表演,觀眾等待 T
//觀眾觀看,演員表演 F
String voice;//表演的節目
boolean flag = true;
public synchronized void play(String voice){
if(!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演員表演了:"+voice);
this.notifyAll();
this.voice = voice;
this.flag = !this.flag;
}
public synchronized void watch(){
if(flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("觀看了"+voice);
// 通知演員表演
this.notifyAll();
this.flag = !this.flag;
}
}
/*
演員表演了:x節目播放中
觀看了x節目播放中
演員表演了:y節目播放中
觀看了y節目播放中
演員表演了:x節目播放中
觀看了x節目播放中
演員表演了:y節目播放中
觀看了y節目播放中
演員表演了:x節目播放中
觀看了x節目播放中
執行緒池
-
經常創建和銷毀、使用量大的資源,對性能影響很大
-
思路:提前創建好多個執行緒,放入執行緒池,使用時直接獲取,使用完放回池中,可以避免頻繁創建銷毀、實作重復利用,
-
好處:
- 提高回應速度
- 降低資源消耗
- 便于執行緒管理
- corePoolSize:核心池大小
- maximumPoolSize:最大執行緒數
- keepAliveTime:執行緒沒有任務時最多保持多少時間后終結
-
相關api:
- ExecutorService :真正的執行緒池介面,常見子類ThreadPoolExecutor
- 相關方法:
- 相關方法:
- ExecutorService :真正的執行緒池介面,常見子類ThreadPoolExecutor
-
Executors:工具類,用于創建并回傳不同型別的執行緒池
public class MyThread implements Runnable {
private int number=10;
private boolean flag = true;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName());
}
}
public static void main(String[] args) {
//創建執行緒池,引數為大小
ExecutorService service = Executors.newFixedThreadPool(10);
//執行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
// 關閉鏈接
service.shutdownNow();
}
}
/*
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-2
pool-1-thread-2
pool-1-thread-2
pool-1-thread-2
pool-1-thread-2
pool-1-thread-2
pool-1-thread-2
pool-1-thread-2
pool-1-thread-2
pool-1-thread-2
pool-1-thread-5
pool-1-thread-5
pool-1-thread-5
pool-1-thread-5
pool-1-thread-5
pool-1-thread-5
pool-1-thread-5
pool-1-thread-5
pool-1-thread-5
pool-1-thread-5
pool-1-thread-4
pool-1-thread-4
pool-1-thread-4
pool-1-thread-4
pool-1-thread-4
pool-1-thread-4
pool-1-thread-4
pool-1-thread-4
pool-1-thread-4
pool-1-thread-4
pool-1-thread-3
pool-1-thread-3
pool-1-thread-3
pool-1-thread-3
pool-1-thread-3
pool-1-thread-3
pool-1-thread-3
pool-1-thread-3
pool-1-thread-3
pool-1-thread-3
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/553686.html
標籤:Java
上一篇:關于切片引數傳遞的問題
下一篇:返回列表