Java synchronized 關(guān)鍵字

  1. 先看一段代碼
public  class MainActivity extends AppCompatActivity {
    private static String TAG = "MainActivity";
    private int count = 0;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(MainActivity.this,TestActivity.class);
                startActivity(intent);
            }
        });
        for (int i=0;i < 10;i++){
           Thread thread = new Thread("Thread_"+i){
               @Override
               public void run(){
                   for (int j=0;j < 1000;j++){
                       count = count+1;
                       Log.d(TAG,Thread.currentThread().getName()+":"+count);
                   }
               }
           };
           thread.start();
        }
    }
}

我們預(yù)測下這段代碼的執(zhí)行結(jié)果,也就是count的最終值。有人可能會說是10000。但是實際結(jié)果是小于等于10000的一個數(shù)。原因是

count = count+1;

是一個非原子操作,至少包含三個語句

  • 從內(nèi)存取出count的值
  • 給count加1
  • 寫回內(nèi)存
    那極有可能存在這種情況
    線程1從內(nèi)存中讀到count的值為1,此時線程2也讀到了1,然后2個線程都給count做自增操作并寫回內(nèi)存,此時內(nèi)存中count的值為2,并不是正確結(jié)果3。
  1. 那如何解決多線程并發(fā)導致不能獲得正確結(jié)果的問題呢?
    java引入了鎖的機制,也就是關(guān)鍵字synchronized同步鎖,每個對象都有一把獨立的鎖,類對象只有一個鎖。只有獲得鎖,才能執(zhí)行被synchronized修飾的代碼塊或者方法。
  • synchronized修飾代碼塊
    我們把上面的代碼稍微修改下
for (int i=0;i < 10;i++){
      final Thread thread = new Thread("Thread_"+i){
          @Override
          public void run(){
          synchronized (MainActivity.this){
                 for (int j=0;j < 1000;j++){
                       count = count+1;
                       Log.d(TAG,Thread.currentThread().getName()+":"+count);
                  }
            }
         }
      };
     thread.start();
}

每個線程執(zhí)行的時候,都嘗試去獲取MainActivity對象的鎖,如果拿不到,就阻塞等。這就保證了同一時刻只有一個線程讀寫count這個變量,從而確保了結(jié)果的正確性,但是計算耗時增加了,效率變低。
修飾代碼塊很好理解,sychronized拿到的是實參對象的鎖。

  • 修飾成員方法
    看一段代碼
public  class MainActivity extends AppCompatActivity {
    private static String TAG = "MainActivity";
    private int count = 0;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final Thread thread = new Thread("Thread_"+"test"){
            @Override
            public void run(){
                try {
                    test();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        };
        thread.start();
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                    synchronized (MainActivity.class){
                        Log.d(TAG,"我獲得了類鎖");
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        });
        thread1.start();
    }
    public synchronized void test() throws Exception{
       while (true){
           Thread.sleep(1000);
       }
    }
}

我們用sychrionized修飾了increase這個方法。如果這個sychrionized拿到的是類鎖,那么

Log.d(TAG,"我拿到了類鎖");

代碼將永遠得不到執(zhí)行,但是實際情況是

01-02 18:03:49.760 14175-14203/com.debug.pluginhost D/MainActivity: 我獲得了類鎖

