併發編程主要設計兩個關鍵字:一個是synchronized,另外一個是volatile。下面主要講解這兩個關鍵字,並對這兩個關機進行比較。編程
synchronized緩存
synchronized是經過JMV種的monitorenter和monitorexit指令實現同步。monitorenter指令是在編譯後插入到同步代碼的開始位置,而monitorexit插入到同步代碼的結束位置和異常位置。每個對象都與一個monitor相關聯,當monitor被只有後,它將處於鎖定狀態。多線程
當一個線程試圖訪問同步代碼時,它必須先得到鎖;退出或者拋出異常時,必須釋放鎖。Java中,每個對象均可以做爲鎖。具體的表現形式有3種:併發
權限修飾符 synchronized 返回值類型 函數名(形參列表..){ //函數體 }
權限修飾符 static synchronized 返回值類型 函數名(形參列表..){ //函數體 }
Synchronized(鎖){ //須要同步的代碼塊 }
注意:在同步代碼塊/同步方法中調用sleep()不會釋放鎖對象,調用wait()會釋放鎖對象ide
Synchronized提供了一種排他式的數據同步機制,某個線程在獲取monitor lock的時候可能會被阻塞,而這種阻塞有兩個明顯的缺陷:1. 沒法控制阻塞時長; 2. 阻塞不能被中斷函數
public class SyncDefect { /** *線程休眠一個小時 */ public synchronized void syncMethod(){ try { TimeUnit.HOURS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) throws InterruptedException { SyncDefect defect = new SyncDefect(); new Thread(defect::syncMethod,"t1").start(); //休眠3毫秒後啓動線程t2,確保t1先進入同步方法 TimeUnit.MILLISECONDS.sleep(3); Thread t2 = new Thread(defect::syncMethod, "t2"); t2.start(); //休眠3毫秒後中斷線程t2,確保t2已經啓動 TimeUnit.MILLISECONDS.sleep(3); t2.interrupt(); System.out.println(t2.isInterrupted()); //true System.out.println(t2.getState()); //BLOCKED } }
針對synchronized的兩個缺點,可使用BooleanLock來解決測試
public interface Lock { void lock() throws InterruptedException; /** * 指定獲取鎖的超時時間 * @param mills 等待獲取鎖的最大時間 * @throws InterruptedException * @throws TimeoutException */ void lock(long mills) throws InterruptedException, TimeoutException; void unlock(); List<Thread> getBlockedThreads(); }
public class BooleanLock implements Lock { /** * 記錄取得鎖的線程 */ private Thread currentThread; /** * Bollean開關,標誌鎖是否已經被獲取 */ private boolean locked = false; private List<Thread> blockedList = new ArrayList<>(); @Override public void lock() { //使用同步代碼塊的方式獲取鎖 synchronized (this) { Thread currentThread = Thread.currentThread(); //當鎖已經被某個線程獲取,將當前線程加入阻塞隊列,並使用this.wait()釋放thisMonitor while (locked){ try { if(!blockedList.contains(currentThread)){ blockedList.add(currentThread); } this.wait(); } catch (InterruptedException e) { blockedList.remove(currentThread); e.printStackTrace(); } } blockedList.remove(currentThread); this.locked = true; this.currentThread = currentThread; } } @Override public void lock(long mills) throws InterruptedException, TimeoutException { synchronized (this){ if(mills <= 0) {//時間不合法,調用默認的lock() this.lock(); } else { long remainingMills = mills; long endMills = System.currentTimeMillis() + remainingMills; while (locked) { if (remainingMills <= 0) {//在指定的時間內未獲取鎖或者當前線程被其它線程喚醒,拋出異常 throw new TimeoutException(Thread.currentThread().getName()+" can't get lock during "+mills); } if(!blockedList.contains(Thread.currentThread())){ blockedList.add(Thread.currentThread()); } //等待remainingMills後從新嘗試獲取鎖 this.wait(remainingMills); remainingMills = endMills - System.currentTimeMillis(); } blockedList.remove(Thread.currentThread()); this.locked = true; this.currentThread = Thread.currentThread(); } } } @Override public void unlock() { synchronized (this) { if(Thread.currentThread() == currentThread) { this.locked = false; this.notifyAll(); } } } @Override public List<Thread> getBlockedThreads() { return Collections.unmodifiableList(blockedList); } }
/** * 測試阻塞中斷 */ public class BooleanLockInterruptTest { private final Lock lock = new BooleanLock(); public void syncMethod() { try { lock.lock(); System.out.println(Thread.currentThread().getName()+" get lock."); TimeUnit.HOURS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println("BLOCKED THREAD :"+lock.getBlockedThreads()); lock.unlock(); } } public static void main(String[] args) throws InterruptedException { BooleanLockInterruptTest test = new BooleanLockInterruptTest(); new Thread(test::syncMethod,"t1").start(); TimeUnit.MILLISECONDS.sleep(3); Thread t2 = new Thread(test::syncMethod, "t2"); t2.start(); TimeUnit.MILLISECONDS.sleep(3); t2.interrupt(); System.out.println(t2.isInterrupted()); //true System.out.println(t2.getState()); //RUNNABLE } }
/** * 測試超時 */ public class BooleanLockTimeOutTest { private final Lock lock = new BooleanLock(); public void syncTimeOutMethod() { try { lock.lock(1000); System.out.println(Thread.currentThread().getName()+" get lock."); TimeUnit.HOURS.sleep(1); } catch (InterruptedException | TimeoutException e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main(String[] args) throws InterruptedException { BooleanLockTimeOutTest test = new BooleanLockTimeOutTest(); new Thread(test::syncTimeOutMethod,"t1").start(); TimeUnit.MILLISECONDS.sleep(3); new Thread(test::syncTimeOutMethod, "t2").start(); } }
針對是synhronized還有一些概念及相關知識點須要補充優化
public synchronized void transfer(int from , int to, double amount) throws InterruptedException{ while(accounts[from] < amount){ //wait on intrinsic object lock’s single condition wait(); } accounts[from] -= amount; accounts[to] += amount; //notify all threads waiting on the condition notifyAll(); }
volatilethis
volatile是輕量級的synchronized,它爲實例域的同步訪問提供了一種免鎖機制,不會引發線程上下文的切換和調度。它在多處理器開發中保證了共享變量的「可見性「,若是一個屬性被聲明成volatile,Java模型會確保全部的線程看到這個變量的值時一致的。【volatile變量不能提供原子性】spa
volatile主要用來鎖住一個屬性,在對該屬性的值進行寫操做時,會將數據寫回主存,並將CPU裏緩存了該內存地址的數據無效。【線程在對volatile修飾的變量進行讀寫操做時,會首先檢查線程緩存的值是否失效,若是失效,就會從主存中把數據讀到線程緩存裏】。 volatile本質上就是告訴JVM當前變量的值須要從主存中讀取,當前變量的值被修改後直接刷新到主存中,且不須要被編譯器優化(即:禁止命令重排)。
使用示範:
private volatile boolean done; public boolean isDone(){ return done; } public void setDone(boolean done){ this.done = done; } // Same as private boolean done; public synchronized boolean isDone(){ return done; } public synchronized void setDone(boolean done){ this.done = done; }
比較synchronized和volatile
|
volatile
|
synchronized
|
做用對象
|
實例變量、類變量
|
方法、代碼塊
|
原子性
|
不具有
|
具有
|
可見性
|
具有
|
具有
|
可見性原理
|
使用機器指令的方式迫使其它工做內存中的變量失效
|
利用monitor鎖的排它性實現
|
是否會指令重排
|
否
|
是
|
是否形成線程阻塞
|
否
|
是
|