天天看點

Java并發程式設計:Synchronized及其實作原理

Java并發程式設計系列:

  • Java 并發程式設計:核心理論 
  • Java并發程式設計:Synchronized及其實作原理
  • Java并發程式設計:Synchronized底層優化(輕量級鎖、偏向鎖)
  • Java 并發程式設計:線程間的協作(wait/notify/sleep/yield/join) 
  • Java 并發程式設計:volatile的使用及其原理

一、Synchronized的基本使用

  Synchronized是Java中解決并發問題的一種最常用的方法,也是最簡單的一種方法。Synchronized的作用主要有三個:(1)確定線程互斥的通路同步代碼(2)保證共享變量的修改能夠及時可見(3)有效解決重排序問題。從文法上講,Synchronized總共有三種用法:

  (1)修飾普通方法

  (2)修飾靜态方法

  (3)修飾代碼塊

  接下來我就通過幾個例子程式來說明一下這三種使用方式(為了便于比較,三段代碼除了Synchronized的使用方式不同以外,其他基本保持一緻)。

1、沒有同步的情況:

代碼段一:

package com.paddx.test.concurrent;

public class SynchronizedTest {
    public void method1(){
        System.out.println("Method 1 start");
        try {
            System.out.println("Method 1 execute");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 1 end");
    }

    public void method2(){
        System.out.println("Method 2 start");
        try {
            System.out.println("Method 2 execute");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 2 end");
    }

    public static void main(String[] args) {
        final SynchronizedTest test = new SynchronizedTest();

        new Thread(new Runnable() {
            @Override
            public void run() {
                test.method1();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                test.method2();
            }
        }).start();
    }
}
      

執行結果如下,線程1和線程2同時進入執行狀态,線程2執行速度比線程1快,是以線程2先執行完成,這個過程中線程1和線程2是同時執行的。

Method

1

start

Method

1

execute

Method

2

start

Method

2

execute

Method

2

end

Method

1

end

 2、對普通方法同步:

代碼段二:

package com.paddx.test.concurrent;

public class SynchronizedTest {
    public synchronized void method1(){
        System.out.println("Method 1 start");
        try {
            System.out.println("Method 1 execute");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 1 end");
    }

    public synchronized void method2(){
        System.out.println("Method 2 start");
        try {
            System.out.println("Method 2 execute");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 2 end");
    }

    public static void main(String[] args) {
        final SynchronizedTest test = new SynchronizedTest();

        new Thread(new Runnable() {
            @Override
            public void run() {
                test.method1();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                test.method2();
            }
        }).start();
    }
}
      

執行結果如下,跟代碼段一比較,可以很明顯的看出,線程2需要等待線程1的method1執行完成才能開始執行method2方法。

Method

1

start

Method

1

execute

Method

1

end

Method

2

start

Method

2

execute

Method

2

end

3、靜态方法(類)同步

代碼段三:

package com.paddx.test.concurrent;
 
 public class SynchronizedTest {
     public static synchronized void method1(){
         System.out.println("Method 1 start");
         try {
             System.out.println("Method 1 execute");
             Thread.sleep(3000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println("Method 1 end");
     }
 
     public static synchronized void method2(){
         System.out.println("Method 2 start");
         try {
             System.out.println("Method 2 execute");
             Thread.sleep(1000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println("Method 2 end");
     }
 
     public static void main(String[] args) {
         final SynchronizedTest test = new SynchronizedTest();
         final SynchronizedTest test2 = new SynchronizedTest();
 
         new Thread(new Runnable() {
             @Override
             public void run() {
                 test.method1();
             }
         }).start();
 
         new Thread(new Runnable() {
             @Override
             public void run() {
                 test2.method2();
             }
         }).start();
     }
 }
      

  執行結果如下,對靜态方法的同步本質上是對類的同步(靜态方法本質上是屬于類的方法,而不是對象上的方法),是以即使test和test2屬于不同的對象,但是它們都屬于SynchronizedTest類的執行個體,是以也隻能順序的執行method1和method2,不能并發執行。

Method

1

start

Method

1

execute

Method

1

end

Method

2

start

Method

2

execute

Method

2

end

4、代碼塊同步

代碼段四:

package com.paddx.test.concurrent;

public class SynchronizedTest {
    public void method1(){
        System.out.println("Method 1 start");
        try {
            synchronized (this) {
                System.out.println("Method 1 execute");
                Thread.sleep(3000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 1 end");
    }

    public void method2(){
        System.out.println("Method 2 start");
        try {
            synchronized (this) {
                System.out.println("Method 2 execute");
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 2 end");
    }

    public static void main(String[] args) {
        final SynchronizedTest test = new SynchronizedTest();

        new Thread(new Runnable() {
            @Override
            public void run() {
                test.method1();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                test.method2();
            }
        }).start();
    }
}
      

執行結果如下,雖然線程1和線程2都進入了對應的方法開始執行,但是線程2在進入同步塊之前,需要等待線程1中同步塊執行完成。

Method

1

start

Method

1

execute

Method

2

start

Method

1

end

Method

2

execute

Method

2

end

二、Synchronized 原理

  如果對上面的執行結果還有疑問,也先不用急,我們先來了解Synchronized的原理,再回頭上面的問題就一目了然了。我們先通過反編譯下面的代碼來看看Synchronized是如何實作對代碼塊進行同步的:

package com.paddx.test.concurrent;

public class SynchronizedDemo {
    public void method() {
        synchronized (this) {
            System.out.println("Method 1 start");
        }
    }
}
      

反編譯結果:

Java并發程式設計:Synchronized及其實作原理

關于這兩條指令的作用,我們直接參考JVM規範中描述:

monitorenter :

Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:

• If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.

• If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.

• If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.

這段話的大概意思為:

每個對象有一個螢幕鎖(monitor)。當monitor被占用時就會處于鎖定狀态,線程執行monitorenter指令時嘗試擷取monitor的所有權,過程如下:

1、如果monitor的進入數為0,則該線程進入monitor,然後将進入數設定為1,該線程即為monitor的所有者。

2、如果線程已經占有該monitor,隻是重新進入,則進入monitor的進入數加1.

3.如果其他線程已經占用了monitor,則該線程進入阻塞狀态,直到monitor的進入數為0,再重新嘗試擷取monitor的所有權。

monitorexit: 

The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.

The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.

執行monitorexit的線程必須是objectref所對應的monitor的所有者。

指令執行時,monitor的進入數減1,如果減1後進入數為0,那線程退出monitor,不再是這個monitor的所有者。其他被這個monitor阻塞的線程可以嘗試去擷取這個 monitor 的所有權。 

  通過這兩段描述,我們應該能很清楚的看出Synchronized的實作原理,Synchronized的語義底層是通過一個monitor的對象來完成,其實wait/notify等方法也依賴于monitor對象,這就是為什麼隻有在同步的塊或者方法中才能調用wait/notify等方法,否則會抛出java.lang.IllegalMonitorStateException的異常的原因。

  我們再來看一下同步方法的反編譯結果:

源代碼:

package com.paddx.test.concurrent;

public class SynchronizedMethod {
    public synchronized void method() {
        System.out.println("Hello World!");
    }
}
      
Java并發程式設計:Synchronized及其實作原理

  從反編譯的結果來看,方法的同步并沒有通過指令monitorenter和monitorexit來完成(理論上其實也可以通過這兩條指令來實作),不過相對于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根據該标示符來實作方法的同步的:當方法調用時,調用指令将會檢查方法的 ACC_SYNCHRONIZED 通路标志是否被設定,如果設定了,執行線程将先擷取monitor,擷取成功之後才能執行方法體,方法執行完後再釋放monitor。在方法執行期間,其他任何線程都無法再獲得同一個monitor對象。 其實本質上沒有差別,隻是方法的同步是一種隐式的方式來實作,無需通過位元組碼來完成。

三、運作結果解釋

  有了對Synchronized原理的認識,再來看上面的程式就可以迎刃而解了。

1、代碼段2結果:

  雖然method1和method2是不同的方法,但是這兩個方法都進行了同步,并且是通過同一個對象去調用的,是以調用之前都需要先去競争同一個對象上的鎖(monitor),也就隻能互斥的擷取到鎖,是以,method1和method2隻能順序的執行。

2、代碼段3結果:

  雖然test和test2屬于不同對象,但是test和test2屬于同一個類的不同執行個體,由于method1和method2都屬于靜态同步方法,是以調用的時候需要擷取同一個類上monitor(每個類隻對應一個class對象),是以也隻能順序的執行。

3、代碼段4結果:

  對于代碼塊的同步實質上需要擷取Synchronized關鍵字後面括号中對象的monitor,由于這段代碼中括号的内容都是this,而method1和method2又是通過同一的對象去調用的,是以進入同步塊之前需要去競争同一個對象上的鎖,是以隻能順序執行同步塊。

四 總結

  Synchronized是Java并發程式設計中最常用的用于保證線程安全的方式,其使用相對也比較簡單。但是如果能夠深入了解其原理,對螢幕鎖等底層知識有所了解,一方面可以幫助我們正确的使用Synchronized關鍵字,另一方面也能夠幫助我們更好的了解并發程式設計機制,有助我們在不同的情況下選擇更優的并發政策來完成任務。對平時遇到的各種并發問題,也能夠從容的應對。

 作者:liuxiaopeng

 部落格位址:http://www.cnblogs.com/paddix/ 

 聲明:轉載請在文章頁面明顯位置給出原文連接配接。 

package com.paddx.test.concurrent;

public class SynchronizedTest {
    public void method1(){
        System.out.println("Method 1 start");
        try {
            System.out.println("Method 1 execute");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 1 end");
    }

    public void method2(){
        System.out.println("Method 2 start");
        try {
            System.out.println("Method 2 execute");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 2 end");
    }

    public static void main(String[] args) {
        final SynchronizedTest test = new SynchronizedTest();

        new Thread(new Runnable() {
            @Override
            public void run() {
                test.method1();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                test.method2();
            }
        }).start();
    }
}
      

Method

1

start

Method

1

execute

Method

2

start

Method

2

execute

Method

2

end

Method

1

end

package com.paddx.test.concurrent;

public class SynchronizedTest {
    public synchronized void method1(){
        System.out.println("Method 1 start");
        try {
            System.out.println("Method 1 execute");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 1 end");
    }

    public synchronized void method2(){
        System.out.println("Method 2 start");
        try {
            System.out.println("Method 2 execute");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 2 end");
    }

    public static void main(String[] args) {
        final SynchronizedTest test = new SynchronizedTest();

        new Thread(new Runnable() {
            @Override
            public void run() {
                test.method1();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                test.method2();
            }
        }).start();
    }
}
      

Method

1

start

Method

1

execute

Method

1

end

Method

2

start

Method

2

execute

Method

2

end

package com.paddx.test.concurrent;
 
 public class SynchronizedTest {
     public static synchronized void method1(){
         System.out.println("Method 1 start");
         try {
             System.out.println("Method 1 execute");
             Thread.sleep(3000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println("Method 1 end");
     }
 
     public static synchronized void method2(){
         System.out.println("Method 2 start");
         try {
             System.out.println("Method 2 execute");
             Thread.sleep(1000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println("Method 2 end");
     }
 
     public static void main(String[] args) {
         final SynchronizedTest test = new SynchronizedTest();
         final SynchronizedTest test2 = new SynchronizedTest();
 
         new Thread(new Runnable() {
             @Override
             public void run() {
                 test.method1();
             }
         }).start();
 
         new Thread(new Runnable() {
             @Override
             public void run() {
                 test2.method2();
             }
         }).start();
     }
 }
      

Method

1

start

Method

1

execute

Method

1

end

Method

2

start

Method

2

execute

Method

2

end

package com.paddx.test.concurrent;

public class SynchronizedTest {
    public void method1(){
        System.out.println("Method 1 start");
        try {
            synchronized (this) {
                System.out.println("Method 1 execute");
                Thread.sleep(3000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 1 end");
    }

    public void method2(){
        System.out.println("Method 2 start");
        try {
            synchronized (this) {
                System.out.println("Method 2 execute");
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 2 end");
    }

    public static void main(String[] args) {
        final SynchronizedTest test = new SynchronizedTest();

        new Thread(new Runnable() {
            @Override
            public void run() {
                test.method1();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                test.method2();
            }
        }).start();
    }
}
      

Method

1

start

Method

1

execute

Method

2

start

Method

1

end

Method

2

execute

Method

2

end

package com.paddx.test.concurrent;

public class SynchronizedDemo {
    public void method() {
        synchronized (this) {
            System.out.println("Method 1 start");
        }
    }
}
      
Java并發程式設計:Synchronized及其實作原理
package com.paddx.test.concurrent;

public class SynchronizedMethod {
    public synchronized void method() {
        System.out.println("Hello World!");
    }
}
      
Java并發程式設計:Synchronized及其實作原理