這行代碼得到了執(zhí)行,這說明synchronized修飾成員方法的時候,獲得是對象的鎖。

  • 修飾靜態(tài)方法
    修飾靜態(tài)方法我們就不舉例了,這種情況,synchronized獲得是類對象的鎖。
  1. wait(),notify()和notifyAll()
    synchrinized拿到鎖后,如果需要,可以中途可以通過lockobj.wait()釋放鎖并阻塞在wait()處,等待其他線程通過lockobj.notify()或者lockobj.notifyAll()喚醒繼續(xù)執(zhí)行,一個典型的例子就是阻塞隊列。
    看代碼
    static class BlockQ{
        ArrayList<Object> queue = new ArrayList<>();
        int maxSize = 1;
        public void push(Object o){
            try {
                synchronized (queue){
                    while (queue.size() >= maxSize){
                        queue.wait();
                    }
                    Log.d(TAG,"push :"+o);
                    queue.add(o);
                    queue.notify();

                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        public Object pop(){
            try {
                synchronized (queue){
                    while (queue.size() == 0){
                        queue.wait();
                    }
                    Object o = queue.remove(0);
                    Log.d(TAG,"pop對象:"+o.toString());
                    queue.notify();
                    return o;
                }
            }catch (Exception e){
                e.printStackTrace();
            }
            return null;
        }
    }

我們想象有兩個集合CompetitionSet和WaitSet,我們簡稱為C和W,在C中的線程是有資格獲取對象鎖的,在W中的線程是沒資格的,但是如果有人通知它們或者它們當中一個,它們就可以進入C集合,參加競爭鎖。默認情況下,大家都在集合C,假設(shè)某個線程T獲得鎖,它開始執(zhí)行代碼,但是它發(fā)現(xiàn)自己無法處理當前的情況,因此只能等待,通過調(diào)用wait方法進入W集合,并釋放自己拿到的鎖

while (queue.size() == 0){ //我無法處理這種情況
       queue.wait();//進入等待集合吧,并且釋放了自己獲得的鎖
 }

當有線程T1獲得了鎖,并成功執(zhí)行push方法后,發(fā)出了notify()通知,此時JVM會從W中隨機選一個線程T進入C集合去競爭鎖,一旦T獲得鎖,它將從wait()處繼續(xù)執(zhí)行代碼。如果能把notify弄明白,那么notifyAll就不難了,從字面意思就能理解,notifyAll會把W集合里所有的等待線程都放入到C集合里去競爭鎖。

  1. 死鎖
    大家仔細考慮下上面的代碼有什么問題?
    我們假設(shè)有2個線程C1和C2調(diào)用pop方法,有1個線程P1調(diào)用push方法,假設(shè)執(zhí)行順序是這樣的
  • C1執(zhí)行,發(fā)現(xiàn)queue里沒數(shù)據(jù),那么C1進入W集合
  • C2執(zhí)行,發(fā)現(xiàn)queue里沒數(shù)據(jù),那么C2進入W集合
  • P1執(zhí)行,push一個數(shù)據(jù)到隊列后,喚醒C1線程,此時C集合里有C1和P1,如果此時P1再次獲得對象鎖,那么P1進入W集合,此時C集合里只剩下C1,queue.size = 1
  • C1獲得對象鎖,pop數(shù)據(jù)后,喚醒了C2,此時C1、C2在集合C中,queue.size = 0
  • C1獲得對象鎖,因為queue.size = 0,因此C1進入W集合
  • C2獲得對象鎖,因為queue.size = 0,因此C2進入W集合
  • 程序陷入死鎖
    思考notify和notifyAll的不同,在這種場景下只能用notifyAll。因為notifyAll會喚醒所有在W集合中的線程。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 聽完潤總這期第三個8小時想到了之前聽大鵬演講自己的成功之路 孟母三遷般的隨著公司的腳步搬家 就為了每次有演藝工作都...
    臧寶瓏閱讀 245評論 0 0
  • 心想生自在人生自在經(jīng) 提問 1我現(xiàn)在頭腦知道物質(zhì)世界不存在,是假相,所有的山川河流都是假相,可就是看起來特別真實,...
    實現(xiàn)幸福的人生閱讀 1,135評論 0 6
  • 別忘了學習。20來歲正處于腦力最好、學習效率最高的人生階段,要珍惜。不要相信先搞人脈,后搞學習。人脈再廣,沒有能力...
    Kecvin閱讀 247評論 0 0

友情鏈接更多精彩內(nèi)容