線程同步鎖的使用方式


---恢復內容開始---

多線程

在開發中,遇到耗時的操作,我們需要把耗時的邏輯放入子線程中執行,防止Android頁面卡頓。

為什么使用同步鎖?

前段時間我做了一個多任務下載的功能,每一個任務開啟一個線程,同時創建了一個線程池,存放所有的任務線程,並且可以設定可支持同時下載2個任務。當下載完成文件后,

需要解析文件的操作,並把解析的數據插入數據庫。現在就有一種情況是如果兩個任務同時執行完成,同時解析文件,獲取數據后,同時插入數據庫,由於插入的表比較多,

而且數據庫的DB使用的是單例,這樣就會出現插入錯亂的bug。我們采用synchronized聲明該方法為同步方法,如果一個方法正在執行,別的方法調用,則處於等待狀態。

當這個方法執行完成后,可以調用解鎖方法,wait():釋放占有的對象鎖,線程進入等待池。

synchronized使用:

因為很方便,比如需要對一個方法進行同步,那么只需在方法的簽名添加一個synchronized關鍵字。

1. // 未同步的方法
public void test() {}
2. // 同步的方法
pubilc synchronized void test() {}
3. // synchronized 也可以用在一個代碼塊上
public void test() {
     synchronized(obj) {
          System.out.println("===");
     }
}
synchronized 用在方法和代碼塊上有什么區別呢?
1.synchronized 用在方法簽名上(pubilc synchronized void test())
當某個線程調用此方法時,會獲取該實例的對象鎖,方法未結束之前,其他線程只能去等待。
當這個方法執行完時,才會釋放對象鎖。其他線程才有機會去搶占這把鎖,去執行方法test,但是發生這一切的基礎應當是所有線程使用的同一個對象實例,才能實現互斥的現象。
否則synchronized關鍵字將失去意義。
2.synchronized 用在代碼塊的使用方式:synchronized(obj){//todo code here}
當線程運行到該代碼塊內,就會擁有obj對象的對象鎖,如果多個線程共享同一個Object對象,那么此時就會形成互斥!特別的,當obj == this時,表示當前調用該方法的實例對象。
就像以下:
public void test() {
     ...
     synchronized(this) {
          // todo your code
     }
     ...
}
此時,其效果等同於
public synchronized void test() {
     // todo your code
}

使用synchronized代碼塊,可以只對需要同步的代碼進行同步,這樣可以大大的提高效率。

小結:
使用synchronized 代碼塊相比方法有兩點優勢:
1、可以只對需要同步的使用
2、與wait()/notify()/nitifyAll()一起使用時,比較方便。

wait() 與notify()/notifyAll()使用:

這三個方法都是Object的方法,並不是線程的方法!

wait() : 釋放占有的對象鎖,線程進入等待池,釋放cpu,而其他正在等待的線程即可搶占此鎖,獲得鎖的線程即可運行程序。而sleep()不同的是,線程調用此方法后,會休眠一段時間,休眠期間,會暫時釋放cpu,但並不釋放對象鎖。也就是說,在休眠期間,其他線程依然無法進入此代碼內部。休眠結束,線程重新獲得cpu,執行代碼。wait()和sleep()最大的不同在於wait()會釋放對象鎖,而sleep()不會!

notify() : 該方法會喚醒因為調用對象的wait()而等待的線程,其實就是對對象鎖的喚醒,從而使得wait()的線程可以有機會獲取對象鎖。調用notify()后,並不會立即釋放鎖,

而是繼續執行當前代碼,直到synchronized中的代碼全部執行完畢,才會釋放對象鎖。
JVM則會在等待的線程中調度一個線程去獲得對象鎖,執行代碼。需要注意的是, wait()和notify()必須在synchronized代碼塊中調用
notifyAll()則是喚醒所有等待的線程。
 
為了說明這一點,舉例如下:
兩個線程依次打印"A""B",總共打印10次。
線程A:
public class Produce implements Runnable {
 
     @Override
     public void run() {
            // TODO Auto-generated method stub
            int count = 10;
            while(count > 0) {
                 synchronized (Test. obj) {
                      //System.out.print("count = " + count);
                     System. out.print( "A");
                     count --;
                     Test. obj.notify();
                     
                      try {
                           Test. obj.wait();
                     } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                           e.printStackTrace();
                     }
                }
                
           }
 
     }
 
}
線程B:
public class Consumer implements Runnable {
 
