1. 程式人生 > >執行緒同步與鎖(一)

執行緒同步與鎖(一)

   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部分程式碼在各子執行緒中分別執行。

 

  寫在最後:加鎖同步只在多執行緒中才有存在的意義,單執行緒中必然是順序執行。將執行緒看作是跑道,單跑道就不存在超越的問題。多執行緒就是多條跑到,同步程式碼或者同步方法就是跑到交叉處,必須容前面的先通過,後面的才能動身。