當多個線程對同一數據進行訪問時,容易出現線程安全問題,這個時候就須要讓線程同步來保證數據的安全。線程同步就是說在兩個或兩個以上的線程訪問同一資源的時候,須要用到某種方式來保證資源在某一時刻只能被一個線程訪問java
線程同步的實現方案:安全
1、同步代碼塊:synchronized(同步監視器)併發
一、認識同步監視器(鎖子)框架
synchronized(同步監視器){}ide
1)必須是引用數據類型,不能是基本數據類型性能
2)在同步代碼塊中能夠改變同步監視器對象的值,不能改變其引用this
3)儘可能不要使用String和包裝類Integer作同步監視器,若是要使用,則必須保證代碼快啊中不對其作任何操做spa
4)通常使用共享資源作同步器線程
5)能夠建立一個專門的同步監視器,沒有任何含義code
6)建議使用final來修飾同步監視器
二、同步代碼塊的執行過程
1)第一個線程來到同步代碼塊,發現同步監視器是open狀態,須要close,而後執行其中的代碼
2)第一個線程執行過程當中,發生了線程切換(阻塞 就緒),第一個線程失去了CPU,可是沒有開鎖
3)第二個線程獲取了CPU,來到同步代碼塊,發現同步監視器close狀態,沒法執行其中的代碼,第二個也進入了阻塞狀態
4)第一個線程再次得到CPU,執行後續代碼,執行完畢釋放鎖
5)第二個線程再次得到CPU,來到同步代碼塊發現是開鎖狀態,重複第一個線程的處理過程
三、下面的代碼是用同步代碼塊來實現線程同步(多個窗口實現安全售票)
public class TiketsTest { public static void main(String[] args) { for(int i = 0;i<5;i++){//運用循環來開啓五個線程(模擬五個售票員) new Thread(new TiketsRunnable(),"售票員"+(i+1)).start();//此處爲了方便直接使用匿名對象 } } public class TiketsRunnable implements Runnable { private int tikets = 100;//要賣票的總數 private Object obj = new Object(); @Override public void run() { while (true){ synchronized (obj) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } if (tikets <= 0) { break; } System.out.println(Thread.currentThread().getName() + "賣了第" + tikets-- + "票"); } } } }
2、同步方法:修飾符 synchronized 返回值類型 方法名(參數){}
一、不要將run()定義爲同步方法
二、同步方法的同步監視器是this
三、同步代碼塊的效率要高於同步方法
1)同步方法的鎖是this,一旦鎖住一個方法,就鎖住了全部的同步方法;同步代碼塊只是鎖住了使用該同步代碼塊,而沒有鎖住使用其餘監視器的代碼塊
2)同步方法是將線程鎖在了方法的外部,而同步代碼塊將線程鎖在了代碼塊的外部,可是倒是方法的內部
4、下面的代碼是用同步方法來實現線程同步(多個窗口實現安全售票)
public class TiketsTest { public static void main(String[] args) { for(int i = 0;i<5;i++){//運用循環來開啓五個線程(模擬五個售票員) new Thread(new TiketsRunnable(),"售票員"+(i+1)).start();//此處爲了方便直接使用匿名對象 } } } public class TiketsRunnable implements Runnable { private int tikets = 3; private Object obj = new Object(); @Override public void run() { while (true) { sell(); if (tikets <= 0) { break; } } } public synchronized void sell(){//同步方法 if(tikets<=0){ return; } try { Thread.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "賣了第" + tikets+ "票"); tikets --; } }
3、Lock鎖
一、Lock鎖
1)JDK1.5後新增功能,與採用synchronized想比,lock鎖可提供多種鎖方案,更靈活
2)java.util.concurrent.lock 中的 Lock 框架是鎖定的一個抽象,它容許把鎖定的實現做爲 Java 類,而不是做爲語言的特性來實現。這就爲 Lock 的多種實現留下了空間,各類實現可能有不一樣的調度算
法、性能特性或者鎖定語義。
3)ReentrantLock 類實現了 Lock ,它擁有與 synchronized 相同的併發性和內存語義, 可是添加了相似鎖投票、定時鎖等候和可中斷鎖等候的一些特性。此外,它還提供了在激烈爭用狀況下更佳的
性能。
注意:若是同步代碼有異常,要將unlock()寫入finally語句塊中,確保關鎖
二、Lock和synchronized的區別
1)Lock是顯示鎖(須要手動開鎖、關鎖,不要忘記關鎖),synchronized是隱式鎖,遇到異常自動解鎖
2)Lock鎖只有代碼塊鎖,synchronized有代碼塊鎖和方法鎖
3)使用Lock鎖,JVM將花費較少的時間來調度線程,性能更好。而且具備更好的擴展性(提供更多的子類)
三、下面的代碼是用Lock鎖來實現線程同步(多個窗口實現安全售票)
public class TiketsTest { public static void main(String[] args) { for(int i = 0;i<5;i++){//運用循環來開啓五個線程(模擬五個售票員) new Thread(new TiketsRunnable(),"售票員"+(i+1)).start();//此處爲了方便直接使用匿名對象 } } } import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class TiketsRunnable implements Runnable { private int tikets = 100; Lock lock = new ReentrantLock(); @Override public void run() { while (true) { lock.lock();//開鎖 try { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } if (tikets <= 0) { break; } System.out.println(Thread.currentThread().getName() + "賣了第" + tikets-- + "票"); }finally { lock.unlock();//放在finally語句塊中確保關鎖 } } } }
4、三種鎖的優先使用順序
Lock鎖 —— 同步代碼塊(已經進入了方法體,分配了相應的資源)—— 同步方法(在方法體以外)