     @Override
     public synchronized void run() {
            // TODO Auto-generated method stub
            int count = 10;
            while(count > 0) {
                 synchronized (Test. obj) {
                     
                     System. out.print( "B");
                     count --;
                     Test. obj.notify(); // 主動釋放對象鎖
                     
                      try {
                           Test. obj.wait();
                           
                     } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                           e.printStackTrace();
                     }
                }
                
           }
     }
}
測試類如下:
 
public class Test {
 
     public static final Object obj = new Object();
     
     public static void main(String[] args) {
           
            new Thread( new Produce()).start();
            new Thread( new Consumer()).start();
           
     }
}

這里使用static obj作為鎖的對象,當線程Produce啟動時(假如Produce首先獲得鎖,則Consumer會等待),打印“A”后,會先主動釋放鎖,然后阻塞自己。Consumer獲得對象鎖,打印“B”,然后釋放鎖,阻塞自己,那么Produce又會獲得鎖,然后...一直循環下去,直到count = 0.這樣,使用Synchronized和wait()以及notify()就可以達到線程同步的目的。

------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------

除了wait()和notify()協作完成線程同步之外,使用Lock也可以完成同樣的目的。

ReentrantLock: 

ReentrantLock 與synchronized有相同的並發性和內存語義,還包含了中斷鎖等候和定時鎖等候,意味着線程A如果先獲得了對象obj的鎖,那么線程B可以在等待指定時間內依然無法獲取鎖,那么就會自動放棄該鎖。

但是由於synchronized是在JVM層面實現的,因此系統可以監控鎖的釋放與否,而ReentrantLock使用代碼實現的,系統無法自動釋放鎖,需要在代碼中finally子句中顯式釋放鎖lock.unlock();

這樣的例子,使用lock 如何實現呢?

public class Producer implements Runnable{
 
     private Lock lock;
     public Producer(Lock lock) {
            this. lock = lock;
     }
     @Override
     public void run() {
            // TODO Auto-generated method stub
            int count = 10;
            while (count > 0) {
                 try {
                      lock.lock();
                     count --;
                     System. out.print( "A");
                } finally {
                      lock.unlock();
                      try {
                           Thread. sleep(90L);
                     } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                           e.printStackTrace();
                     }
                }
           }
     }
}
public class Consumer implements Runnable {
 
     private Lock lock;
     public Consumer(Lock lock) {
            this. lock = lock;
     }
     @Override
     public void run() {
            // TODO Auto-generated method stub
            int count = 10;
            while( count > 0 ) {
                 try {
                      lock.lock();
                     count --;
                     System. out.print( "B");
                } finally {
                      lock.unlock(); //主動釋放鎖
                      try {
                           Thread. sleep(91L);
                     } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                           e.printStackTrace();
                     }
                }
           }
 
     }
 
}
調用代碼:
 
public class Test {
 
     public static void main(String[] args) {
           Lock lock = new ReentrantLock();
           
           Consumer consumer = new Consumer(lock);
           Producer producer = new Producer(lock);
           
            new Thread(consumer).start();
            new Thread( producer).start();
           
     }
}

  

使用建議:
 
在並發量比較小的情況下,使用synchronized是個不錯的選擇,但是在並發量比較高的情況下,其性能下降很嚴重,此時ReentrantLock是個不錯的方案。完畢
 

 

 

 
 
 
 
 
 

---恢復內容結束---


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM