1. 程式人生 > >Java併發synchronized詳解

Java併發synchronized詳解

今天和大家一起學習下併發程式設計,先舉一個簡單的生活例子,我們去醫院或者銀行排隊叫號,那每個工作人員之間如何保證不會叫重號呢?

  • public class TicketDemo extends Thread {   
        private int index = 1;    
        private static final int MAX = 10;    
        @Override    
        public void run() {        
            while (index <= MAX) {            
                            System.out.println(Thread.currentThread().getName() + "呼叫的號碼是:" + (index++));       
            }   
       }    
       
       public static void main(String[] args) {        
            TicketDemo t1 = new TicketDemo();        
            TicketDemo t2 = new TicketDemo();        
            TicketDemo t3 = new TicketDemo();        
            TicketDemo t4 = new TicketDemo();        
            t1.start();        
            t2.start();        
            t3.start();        
            t4.start();    
      }
    }
    

    執行結果
    f812f627175ebb060e3aaeaaa3ba7070.pngf812f627175ebb060e3aaeaaa3ba7070.png

    我們發現,每一個執行緒都擁有自己的index變數,我們需要將index改成共享變數,有static

    public class TicketDemo extends Thread {   
        private static int index = 1;    
        private static final int MAX = 10;    
        @Override    
        public void run() {        
            while (index <= MAX) {            
                System.out.println(Thread.currentThread().getName() + "呼叫的號碼是:" + (index++));       
            }   
       }    
       
       public static void main(String[] args) {        
            TicketDemo t1 = new TicketDemo();        
            TicketDemo t2 = new TicketDemo();        
            TicketDemo t3 = new TicketDemo();        
            TicketDemo t4 = new TicketDemo();        
            t1.start();        
            t2.start();        
            t3.start();        
            t4.start();    
      }
    }
    

    執行結果
    b9260a46cbdc72c9ef8fb0f62109b9c3.png

    但是我們如果將號碼增到5000,50000萬時,會出現跳號、重號、超過最大值的情況
    7721a8f732bec3845bd99c0118ceef77.png7721a8f732bec3845bd99c0118ceef77.png

    我們可以通過synchronized將併發的程式碼加鎖

    public class TicketDemo extends Thread {   
        private static int index = 1;    
        private static final int MAX = 10;    
        @Override    
        public void run() {        
            while (index <= MAX) {            
                synchronized (this) {
                    System.out.println(Thread.currentThread().getName() + "呼叫的號碼是:" + (index++));       
            }   
                }
       }    
       
       public static void main(String[] args) {        
            TicketDemo t1 = new TicketDemo();        
            TicketDemo t2 = new TicketDemo();        
            TicketDemo t3 = new TicketDemo();        
            TicketDemo t4 = new TicketDemo();        
            t1.start();        
            t2.start();        
            t3.start();        
            t4.start();    
      }
    }
    

    結果輸出
    ace295223360373733b4c23a3f3fc888.pngace295223360373733b4c23a3f3fc888.png

    在Java中,每個物件都會有一個monitor物件,監視器。

    1. 某一執行緒佔有這個物件的時候,先monitor的計數器是不是0,如果是0說明還沒有執行緒佔有,這個時候執行緒佔有這個物件,並且這個物件的monitor+1,;如果不為0,表示這個執行緒已經被其他執行緒佔有,這個執行緒等待。當執行緒釋放佔有權的時候,monitor-1

    2. 使用synchronized對程式碼塊進行加鎖,是使用monitorenter和monitorexit配合使用
      3db60831688a8047d06549113be4c932.png3db60831688a8047d06549113be4c932.png

    3. 使用synchronized對方法進行加鎖,使用ACC_SYNCHRONIZED
      12d34168fda8555c9cb4b766e4374184.png12d34168fda8555c9cb4b766e4374184.png

    鎖:
    jdk1.6以前,都是重量鎖
    Java虛擬機器對synchronized的優化:

    • 偏向鎖:在物件第一次被某一執行緒佔有的時候,是否偏向鎖職位1,鎖表01,寫入執行緒號;當其他的執行緒訪問的時候,競爭;競爭的結果有兩種,成功或失敗。很多次被第一次佔有它的執行緒獲取次數多
      CAS演算法:compare and set
      競爭不激烈的時候適用
    • 輕量級鎖:執行緒有交替使用,互斥性不是很強,CAS失敗,置為00
    • 重量級鎖(等待時間長):強互斥,置為10
    • 自旋鎖:競爭失敗的時候,不是馬上轉化級別,而是執行幾次空迴圈
    • 鎖消除:JIT進行編譯的時候,把不必要的鎖去掉