執行緒同步與鎖(一)
im專案中都會存在離線訊息,我們在接受到訊息後,開啟子執行緒,處理相關業務邏輯。因為業務邏輯需遵循一定的處理順序,我們將部分程式碼加上了鎖。但是在離線訊息太多時,卻出現了執行緒問題:OutOfMemoryError: pthread_create (1040KB stack) failed: Try again 本地建立執行緒時發現記憶體不夠,棧記憶體溢位。花費了不少時間,一方面添加了佇列,另一方面將專案中的同步程式碼好好梳理了一遍,總算將這個問題解決了。在這裡,將執行緒同步的知識點梳理梳理,記錄如下:
1.關鍵字synchronize
答:同步方法的鎖物件是呼叫者本身。如方法加鎖後,物件p在不同執行緒呼叫該方法,會造成堵塞。
public void useMethodInThread(final int type, final String tag) { new Thread(new Runnable() { @Override public void run() { switch (type) { case 1: method1(tag); break; case 2: method2(tag); break; case 3: method3(tag); break; case 4: method4(tag); break; case 5: method5(tag); break; default: break; } } }).start(); }
日誌:/** * 同步方法 */ public synchronized void method1(final String str) { Log.i(tag, "start method1 " + str); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } finally { Log.i(tag, "start method1 after sleep " + str); } } //呼叫 useMethodInThread(1, "001"); useMethodInThread(1, "002");
10-26 15:18:32.572 I/TestLockActivity: start method1 001
10-26 15:18:34.572 I/TestLockActivity: start method1 after sleep 001
10-26 15:18:34.572 I/TestLockActivity: start method1 002
10-26 15:18:36.572 I/TestLockActivity: start method1 after sleep 002
第一次呼叫後,該方法被堵塞,直到呼叫完畢後,才進行第二次的呼叫。沒毛病。
2.同步方法與同步程式碼塊synchronized(this),會造成堵塞嗎?
答:同步程式碼塊synchronized(this)使用了當前物件作為鎖物件,與同步方法中的鎖物件一致,會造成執行緒堵塞。
public void useMethodInThread(final int type, final String tag) {
new Thread(new Runnable() {
@Override
public void run() {
switch (type) {
case 1:
method1(tag);
break;
case 2:
method2(tag);
break;
default:
break;
}
}
}).start();
}
/**
* 同步程式碼塊
*/
public void method2(String str) {
synchronized (this) {
Log.i(tag, "start method2 " + str);
}
}
//呼叫
useMethodInThread(1, "001");
useMethodInThread(2, "002");
日誌:
10-26 15:38:05.342 I/TestLockActivity: start method1 001
10-26 15:38:07.342 I/TestLockActivity: start method1 after sleep 001
10-26 15:38:07.392 I/TestLockActivity: start method2 002
同步方法中使用了執行緒sleep2秒,此時同步程式碼塊一直在等待鎖釋放後,才執行程式碼。
3.同步程式碼塊後的非同步程式碼,會在同步堵塞時,優先執行嗎?
答:不會。這就像跑步,同步程式碼塊是獨行橋,橋上堵塞後,哪怕橋後的是多跑道,依然需要等待前面的先過橋。
/**
* 同步程式碼與非同步程式碼
*/
byte[] bytes = new byte[1];
public void method3(String str) {
Log.i(tag, "start method3 first " + str);
synchronized (bytes) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
Log.i(tag, "start method3 synchronized " + str);
}
}
Log.i(tag, "start method3 last " + str);
}
//呼叫:
useMethodInThread(3, "001");
useMethodInThread(3, "002");
日誌:
10-26 16:02:59.522 I/TestLockActivity: start method3 first 001
10-26 16:02:59.522 I/TestLockActivity: start method3 first 002
10-26 16:03:01.522 I/TestLockActivity: start method3 synchronized 001
10-26 16:03:01.522 I/TestLockActivity: start method3 last 001
10-26 16:03:03.572 I/TestLockActivity: start method3 synchronized 002
10-26 16:03:03.572 I/TestLockActivity: start method3 last 002
第二次呼叫時,在同步程式碼塊中造成堵塞,釋放鎖後,各子執行緒獨立執行。
4.同步方法中新增handler延遲,會釋放鎖物件嗎?
答:會。建立handler時,一般需要設定looper,預設使用主執行緒的looper,這時執行緒切換,釋放了鎖物件。
private synchronized void method4(String str) {
Log.i(tag, "start method4 first " + str);
Message obtain = Message.obtain();
obtain.what = 0;
obtain.obj = str;
handler.sendMessageDelayed(obtain, 2000);
}
Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 0) {
Log.i(tag, "start method4 handler " + msg.obj);
}
}
};
//呼叫:
useMethodInThread(4, "001");
useMethodInThread(4, "002");
日誌:
10-26 16:25:19.312 I/TestLockActivity: start method4 first 001
10-26 16:25:19.312 I/TestLockActivity: start method4 first 002
10-26 16:25:21.312 I/TestLockActivity: start method4 handler 001
10-26 16:25:21.312 I/TestLockActivity: start method4 handler 002
5.同步方法中新增timer延遲,會釋放鎖物件嗎?
答:會。timer內部應該也做了執行緒切換,釋放了鎖物件。
private synchronized void method5(String str) {
Log.i(tag, "start method5 first " + str);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
Log.i(tag, "start method5 sleep " + str);
}
runTimer(str);
}
private void runTimer(final String str) {
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
Log.i(tag, "start method5 timerTask " + str);
}
};
Timer timer = new Timer();
timer.schedule(timerTask, 2000);
}
//呼叫
useMethodInThread(5, "001");
useMethodInThread(5, "002");
日誌:
10-26 16:45:09.912 I/TestLockActivity: start method5 first 001
10-26 16:45:11.912 I/TestLockActivity: start method5 sleep 001
10-26 16:45:11.962 I/TestLockActivity: start method5 first 002
10-26 16:45:13.912 I/TestLockActivity: start method5 timerTask 001
10-26 16:45:13.962 I/TestLockActivity: start method5 sleep 002
10-26 16:45:15.962 I/TestLockActivity: start method5 timerTask 002
啟動timer後,立即釋放了鎖,第二次呼叫的程式碼立即執行,列印了method5 first 002,後面的timer部分程式碼在各子執行緒中分別執行。
寫在最後:加鎖同步只在多執行緒中才有存在的意義,單執行緒中必然是順序執行。將執行緒看作是跑道,單跑道就不存在超越的問題。多執行緒就是多條跑到,同步程式碼或者同步方法就是跑到交叉處,必須容前面的先通過,後面的才能動身。