java多執行緒總結-同步容器與併發容器的對比與介紹
目錄
- 1 容器集簡單介紹
- 2 同步容器
- 3 併發容器
- 4 案例講解
- 4.1 Map/Set
- 4.2 List
- 4.3 Queue
- 4.3.1 ConcurrentLinkedQueue
- 4.3.2 阻塞佇列LinkedBlockingQueue
- 4.3.3 BlockingQueue
- 4.3.4 延時佇列DelayQueue
- 4.3.5 轉移佇列LinkedTransferQueue
- 4.3.6 SynchronousQueue
1 容器集簡單介紹
java.util包下面的容器集主要有兩種,一種是Collection介面下面的List和Set,一種是Map,
大致結構如下:
- Collection
- List
- LinkedList
- ArrayList
- Vector
- Stack
- Set
- HashSet
- TreeSet
- LinkedSet
- List
- Map
- Hashtable
- HashMap
- WeakHashMap
2 同步容器
同步容器也叫執行緒安全容器,是通過syncrhoized關鍵字對執行緒不安全的操作進行加鎖來保證執行緒安全的
1.Vector、Stack、HashTable
2.Collections 工具類中提供的同步集合類
Collections類是一個工具類,相當於Arrays類對於Array的支援,Collections類中提供了大量對集合或者容器進行排序、查詢的方法。它還提供了幾個靜態方法來建立同步容器類:
3 併發容器
java.util.concurrent提供了多種執行緒安全容器,大多數是使用系統底層技術實現的執行緒安全,也叫併發容器,類似native。Java8中使用CAS。
4 案例講解
這裡主要介紹一些常見的同步容器和併發容器,通過案例輸出結果對比進行介紹
我大致分為了三類Map/Set,List,Queue來進行講解,但一個Map/Set,只介紹了Map,因為在java的設計中,Set就是Map,說白了就是隻有Key沒有Value的Map,好了,現在開始進入正題
4.1 Map/Set
程式碼中new了三個Map,HashTable,ConcurrentHashMap,ConcurrentSkipListMap比較每個map的執行效率,起100個執行緒向map中存放10000條隨機數,並通過門閂CountDownLatch控制執行狀態,輸出執行時間
/**
* 併發容器 - ConcurrentMap
*/
package com.bernardlowe.concurrent.t06;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.CountDownLatch;
public class Test_01_ConcurrentMap {
public static void main(String[] args) {
final Map<String, String> map = new Hashtable<>();
// final Map<String, String> map = new ConcurrentHashMap<>();
// final Map<String, String> map = new ConcurrentSkipListMap<>();
final Random r = new Random();
Thread[] array = new Thread[100];
final CountDownLatch latch = new CountDownLatch(array.length);
long begin = System.currentTimeMillis();
for(int i = 0; i < array.length; i++){
array[i] = new Thread(new Runnable() {
@Override
public void run() {
for(int j = 0; j < 10000; j++){
map.put("key"+r.nextInt(100000000), "value"+r.nextInt(100000));
}
latch.countDown();
}
});
}
for(Thread t : array){
t.start();
}
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("執行時間為 : " + (end-begin) + "毫秒!");
}
}
Hashtable結果:
ConcurrentHashMap結果:
ConcurrentSkipListMap結果:
ConcurrentHashMap的底層是雜湊實現的同步Map(Set)
ConcurrentSkipListMap內部是SkipList(跳錶)結構實現的非阻塞讀/寫/刪除 的 Map,它的value是有序儲存的, 而且其內部是由縱橫連結串列組成,在JDK1.8中,ConcurrentHashMap的效能和儲存空間要優於ConcurrentSkipListMap
為了讓測試資料結果對比更加直觀,我這裡故意將生成的隨機數調的比較大。這裡需要注意一下,在測試的時候,如果機器效能比較好,可能結果會出現誤差,因為System.currentTimeMillis(),這個方法呼叫了個native方法,獲取的時間精度會依賴於作業系統的實現機制,具體為什麼,可以看看這篇文章http://blog.sina.com.cn/s/blog_6b8bd9d80101fe8t.html。但我按照文件的辦法將System.currentTimeMillis()改為System.nanoTime(),發現並沒有解決這個問題,可能是因為並沒有達到納秒級別吧。
4.2 List
下面程式碼與4.1的程式碼類似,也是new了三個List,ArrayList,Vector,CopyOnWriteArrayList,起100個執行緒向map中存放1000條隨機數,並通過門閂CountDownLatch控制執行狀態,輸出執行時間和最後list的的長度。由於ArrayList是執行緒不安全,在多執行緒執行的時候,需要try{}catch{},否則會因為陣列越界而報錯,因為ArrayList底層是一個長度動態擴充套件的陣列
/**
* 併發容器 - CopyOnWriteList
*/
package com.bernardlowe.concurrent.t06;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Vector;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
public class Test_02_CopyOnWriteList {
public static void main(String[] args) {
final List<String> list = new ArrayList<String>(); // 執行緒不安全
// final List<String> list = new Vector<>(); // 執行緒安全
// final List<String> list = new CopyOnWriteArrayList<>(); // 執行緒安全
final Random r = new Random();
Thread[] array = new Thread[100];
final CountDownLatch latch = new CountDownLatch(array.length);
long begin = System.currentTimeMillis();
for(int i = 0; i < array.length; i++){
array[i] = new Thread(new Runnable() {
@Override
public void run() {
for(int j = 0; j < 1000; j++){
try {
list.add("value" + r.nextInt(100000));
} catch (Exception e) {
}
}
latch.countDown();
}
});
}
for(Thread t : array){
t.start();
}
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("執行時間為 : " + (end-begin) + "毫秒!");
System.out.println("List.size() : " + list.size());
}
}
ArrayList結果:因為ArrayList是執行緒不安全的,所以在多執行緒環境中,可能會丟失資料
Vector結果:
CopyOnWriteArrayList結果:
CopyOnWriteArrayList是讀寫分離的,寫時複製出一個新的陣列,完成插入、修改或者移除操作後將新陣列賦值給array,讀取時直接讀取最新的陣列,所以在寫操作時,效率非常低(雖然寫比較慢,但它在刪除陣列頭和尾還是很快的)
從上面三個結果可以看出,CopyOnWriteArrayList雖然保證了執行緒安全,但它的寫操作效率太低了,但相比Vector,併發安全且效能比Vector好,Vector是增刪改查方法都加了synchronized,保證同步,但是每個方法執行的時候都要去獲得鎖,效能就會大大下降,而CopyOnWriteArrayList 只是在增刪改上加鎖,但是讀不加鎖,在讀方面的效能就好於Vector,CopyOnWriteArrayList支援讀多寫少的併發情況,所以CopyOnWriteArrayList是不會存在髒讀問題的
4.3 Queue
這一節主要介紹一些併發佇列的常用api
4.3.1 ConcurrentLinkedQueue
基礎連結串列同步佇列
peek() -> 檢視queue中的首資料
poll() -> 獲取queue中的首資料
/**
* 併發容器 - ConcurrentLinkedQueue
* 佇列 - 連結串列實現的。
*/
package com.bernardlowe.concurrent.t06;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
public class Test_03_ConcurrentLinkedQueue {
public static void main(String[] args) {
Queue<String> queue = new ConcurrentLinkedQueue<>();
//向佇列中增加10個數據
for(int i = 0; i < 10; i++){
queue.offer("value" + i);
}
System.out.println(queue);
System.out.println(queue.size());
// peek() -> 檢視queue中的首資料,
System.out.println("首資料 " + queue.peek());
System.out.println("佇列長度 "+ queue.size());
System.out.println("===================");
// poll() -> 獲取queue中的首資料
System.out.println("首資料 " + queue.peek());
System.out.println("佇列長度 "+ queue.size());
}
}
結果:
4.3.2 阻塞佇列LinkedBlockingQueue
阻塞佇列,佇列容量不足自動阻塞,佇列容量為0自動阻塞。
put & take - 自動阻塞
put自動阻塞, 佇列容量滿後,自動阻塞
take自動阻塞方法, 佇列容量為0後,自動阻塞
/**
* 併發容器 - LinkedBlockingQueue
* 阻塞容器。
*/
package com.bernardlowe.concurrent.t06;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
public class Test_04_LinkedBlockingQueue {
final BlockingQueue<String> queue = new LinkedBlockingQueue<>();
final Random r = new Random();
public static void main(String[] args) {
final Test_04_LinkedBlockingQueue t = new Test_04_LinkedBlockingQueue();
new Thread(new Runnable() {
@Override
public void run() {
while(true){
try {
t.queue.put("value"+t.r.nextInt(1000));
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "producer").start();
for(int i = 0; i < 3; i++){
new Thread(new Runnable() {
@Override
public void run() {
while(true){
try {
System.out.println(Thread.currentThread().getName() +
" - " + t.queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "consumer"+i).start();
}
}
}
結果:
結果就是一個簡單的生產者消費者
4.3.3 BlockingQueue
底層陣列實現的有界佇列,當容量不足的時候,有阻塞能力,根據呼叫API(add/put/offer)不同,有不同特性
這裡主要介紹三個api方法add,put,offer
- add方法在容量不足的時候,丟擲異常。
- put方法在容量不足的時候,阻塞等待。
offer方法
單引數offer方法,不阻塞。容量不足的時候,返回false。當前新增資料操作放棄。
三引數offer方法(offer(value,times,timeunit)),容量不足的時候,阻塞times時長(單位為timeunit),如果在阻塞時長內,有容量空閒,新增資料返回true。如果阻塞時長範圍內,無容量空閒,放棄新增資料,返回false。
/**
* 併發容器 - ArrayBlockingQueue
* 有界容器。
*/
package com.bernardlowe.concurrent.t06;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
public class Test_05_ArrayBlockingQueue {
final BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
public static void main(String[] args) {
final Test_05_ArrayBlockingQueue t = new Test_05_ArrayBlockingQueue();
for(int i = 0; i < 5; i++){
// 1.add method
System.out.println("add method : " + t.queue.add("value"+i));
// 2.put method
// try {
// t.queue.put("put"+i);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println("put method : " + i);
// 3.offer method
// System.out.println("offer method : " + t.queue.offer("value"+i));
// try {
// System.out.println("offer method : " +
// t.queue.offer("value"+i, 1, TimeUnit.SECONDS));
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}
System.out.println(t.queue);
}
}
add方法結果:容量不足的時候,丟擲異常
put方法結果:容量不足的時候,阻塞等待
單/多引數offer方法結果:
單引數offer:容量不足,直接返回結果,不阻塞
多引數offer:容量不足,阻塞
4.3.4 延時佇列DelayQueue
延時佇列。根據比較機制,實現自定義處理順序的佇列。常用於定時任務。
如:定時關機。
具體示例程式碼如下
/**
* 併發容器 - DelayQueue
*/
package com.bernardlowe.concurrent.t06;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
public class Test_06_DelayQueue {
static BlockingQueue<MyTask_06> queue = new DelayQueue<>();
public static void main(String[] args) throws InterruptedException {
long value = System.currentTimeMillis();
MyTask_06 task1 = new MyTask_06(value + 2000);
MyTask_06 task2 = new MyTask_06(value + 1000);
MyTask_06 task3 = new MyTask_06(value + 3000);
MyTask_06 task4 = new MyTask_06(value + 2500);
MyTask_06 task5 = new MyTask_06(value + 1500);
queue.put(task1);
queue.put(task2);
queue.put(task3);
queue.put(task4);
queue.put(task5);
System.out.println(queue);
System.out.println(value);
for(int i = 0; i < 5; i++){
System.out.println(queue.take());
}
}
}
class MyTask_06 implements Delayed {
private long compareValue;
public MyTask_06(long compareValue){
this.compareValue = compareValue;
}
/**
* 比較大小。自動實現升序
* 建議和getDelay方法配合完成。
* 如果在DelayQueue是需要按時間完成的計劃任務,必須配合getDelay方法完成。
*/
@Override
public int compareTo(Delayed o) {
return (int)(this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
}
/**
* 獲取計劃時長的方法。
* 根據引數TimeUnit來決定,如何返回結果值。
*/
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(compareValue - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
@Override
public String toString(){
return "Task compare value is : " + this.compareValue;
}
}
結果:
4.3.5 轉移佇列LinkedTransferQueue
這裡主要是兩個方法的區別,add和transfer
- add - 佇列會儲存資料,不做阻塞等待。
- transfer - 是TransferQueue的特有方法。必須有消費者(take()方法的呼叫者)。
/**
* 併發容器 - LinkedTransferQueue
* 轉移佇列
*/
package com.bernardlowe.concurrent.t06;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TransferQueue;
public class Test_07_TransferQueue {
TransferQueue<String> queue = new LinkedTransferQueue<>();
public static void main(String[] args) {
final Test_07_TransferQueue t = new Test_07_TransferQueue();
/*new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " thread begin " );
System.out.println(Thread.currentThread().getName() + " - " + t.queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "output thread").start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
t.queue.transfer("test string");
} catch (InterruptedException e) {
e.printStackTrace();
}*/
new Thread(new Runnable() {
@Override
public void run() {
try {
t.queue.transfer("test string");
// t.queue.add("test string");
System.out.println("add ok");
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " thread begin " );
System.out.println(Thread.currentThread().getName() + " - " + t.queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "output thread").start();
}
}
這裡的transfer()和take()都是阻塞方法,take先請求接收資料或者transfer先發送資料,都會進行阻塞等待。
舉個例子,transfer()就相當與手機打電話,當A給B打電話,B必須接收到電話訊號接聽才能進行通話,否則A會一直等待
add()就相當於A給B發簡訊,簡訊已經存到了運營商那邊,等待B接收,不管發簡訊時B是否線上
4.3.6 SynchronousQueue
該佇列一個容量為0的佇列,是一個特殊的TransferQueue,它和TransferQueue很像,但這個佇列必須要有消費執行緒才行
又兩個方法add,put
add方法,無阻塞。若沒有消費執行緒阻塞等待資料,則丟擲異常。
put方法,有阻塞。若沒有消費執行緒阻塞等待資料,則阻塞。
/**
* 併發容器 - SynchronousQueue
*/
package com.bernardlowe.concurrent.t06;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
public class Test_08_SynchronusQueue {
BlockingQueue<String> queue = new SynchronousQueue<>();
public static void main(String[] args) {
final Test_08_SynchronusQueue t = new Test_08_SynchronusQueue();
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " thread begin " );
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " - " + t.queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "output thread").start();
/*try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
// t.queue.add("test add");
try {
t.queue.put("test put");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " queue size : " + t.queue.size());
}
}
將t.queue.add("test add");
的註釋開啟,t.queue.put("test put");
加上註釋
add方法異常結果: 因為它是一個容量為0的佇列
相關推薦
java多執行緒總結-同步容器與併發容器的對比與介紹
目錄 1 容器集簡單介紹 2 同步容器 3 併發容器 4 案例講解 4.1 Map/Set 4.2 List 4.3 Queue 4.3.1 C
js單執行緒與java多執行緒、同步與非同步
寫這篇部落格源於想對比一下單執行緒js和多執行緒java兩種語言的區別。 定義區: 單執行緒:只能執行一個任務,只有在完成執行後,才能繼續執行其他的任務。 多執行緒:有多個執行緒,可以同時執行多個任務。
最全java多執行緒總結2--如何進行執行緒同步
上篇對執行緒的一些基礎知識做了總結,本篇來對多執行緒程式設計中最重要,也是最麻煩的一個部分——同步,來做個總結。 建立執行緒並不難,難的是如何讓多個執行緒能夠良好的協作執行,大部分需要多執行緒處理的事情都不是完全獨立的,大都涉及到資料的共享,本篇是對執行緒同步的一個總結,如有紕漏的地方,歡迎在評論中指
Java多執行緒學習筆記21之單例模式與多執行緒
詳細程式碼見:github程式碼地址 第六章 單例模式與多執行緒 前言: 我之前已經開設了23個設計模式這個專欄,介紹了很多的Java設計模式,其中一些模式對於絕 大多數程式語言設計思想都是類似的,需要了解單例模式的可以去看看。 我們在實際開發中經常用到單例模式,但
Java學習筆記(一) Java多執行緒 synchronized同步方法
1.提出問題-例項變數非執行緒安全 如果多個執行緒同時訪問1個物件的例項變數,則可能出現"非執行緒安全"問題。 1.1 何為非執行緒安全? 我的理解是多個執行緒對一個例項變數操作會出現值被更改,不同步的情況。 1.2 舉例 1.2.1 有私有變數的類HasPr
Java學習筆記(二) Java多執行緒 synchronized同步方法-防止髒讀
1. 髒讀 在給一個物件賦值的時候進行了同步, 但是在取值的時候可能出現意外,此值已經被其他執行緒修改了,這種情況就是髒讀 1.1 PublicVar類 public class PublicVar { public String userName =
Java多執行緒的同步機制(synchronized)
原文地址 一段synchronized的程式碼被一個執行緒執行之前,他要先拿到執行這段程式碼的許可權,在 java裡邊就是拿到某個同步物件的鎖(一個物件只有一把鎖); 如果這個時候同步物件的鎖被其他執行緒拿走了,他(這個執行緒)就只能等了(執行緒阻塞在鎖池 等待佇列中)。 取到鎖後,他就開始執行同步程式碼
Java多執行緒總結之執行緒安全佇列Queue
在Java多執行緒應用中,佇列的使用率很高,多數生產消費模型的首選資料結構就是佇列。Java提供的執行緒安全的Queue可以分為阻塞佇列和非阻塞佇列,其中阻塞佇列的典型例子是BlockingQueue,非阻塞佇列的典型例子是ConcurrentLinkedQueue,在實際
java多執行緒之同步鎖(Lock)
從Java5開始,提供了Lock, Lock提供了比synchronized方法和synchronized程式碼塊更廣泛的鎖定操作,Lock可以實現更靈活的結構,並且支援多個相關的Condition物件(物件監視器)。 Lock是控制多個執行緒對共享
java多執行緒解決同步問題的幾種方式、原理和程式碼
生產者類: publicclassProducerextendsThread{// 每次生產的產品數量privateint num;// 所在放置的倉庫privateStorage storage;// 建構函式,設定倉庫publicProducer(Storage storage){this.stora
java多執行緒之同步計數器
同步計數器之--CountDownLatch 使用場景:當用戶上傳檔案,我們可以多開幾個執行緒來上傳檔案,當所有的執行緒上傳完成後,檔案才算上傳完成,在此場景下可以使用CountDownLatch,當每個執行緒上傳完成,呼叫CountDownLatch.countDow
java 多執行緒 總結 案例
學完東西后,要學會總結,學會記錄筆記,這樣才會有更大的收穫 首先我們瞭解執行緒和程序的基本概念 一、概念(程式 程序 執行緒) 1、程式:指令集 靜態概念 2、程序:作業系統 排程程式 動態概念 3:執行緒:在程序內多條執行路徑 真正的多執行緒是指多個cpu 二、建立
Java多執行緒、同步非同步及阻塞和非阻塞
1、程序和執行緒的概念 程序:執行中的應用程式稱為程序,擁有系統資源(cpu、記憶體) 執行緒:程序中的一段程式碼,一個程序中可以有多段程式碼。本身不擁有資源(共享所在程序的資源); 在java中,程式入口被自動建立為主執行緒,在主執行緒中可以建立多個子執
Java多執行緒總結(6)— 執行緒池的基本使用和執行流程分析
1 執行緒池的實現原理及基本類結構 合理利用執行緒池能夠帶來三個好處。 降低資源消耗。通過重複利用已建立的執行緒降低執行緒建立和銷燬造成的消耗。 提高響應速度。當任務到達時,任務可以不需要等到執行緒建立就能立即執行。 提高執行緒的可管理性。執行緒是稀缺
java多執行緒總結(多處精摘,面試總結)
用多執行緒只有一個目的,那就是更好的利用cpu的資源,因為所有的多執行緒程式碼都可以用單執行緒來實現。說這個話其實只有一半對,因為反應“多角色”的程式程式碼,最起碼每個角色要給他一個執行緒吧,否則連實際場景都無法模擬,當然也沒法說能用單執行緒來實現:比如最常見
最全java多執行緒總結3——瞭解阻塞佇列和執行緒安全集合不
看了前兩篇你肯定已經理解了 java 併發程式設計的低層構建。然而,在實際程式設計中,應該經可能的遠離低層結構,畢竟太底層的東西用起來是比較容易出錯的,特別是併發程式設計,既難以除錯,也難以發現問題,我們還是使用由併發處理的專業人員實現的較高層次的結構要方便、安全得多。 阻塞佇列 對於許多執行緒問題,
java多執行緒總結(系列一)
1、Java中的Thread建立和狀態 (1)新建(New) 建立後尚未啟動。ex:Thread t = new Thread(){//重寫run方法}; (2)可執行(Runnable) 可能正在執行,也可能正在等待 CPU 時間片。包含了作業系統執行緒狀態中的 Running 和 Ready。 (
Java多執行緒之併發包,併發佇列
目錄 1 併發包 2 併發佇列 1 併發包 1.1同步容器類 1.1.1Vector與ArrayList區別 1.1.1.1 ArrayList是最常用的List實現類,內部是通過陣列實現的,它允許對
Java多執行緒——物件及變數的併發訪問
Java多線系列文章是Java多執行緒的詳解介紹,對多執行緒還不熟悉的同學可以先去看一下我的這篇部落格Java基礎系列3:多執行緒超詳細總結,這篇部落格從巨集觀層面介紹了多執行緒的整體概況,接下來的幾篇文章是對多執行緒的深入剖析。 本篇文章主要介紹Java多執行緒中的同步,也就是如何在Java語
Java多執行緒學習與總結(Join)
join()方法的用法: join()是主執行緒 等待子執行緒的終止。也就是在子執行緒呼叫了 join() 方法後面的程式碼,只有等到子執行緒結束了才能執行。 例子如下: Java程式碼 p