Java併發程式設計指南
多執行緒是實現併發機制的一種有效手段。在 Java 中實現多執行緒有兩種手段,一種是繼承 Thread 類,另一種就是實現 Runnable/Callable 介面。
一、同步
1.1 synchronized 關鍵字,用來給物件和方法或者程式碼塊加鎖。
同步方法 synchronized T methodName(){} 同步方法鎖定的是當前物件。當多執行緒通過同一個物件引用多次呼叫當前同步方法時,需同步執行。靜態同步方法,鎖的是當前型別的類物件。
同步方法隻影響鎖定同一個鎖物件的同步方法。不影響其他執行緒呼叫非同步方法,或呼叫其他鎖資源的同步方法。
鎖可重入。 同一個執行緒,多次呼叫同步程式碼,鎖定同一個鎖物件,可重入。子類同步方法覆蓋父類同步方法。可以指定呼叫父類的同步方法,相當於鎖的重入。
當同步方法中發生異常的時候,自動釋放鎖資源。不會影響其他執行緒的執行。
同步程式碼塊 T methodName(){synchronized(object){} } 同步程式碼塊在執行時,是鎖定 object 物件。當多個執行緒呼叫同一個方法時,鎖定物件不變的情況下,需同步執行。 同步程式碼塊的同步粒度更加細緻,效率更高。 T methodName(){ synchronized(this){} } 當鎖定物件為 this 時,相當於同步方法。
同步程式碼一旦加鎖後,那麼會有一個臨時的鎖引用執行鎖物件,和真實的引用無直接關聯。在鎖未釋放之前,修改鎖物件引用,不會影響同步程式碼的執行。
Java 虛擬機器中的同步(Synchronization)基於進入和退出管程(Monitor)物件實現。同步方法 並不是由 monitor enter 和 monitor exit 指令來實現同步的,而是由方法呼叫指令讀取執行時常量池中方法的 ACC_SYNCHRONIZED 標誌來隱式實現的。在 Java 虛擬機器(HotSpot)中,monitor 是由 ObjectMonitor 實現的。
1 /** 2 * synchronized關鍵字 3 * 鎖物件。synchronized(this)和synchronized方法都是鎖當前物件。 4 */ 5 package concurrent.t01; 6 7 import java.util.concurrent.TimeUnit; 8 9 public class Test_01 { 10 private int count = 0; 11 private Object o = new Object(); 12 13 public void testSync1(){synchronized關鍵字14 synchronized(o){ 15 System.out.println(Thread.currentThread().getName() 16 + " count = " + count++); 17 } 18 } 19 20 public void testSync2(){ 21 synchronized(this){ 22 System.out.println(Thread.currentThread().getName() 23 + " count = " + count++); 24 } 25 } 26 27 public synchronized void testSync3(){ 28 System.out.println(Thread.currentThread().getName() 29 + " count = " + count++); 30 try { 31 TimeUnit.SECONDS.sleep(3); 32 } catch (InterruptedException e) { 33 e.printStackTrace(); 34 } 35 } 36 37 public static void main(String[] args) { 38 final Test_01 t = new Test_01(); 39 new Thread(new Runnable() { 40 @Override 41 public void run() { 42 t.testSync3(); 43 } 44 }).start(); 45 new Thread(new Runnable() { 46 @Override 47 public void run() { 48 t.testSync3(); 49 } 50 }).start(); 51 } 52 53 }
1.2 volatile 關鍵字
變數的執行緒可見性。在 CPU 計算過程中,會將計算過程需要的資料載入到 CPU 計算快取中,當 CPU 計算中斷時,有可能重新整理快取,重新讀取記憶體中的資料。線上程執行的過程中,如果某變數被其他執行緒修改,可能造成資料不一致的情況,從而導致結果錯誤。而 volatile修飾的變數是執行緒可見的,當 JVM 解釋 volatile 修飾的變數時,會通知 CPU,在計算過程中,每次使用變數參與計算時,都會檢查記憶體中的資料是否發生變化,而不是一直使用 CPU 快取中的資料,可以保證計算結果的正確。volatile 只是通知底層計算時,CPU 檢查記憶體資料,而不是讓一個變數在多個執行緒中同步。
1 /** 2 * volatile關鍵字 3 * volatile的可見性 4 * 通知OS作業系統底層,在CPU計算過程中,都要檢查記憶體中資料的有效性。保證最新的記憶體資料被使用。 5 * 6 */ 7 package concurrent.t01; 8 9 import java.util.concurrent.TimeUnit; 10 11 public class Test_09 { 12 13 volatile boolean b = true; 14 15 void m(){ 16 System.out.println("start"); 17 while(b){} 18 System.out.println("end"); 19 } 20 21 public static void main(String[] args) { 22 final Test_09 t = new Test_09(); 23 new Thread(new Runnable() { 24 @Override 25 public void run() { 26 t.m(); 27 } 28 }).start(); 29 30 try { 31 TimeUnit.SECONDS.sleep(1); 32 } catch (InterruptedException e) { 33 // TODO Auto-generated catch block 34 e.printStackTrace(); 35 } 36 37 t.b = false; 38 } 39 40 }volatile的可見性
1 /** 2 * volatile關鍵字 3 * volatile的非原子性問題 4 * volatile, 只能保證可見性,不能保證原子性。 5 * 不是枷鎖問題,只是記憶體資料可見。 6 */ 7 package concurrent.t01; 8 9 import java.util.ArrayList; 10 import java.util.List; 11 12 public class Test_10 { 13 14 volatile int count = 0; 15 /*synchronized*/ void m(){ 16 for(int i = 0; i < 10000; i++){ 17 count++; 18 } 19 } 20 21 public static void main(String[] args) { 22 final Test_10 t = new Test_10(); 23 List<Thread> threads = new ArrayList<>(); 24 for(int i = 0; i < 10; i++){ 25 threads.add(new Thread(new Runnable() { 26 @Override 27 public void run() { 28 t.m(); 29 } 30 })); 31 } 32 for(Thread thread : threads){ 33 thread.start(); 34 } 35 for(Thread thread : threads){ 36 try { 37 thread.join(); 38 } catch (InterruptedException e) { 39 // TODO Auto-generated catch block 40 e.printStackTrace(); 41 } 42 } 43 System.out.println(t.count); 44 } 45 }volatile的非原子性問題
1.3 wait¬ify
當執行緒執行wait()方法時候,會釋放當前的鎖,然後讓出CPU,進入等待狀態。只有當 notify/notifyAll() 被執行時候,才會喚醒一個或多個正處於等待狀態的執行緒,然後繼續往下執行,直到執行完synchronized 程式碼塊的程式碼或是中途遇到wait() ,再次釋放鎖。
由於 wait()、notify/notifyAll() 在synchronized 程式碼塊執行,說明當前執行緒一定是獲取了鎖的。wait()、notify/notifyAll() 方法是Object的本地final方法,無法被重寫。
1 /** 2 * 生產者消費者 3 * wait¬ify 4 * wait/notify都是和while配合應用的。可以避免多執行緒併發判斷邏輯失效問題。各位想想為什麼不能用if 5 */ 6 package concurrent.t04; 7 8 import java.util.LinkedList; 9 import java.util.concurrent.TimeUnit; 10 11 public class TestContainer01<E> { 12 13 private final LinkedList<E> list = new LinkedList<>(); 14 private final int MAX = 10; 15 private int count = 0; 16 17 public synchronized int getCount(){ 18 return count; 19 } 20 21 public synchronized void put(E e){ 22 while(list.size() == MAX){ 23 try { 24 this.wait(); 25 } catch (InterruptedException e1) { 26 e1.printStackTrace(); 27 } 28 } 29 30 list.add(e); 31 count++; 32 this.notifyAll(); 33 } 34 35 public synchronized E get(){ 36 E e = null; 37 while(list.size() == 0){ 38 try{ 39 this.wait(); 40 } catch (InterruptedException e1) { 41 e1.printStackTrace(); 42 } 43 } 44 e = list.removeFirst(); 45 count--; 46 this.notifyAll(); 47 return e; 48 } 49 50 public static void main(String[] args) { 51 final TestContainer01<String> c = new TestContainer01<>(); 52 for(int i = 0; i < 10; i++){ 53 new Thread(new Runnable() { 54 @Override 55 public void run() { 56 for(int j = 0; j < 5; j++){ 57 System.out.println(c.get()); 58 } 59 } 60 }, "consumer"+i).start(); 61 } 62 try { 63 TimeUnit.SECONDS.sleep(2); 64 } catch (InterruptedException e) { 65 // TODO Auto-generated catch block 66 e.printStackTrace(); 67 } 68 for(int i = 0; i < 2; i++){ 69 new Thread(new Runnable() { 70 @Override 71 public void run() { 72 for(int j = 0; j < 25; j++){ 73 c.put("container value " + j); 74 } 75 } 76 }, "producer"+i).start(); 77 } 78 } 79 80 }wait¬ify
1.4 AtomicXxx 型別
原子型別。
在 concurrent.atomic 包中定義了若干原子型別,這些型別中的每個方法都是保證了原子操作的。多執行緒併發訪問原子型別物件中的方法,不會出現資料錯誤。在多執行緒開發中,如果某資料需要多個執行緒同時操作,且要求計算原子性,可以考慮使用原子型別物件。
- AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
- AtomicIntegerArray,AtomicLongArray
- AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
- AtomicMarkableReference,AtomicStampedReference,AtomicReferenceArray
注意:原子型別中的方法 是保證了原子操作,但多個方法之間是沒有原子性的。即訪問對2個或2個以上的atomic變數(或者對單個atomic變數進行2次或2次以上的操作),還是需要同步。
1 /** 2 * AtomicXxx 3 * 同步型別 4 * 原子操作型別。 其中的每個方法都是原子操作。可以保證執行緒安全。 5 */ 6 package concurrent.t01; 7 8 import java.util.ArrayList; 9 import java.util.List; 10 import java.util.concurrent.atomic.AtomicInteger; 11 12 public class Test_11 { 13 AtomicInteger count = new AtomicInteger(0); 14 void m(){ 15 for(int i = 0; i < 10000; i++){ 16 /*if(count.get() < 1000)*/ 17 count.incrementAndGet(); 18 } 19 } 20 21 public static void main(String[] args) { 22 final Test_11 t = new Test_11(); 23 List<Thread> threads = new ArrayList<>(); 24 for(int i = 0; i < 10; i++){ 25 threads.add(new Thread(new Runnable() { 26 @Override 27 public void run() { 28 t.m(); 29 } 30 })); 31 } 32 for(Thread thread : threads){ 33 thread.start(); 34 } 35 for(Thread thread : threads){ 36 try { 37 thread.join(); 38 } catch (InterruptedException e) { 39 // TODO Auto-generated catch block 40 e.printStackTrace(); 41 } 42 } 43 System.out.println(t.count.intValue()); 44 } 45 }AtomicXxx
1.5 CountDownLatch 門閂
門閂是 concurrent 包中定義的一個型別,是用於多執行緒通訊的一個輔助型別。門閂相當於在一個門上加多個鎖,當執行緒呼叫 await 方法時,會檢查門閂數量,如果門閂數量大於 0,執行緒會阻塞等待。當執行緒呼叫 countDown 時,會遞減門閂的數量,當門閂數量為 0 時,await 阻塞執行緒可執行。
1 /** 2 * 門閂 - CountDownLatch 3 * 可以和鎖混合使用,或替代鎖的功能。 4 * 在門閂未完全開放之前等待。當門閂完全開放後執行。 5 * 避免鎖的效率低下問題。 6 */ 7 package concurrent.t01; 8 9 import java.util.concurrent.CountDownLatch; 10 import java.util.concurrent.TimeUnit; 11 12 public class Test_15 { 13 CountDownLatch latch = new CountDownLatch(5); 14 15 void m1(){ 16 try { 17 latch.await();// 等待門閂開放。 18 } catch (InterruptedException e) { 19 e.printStackTrace(); 20 } 21 System.out.println("m1() method"); 22 } 23 24 void m2(){ 25 for(int i = 0; i < 10; i++){ 26 if(latch.getCount() != 0){ 27 System.out.println("latch count : " + latch.getCount()); 28 latch.countDown(); // 減門閂上的鎖。 29 } 30 try { 31 TimeUnit.MILLISECONDS.sleep(500); 32 } catch (InterruptedException e) { 33 // TODO Auto-generated catch block 34 e.printStackTrace(); 35 } 36 System.out.println("m2() method : " + i); 37 } 38 } 39 40 public static void main(String[] args) { 41 final Test_15 t = new Test_15(); 42 new Thread(new Runnable() { 43 @Override 44 public void run() { 45 t.m1(); 46 } 47 }).start(); 48 49 new Thread(new Runnable() { 50 @Override 51 public void run() { 52 t.m2(); 53 } 54 }).start(); 55 } 56 57 }CountDownLatch
1.6 鎖的重入
在 Java 中,同步鎖是可以重入的。只有同一執行緒呼叫同步方法或執行同步程式碼塊,對同一個物件加鎖時才可重入。
當執行緒持有鎖時,會在 monitor 的計數器中執行遞增計算,若當前執行緒呼叫其他同步程式碼,且同步程式碼的鎖物件相同時,monitor 中的計數器繼續遞增。每個同步程式碼執行結束,monitor 中的計數器都會遞減,直至所有同步程式碼執行結束,monitor 中的計數器為 0 時,釋放鎖標記,_Owner 標記賦值為 null。
1 /** 2 * 鎖可重入。 同一個執行緒,多次呼叫同步程式碼,鎖定同一個鎖物件,可重入。 3 */ 4 package concurrent.t01; 5 6 import java.util.concurrent.TimeUnit; 7 8 public class Test_06 { 9 10 synchronized void m1(){ // 鎖this 11 System.out.println("m1 start"); 12 try { 13 TimeUnit.SECONDS.sleep(2); 14 } catch (InterruptedException e) { 15 e.printStackTrace(); 16 } 17 m2(); 18 System.out.println("m1 end"); 19 } 20 synchronized void m2(){ // 鎖this 21 System.out.println("m2 start"); 22 try { 23 TimeUnit.SECONDS.sleep(1); 24 } catch (InterruptedException e) { 25 e.printStackTrace(); 26 } 27 System.out.println("m2 end"); 28 } 29 30 public static void main(String[] args) { 31 32 new Test_06().m1(); 33 34 } 35 36 }鎖可重入
1.7 ReentrantLock
重入鎖,建議應用的同步方式。相對效率比 synchronized 高。量級較輕。使用重入鎖, 必須手工釋放鎖標記。一般都是在 finally 程式碼塊中定義釋放鎖標記的 unlock 方法。
1 /** 2 * ReentrantLock 3 * 重入鎖 4 */ 5 package concurrent.t03; 6 7 import java.util.concurrent.TimeUnit; 8 import java.util.concurrent.locks.Lock; 9 import java.util.concurrent.locks.ReentrantLock; 10 11 public class Test_01 { 12 Lock lock = new ReentrantLock(); 13 14 void m1(){ 15 try{ 16 lock.lock(); // 加鎖 17 for(int i = 0; i < 10; i++){ 18 TimeUnit.SECONDS.sleep(1); 19 System.out.println("m1() method " + i); 20 } 21 }catch(InterruptedException e){ 22 e.printStackTrace(); 23 }finally{ 24 lock.unlock(); // 解鎖 25 } 26 } 27 28 void m2(){ 29 lock.lock(); 30 System.out.println("m2() method"); 31 lock.unlock(); 32 } 33 34 public static void main(String[] args) { 35 final Test_01 t = new Test_01(); 36 new Thread(new Runnable() { 37 @Override 38 public void run() { 39 t.m1(); 40 } 41 }).start(); 42 try { 43 TimeUnit.SECONDS.sleep(1); 44 } catch (InterruptedException e) { 45 e.printStackTrace(); 46 } 47 new Thread(new Runnable() { 48 @Override 49 public void run() { 50 t.m2(); 51 } 52 }).start(); 53 } 54 }重入鎖
1 /** 2 * 嘗試鎖 3 */ 4 package concurrent.t03; 5 6 import java.util.concurrent.TimeUnit; 7 import java.util.concurrent.locks.Lock; 8 import java.util.concurrent.locks.ReentrantLock; 9 10 public class Test_02 { 11 Lock lock = new ReentrantLock(); 12 13 void m1(){ 14 try{ 15 lock.lock(); 16 for(int i = 0; i < 10; i++){ 17 TimeUnit.SECONDS.sleep(1); 18 System.out.println("m1() method " + i); 19 } 20 }catch(InterruptedException e){ 21 e.printStackTrace(); 22 }finally{ 23 lock.unlock(); 24 } 25 } 26 27 void m2(){ 28 boolean isLocked = false; 29 try{ 30 // 嘗試鎖, 如果有鎖,無法獲取鎖標記,返回false。 31 // 如果獲取鎖標記,返回true 32 // isLocked = lock.tryLock(); 33 34 // 阻塞嘗試鎖,阻塞引數代表的時長,嘗試獲取鎖標記。 35 // 如果超時,不等待。直接返回。 36 isLocked = lock.tryLock(5, TimeUnit.SECONDS); 37 38 if(isLocked){ 39 System.out.println("m2() method synchronized"); 40 }else{ 41 System.out.println("m2() method unsynchronized"); 42 } 43 }catch(Exception e){ 44 e.printStackTrace(); 45 }finally{ 46 if(isLocked){ 47 // 嘗試鎖在解除鎖標記的時候,一定要判斷是否獲取到鎖標記。 48 // 如果當前執行緒沒有獲取到鎖標記,會丟擲異常。 49 lock.unlock(); 50 } 51 } 52 } 53 54 public static void main(String[] args) { 55 final Test_02 t = new Test_02(); 56 new Thread(new Runnable() { 57 @Override 58 public void run() { 59 t.m1(); 60 } 61 }).start(); 62 try { 63 TimeUnit.SECONDS.sleep(1); 64 } catch (InterruptedException e) { 65 // TODO Auto-generated catch block 66 e.printStackTrace(); 67 } 68 new Thread(new Runnable() { 69 @Override 70 public void run() { 71 t.m2(); 72 } 73 }).start(); 74 } 75 }嘗試鎖
1 /** 2 * 可打斷 3 * 4 * 阻塞狀態: 包括普通阻塞,等待佇列,鎖池佇列。 5 * 普通阻塞: sleep(10000), 可以被打斷。呼叫thread.interrupt()方法,可以打斷阻塞狀態,丟擲異常。 6 * 等待佇列: wait()方法被呼叫,也是一種阻塞狀態,只能由notify喚醒。無法打斷 7 * 鎖池佇列: 無法獲取鎖標記。不是所有的鎖池佇列都可被打斷。 8 * 使用ReentrantLock的lock方法,獲取鎖標記的時候,如果需要阻塞等待鎖標記,無法被打斷。 9 * 使用ReentrantLock的lockInterruptibly方法,獲取鎖標記的時候,如果需要阻塞等待,可以被打斷。 10 * 11 */ 12 package concurrent.t03; 13 14 import java.util.concurrent.TimeUnit; 15 import java.util.concurrent.locks.Lock; 16 import java.util.concurrent.locks.ReentrantLock; 17 18 public class Test_03 { 19 Lock lock = new ReentrantLock(); 20 21 void m1(){ 22 try{ 23 lock.lock(); 24 for(int i = 0; i < 5; i++){ 25 TimeUnit.SECONDS.sleep(1); 26 System.out.println("m1() method " + i); 27 } 28 }catch(InterruptedException e){ 29 e.printStackTrace(); 30 }finally{ 31 lock.unlock(); 32 } 33 } 34 35 void m2(){ 36 try{ 37 lock.lockInterruptibly(); // 可嘗試打斷,阻塞等待鎖。可以被其他的執行緒打斷阻塞狀態 38 System.out.println("m2() method"); 39 }catch(InterruptedException e){ 40 System.out.println("m2() method interrupted"); 41 }finally{ 42 try{ 43 lock.unlock(); 44 }catch(Exception e){ 45 e.printStackTrace(); 46 } 47 } 48 } 49 50 public static void main(String[] args) { 51 final Test_03 t = new Test_03(); 52 new Thread(new Runnable() { 53 @Override 54 public void run() { 55 t.m1(); 56 } 57 }).start(); 58 try { 59 TimeUnit.SECONDS.sleep(1); 60 } catch (InterruptedException e) { 61 // TODO Auto-generated catch block 62 e.printStackTrace(); 63 } 64 Thread t2 = new Thread(new Runnable() { 65 @Override 66 public void run() { 67 t.m2(); 68 } 69 }); 70 t2.start(); 71 try { 72 TimeUnit.SECONDS.sleep(1); 73 } catch (InterruptedException e) { 74 // TODO Auto-generated catch block 75 e.printStackTrace(); 76 } 77 t2.interrupt();// 打斷執行緒休眠。非正常結束阻塞狀態的執行緒,都會丟擲異常。 78 } 79 }可打斷
1 /** 2 * 公平鎖 3 */ 4 package concurrent.t03; 5 6 import java.util.concurrent.locks.ReentrantLock; 7 8 public class Test_04 { 9 10 public static void main(String[] args) { 11 TestReentrantlock t = new TestReentrantlock(); 12 //TestSync t = new TestSync(); 13 Thread t1 = new Thread(t); 14 Thread t2 = new Thread(t); 15 t1.start(); 16 t2.start(); 17 } 18 } 19 20 class TestReentrantlock extends Thread{ 21 // 定義一個公平鎖 22 private static ReentrantLock lock = new ReentrantLock(true); 23 public void run(){ 24 for(int i = 0; i < 5; i++){ 25 lock.lock(); 26 try{ 27 System.out.println(Thread.currentThread().getName() + " get lock"); 28 }finally{ 29 lock.unlock(); 30 } 31 } 32 } 33 34 } 35 36 class TestSync extends Thread{ 37 public void run(){ 38 for(int i = 0; i < 5; i++){ 39 synchronized (this) { 40 System.out.println(Thread.currentThread().getName() + " get lock in TestSync"); 41 } 42 } 43 } 44 }公平鎖
1 /** 2 * 生產者消費者 3 * 重入鎖&條件 4 * 條件 - Condition, 為Lock增加條件。當條件滿足時,做什麼事情,如加鎖或解鎖。如等待或喚醒 5 */ 6 package concurrent.t04; 7 8 import java.io.IOException; 9 import java.util.LinkedList; 10 import java.util.concurrent.TimeUnit; 11 import java.util.concurrent.locks.Condition; 12 import java.util.concurrent.locks.Lock; 13 import java.util.concurrent.locks.ReentrantLock; 14 15 public class TestContainer02<E> { 16 17 private final LinkedList<E> list = new LinkedList<>(); 18 private final int MAX = 10; 19 private int count = 0; 20 21 private Lock lock = new ReentrantLock(); 22 private Condition producer = lock.newCondition(); 23 private Condition consumer = lock.newCondition(); 24 25 public int getCount(){ 26 return count; 27 } 28 29 public void put(E e){ 30 lock.lock(); 31 try { 32 while(list.size() == MAX){ 33 System.out.println(Thread.currentThread().getName() + " 等待。。。"); 34 // 進入等待佇列。釋放鎖標記。 35 // 藉助條件,進入的等待佇列。 36 producer.await(); 37 } 38 System.out.println(Thread.currentThread().getName() + " put 。。。"); 39 list.add(e); 40 count++; 41 // 藉助條件,喚醒所有的消費者。 42 consumer.signalAll(); 43 } catch (InterruptedException e1) { 44 e1.printStackTrace(); 45 } finally { 46 lock.unlock(); 47 } 48 } 49 50 public E get(){ 51 E e = null; 52 53 lock.lock(); 54 try { 55 while(list.size() == 0){ 56 System.out.println(Thread.currentThread().getName() + " 等待。。。"); 57 // 藉助條件,消費者進入等待佇列 58 consumer.await(); 59 } 60 System.out.println(Thread.currentThread().getName() + " get 。。。"); 61 e = list.removeFirst(); 62 count--; 63 // 藉助條件,喚醒所有的生產者 64 producer.signalAll(); 65 } catch (InterruptedException e1) { 66 e1.printStackTrace(); 67 } finally { 68 lock.unlock(); 69 } 70 71 return e; 72 } 73 74 public static void main(String[] args) { 75 final TestContainer02<String> c = new TestContainer02<>(); 76 for(int i = 0; i < 10; i++){ 77 new Thread(new Runnable() { 78 @Override 79 public void run() { 80 for(int j = 0; j < 5; j++){ 81 System.out.println(c.get()); 82 } 83 } 84 }, "consumer"+i).start(); 85 } 86 try { 87 TimeUnit.SECONDS.sleep(2); 88 } catch (InterruptedException e1) { 89 e1.printStackTrace(); 90 } 91 for(int i = 0; i < 2; i++){ 92 new Thread(new Runnable() { 93 @Override 94 public void run() { 95 for(int j = 0; j < 25; j++){ 96 c.put("container value " + j); 97 } 98 } 99 }, "producer"+i).start(); 100 } 101 } 102 103 }重入鎖&條件
1.8 ThreadLocal
ThreadLocal 提供了執行緒本地的例項。它與普通變數的區別在於,每個使用該變數的執行緒都會初始化一個完全獨立的例項副本。ThreadLocal 變數通常被private static
修飾。當一個執行緒結束時,它所使用的所有 ThreadLocal 相對的例項副本都可被回收。
ThreadLocal 適用於每個執行緒需要自己獨立的例項且該例項需要在多個方法中被使用,也即變數線上程間隔離而在方法或類間共享的場景。每個 Thread 有自己的例項副本,且其它 Thread 不可訪問,那就不存在多執行緒間共享的問題。
1 /** 2 * ThreadLocal 3 * 就是一個Map。key - 》 Thread.getCurrentThread(). value - 》 執行緒需要儲存的變數。 4 * ThreadLocal.set(value) -> map.put(Thread.getCurrentThread(), value); 5 * ThreadLocal.get() -> map.get(Thread.getCurrentThread()); 6 * 記憶體問題 : 在併發量高的時候,可能有記憶體溢位。 7 * 使用ThreadLocal的時候,一定注意回收資源問題,每個執行緒結束之前,將當前執行緒儲存的執行緒變數一定要刪除 。 8 * ThreadLocal.remove(); 9 */ 10 package concurrent.t05; 11 12 import java.util.concurrent.TimeUnit; 13 14 public class Test_01 { 15 16 volatile static String name = "zhangsan"; 17 static ThreadLocal<String> tl = new ThreadLocal<>(); 18 19 public static void main(String[] args) { 20 new Thread(new Runnable() { 21 @Override 22 public void run() { 23 try { 24 TimeUnit.SECONDS.sleep(2); 25 } catch (InterruptedException e) { 26 e.printStackTrace(); 27 } 28 System.out.println(name); 29 System.out.println(tl.get()); 30 } 31 }).start(); 32 33 new Thread(new Runnable() { 34 @Override 35 public void run() { 36 try { 37 TimeUnit.SECONDS.sleep(1); 38 } catch (InterruptedException e) { 39 e.printStackTrace(); 40 } 41 name = "lisi"; 42 tl.set("wangwu"); 43 } 44 }).start(); 45 } 46 47 }ThreadLocal
如果呼叫 ThreadLocal 的 set 方法將一個物件放入Thread中的成員變數threadLocals 中,那麼這個物件是永遠不會被回收的,因為這個物件永遠都被Thread中的成員變數threadLocals引用著,可能會造成 OutOfMemoryError。需要呼叫 ThreadLocal 的 remove 方法 將物件從thread中的成員變數threadLocals中刪除掉。
二、同步容器
執行緒安全的容器物件: Vector, Hashtable。執行緒安全容器物件,都是使用 synchronized方法實現的。
concurrent 包中的同步容器,大多數是使用系統底層技術實現的執行緒安全。類似 native。Java8 中使用 CAS。
2.1 Map/Set
- ConcurrentHashMap/ConcurrentHashSet
底層雜湊實現的同步 Map(Set)。效率高,執行緒安全。使用系統底層技術實現執行緒安全。量級較 synchronized 低。key 和 value 不能為 null。
- ConcurrentSkipListMap/ConcurrentSkipListSet
底層跳錶(SkipList)實現的同步 Map(Set)。有序,效率比 ConcurrentHashMap 稍低。
1 /** 2 * 併發容器 - ConcurrentMap 3 */ 4 package concurrent.t06; 5 6 import java.util.HashMap; 7 import java.util.Hashtable; 8 import java.util.Map; 9 import java.util.Random; 10 <