---恢復內容開始---
多線程
在開發中,遇到耗時的操作,我們需要把耗時的邏輯放入子線程中執行,防止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("==="); } }
public void test() { ... synchronized(this) { // todo your code } ... } 此時,其效果等同於 public synchronized void test() { // todo your code }
使用synchronized代碼塊,可以只對需要同步的代碼進行同步,這樣可以大大的提高效率。
wait() 與notify()/notifyAll()使用:
這三個方法都是Object的方法,並不是線程的方法!
wait() : 釋放占有的對象鎖,線程進入等待池,釋放cpu,而其他正在等待的線程即可搶占此鎖,獲得鎖的線程即可運行程序。而sleep()不同的是,線程調用此方法后,會休眠一段時間,休眠期間,會暫時釋放cpu,但並不釋放對象鎖。也就是說,在休眠期間,其他線程依然無法進入此代碼內部。休眠結束,線程重新獲得cpu,執行代碼。wait()和sleep()最大的不同在於wait()會釋放對象鎖,而sleep()不會!
notify() : 該方法會喚醒因為調用對象的wait()而等待的線程,其實就是對對象鎖的喚醒,從而使得wait()的線程可以有機會獲取對象鎖。調用notify()后,並不會立即釋放鎖,
線程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(); } }
---恢復內容結束---