synchronized可修飾普通方法、靜態方法和代碼塊 。
修飾普通方法,鎖的是對象,一個對象有多個synchronized方法,只要一個線程訪問了其中的一個synchronized方法,其它線程不能同時訪問這個對象中任何一個synchronized方法。
public synchronized void sayHello(){
}
修飾靜態方法,鎖的是類(new多個對象都是源於同一個類,同步靜態方法仍然互鎖)
此類所有的實例對象,都無法同時訪問類中的所有同步靜態(synchronized static)方法
public synchronized static void sayHello(){
}
修飾代碼塊,取決於鎖的是什麼(synchronized(this) synchronized(My.class) )
synchronized(this){
}
1 原理
synchronized是基於JVM層面的,lock是基於JDK層面的
- 代碼塊同步原理
package com.paddx.test.concurrent;
public class SynchronizedDemo {
public void method() {
synchronized (this) {
System.out.println("Method 1 start");
}
}
}
javap 進行class文件反編譯
每個對象有一個監視器鎖(monitor)。當monitor被佔用時就會處於鎖定狀態,線程執行monitorenter指令時嘗試獲取monitor的所有權,過程如下:
1、如果monitor的進入數爲0,則該線程進入monitor,然後將進入數設置爲1,該線程即爲monitor的所有者。
2、如果線程已經佔有該monitor,只是重新進入,則進入monitor的進入數加1.
3、如果其他線程已經佔用了monitor,則該線程進入阻塞狀態,直到monitor的進入數爲0,再重新嘗試獲取monitor的所有權。
執行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!");
}
}
2 幾種鎖類型
synchronized按鎖的量級從輕到重分爲:無鎖---> 偏向鎖--->輕量鎖---->重量鎖 鎖只能升級不能降級
偏向鎖:如果目前只有一個線程多次獲得鎖,就沒必要下次再用鎖的時候重新競爭,輕量級鎖的獲取及釋放依賴多次CAS原子指令,而偏向鎖只需要在置換ThreadID的時候依賴一次CAS原子指令,下次還是這個線程來用對象就可以無需驗證直接獲得鎖。如果有多個線程搶用鎖,則撤銷偏向鎖並膨脹爲輕量鎖。 無阻塞 無自旋 加鎖解鎖無消耗 適用於無多線程執行同步代碼的情況
輕量鎖:競爭鎖的線程首先需要拷貝對象頭中的Mark Word到幀棧的鎖記錄中。拷貝成功後使用CAS操作嘗試將對象的Mark Word更新爲指向當前線程的指針。如果這個更新動作成功了,那麼這個線程就擁有了該對象的鎖。如果更新失敗,那麼意味着有多個線程在競爭。
當競爭線程嘗試佔用輕量級鎖失敗多次之後(使用自旋)輕量級鎖就會膨脹爲重量級鎖,重量級線程指針指向競爭線程,競爭線程也會阻塞,等待輕量級線程釋放鎖後喚醒他。 有自旋 無阻塞 佔用CPU 適用同步代碼執行快的情況
重量鎖:競爭失敗後,線程阻塞,釋放鎖後,喚醒阻塞的線程,不使用自旋鎖,不會那麼消耗CPU,所以重量級鎖適合用在同步塊執行時間長的情況下。 有阻塞 無自旋
還有其他幾種鎖類型作爲延伸閱讀。
互斥鎖:mutex,用於保證在任何時刻,都只能有一個線程訪問該對象。當獲取鎖操作失敗時,線程會進入睡眠,等待鎖釋放時被喚醒
自旋鎖:spinlock,在任何時刻同樣只能有一個線程訪問對象。但是當獲取鎖操作失敗時,不會進入睡眠,而是會在原地自旋,直到鎖被釋放。這樣節省了線程從睡眠狀態到被喚醒期間的消耗,在加鎖時間短暫的環境下會極大的提高效率。線程不會被掛起,而是在不斷的消耗CPU的時間,不停的試圖獲取鎖,雖然CPU的時間被消耗了,但是比線程下文切換時間要少。這個時候使用自旋是划算的。
讀寫鎖:rwlock,區分讀和寫,處於讀操作時,可以允許多個線程同時獲得讀操作。但是同一時刻只能有一個線程可以獲得寫鎖。其它獲取寫鎖失敗的線程都會進入睡眠狀態,直到寫鎖釋放時被喚醒。
注意:寫鎖會阻塞其它讀寫鎖。當有一個線程獲得寫鎖在寫時,讀鎖也不能被其它線程獲取;寫優先於讀,當有線程因爲等待寫鎖而進入睡眠時,則後續讀者也必須等待
適用於讀取數據的頻率遠遠大於寫數據的頻率的場合。
3 synchronized搶佔鎖實例
3.1 synchronized(this)
public class Synchronized {
public void method1(){
synchronized (this) {
System.out.println("method1 start");
try {
System.out.println("Method 1 execute");
Thread.sleep(3000);
} catch (Exception e) {
}
System.out.println("method1 end");
}
}
public void method2(){
synchronized (this) {
System.out.println("method2 start");
try {
System.out.println("Method 2 execute");
Thread.sleep(1000);
} catch (Exception e) {
}
System.out.println("method2 end");
}
}
public static void main(String[] args) {
System.out.println("程序運行");
Synchronized syn = new Synchronized();
// Synchronized syn2 = new Synchronized();
new Thread(()->{
syn.method1();
}).start();
new Thread(()->{
syn.method2();
}).start();
}
}
結果自然是先執行method1,再執行method2
程序運行
method1 start
Method 1 execute
method1 end
method2 start
Method 2 execute
method2 end
3.2 synchronized(*.class)
public class Synchronized {
public void method1(){
synchronized (Synchronized.class) {
System.out.println("method1 start");
try {
System.out.println("Method 1 execute");
Thread.sleep(3000);
} catch (Exception e) {
}
System.out.println("method1 end");
}
}
public void method2(){
synchronized (Synchronized.class) {
System.out.println("method2 start");
try {
System.out.println("Method 2 execute");
Thread.sleep(1000);
} catch (Exception e) {
}
System.out.println("method2 end");
}
}
public static void main(String[] args) {
System.out.println("程序運行");
Synchronized syn = new Synchronized();
Synchronized syn2 = new Synchronized();
new Thread(()->{
syn.method1();
}).start();
new Thread(()->{
syn2.method2();
}).start();
}
}
鎖的是類,那麼該類生成的所有實例對象都會被鎖住。
程序運行
method1 start
Method 1 execute
method1 end
method2 start
Method 2 execute
method2 end
3.3 synchronized(*.class) synchronized(this)
public class Synchronized {
public void method1(){
synchronized (Synchronized.class) {
System.out.println("method1 start");
try {
System.out.println("Method 1 execute");
Thread.sleep(3000);
} catch (Exception e) {
}
System.out.println("method1 end");
}
}
public void method2(){
synchronized (this) {
System.out.println("method2 start");
try {
System.out.println("Method 2 execute");
Thread.sleep(1000);
} catch (Exception e) {
}
System.out.println("method2 end");
}
}
public static void main(String[] args) {
System.out.println("程序運行");
Synchronized syn = new Synchronized();
// Synchronized syn2 = new Synchronized();
new Thread(()->{
syn.method1();
}).start();
new Thread(()->{
syn.method2();
}).start();
}
}
一個鎖的是 .class文件,一個鎖的是對象,那麼到底這兩個方法會互斥執行麼?
答案是不會,因爲本質上鎖的就不是同一個對象!!!
程序運行
method1 start
Method 1 execute
method2 start
Method 2 execute
method2 end
method1 end
3.4 synchronized static 方法
public class Synchronized {
public synchronized static void method1(){
System.out.println("method1 start");
try {
System.out.println("Method 1 execute");
Thread.sleep(3000);
} catch (Exception e) {
}
System.out.println("method1 end");
}
public synchronized static void method2(){
System.out.println("method2 start");
try {
System.out.println("Method 2 execute");
Thread.sleep(1000);
} catch (Exception e) {
}
System.out.println("method2 end");
}
public static void main(String[] args) {
System.out.println("程序運行");
Synchronized syn = new Synchronized();
Synchronized syn2 = new Synchronized();
new Thread(()->{
syn.method1();
}).start();
new Thread(()->{
syn2.method2();
}).start();
}
}
鎖加在靜態方法上,鎖的是class對象,那麼new 的新對象自然也會搶佔鎖
3.5 synchronized static 和 synchronized 混合使用
public class Synchronized {
public synchronized static void method1(){
System.out.println("method1 start");
try {
System.out.println("Method 1 execute");
Thread.sleep(3000);
} catch (Exception e) {
}
System.out.println("method1 end");
}
public synchronized void method2(){
System.out.println("method2 start");
try {
System.out.println("Method 2 execute");
Thread.sleep(1000);
} catch (Exception e) {
}
System.out.println("method2 end");
}
public static void main(String[] args) {
System.out.println("程序運行");
Synchronized syn = new Synchronized();
Synchronized syn2 = new Synchronized();
new Thread(()->{
syn.method1();
}).start();
new Thread(()->{
syn2.method2();
}).start();
}
}
一個鎖的class,一個鎖的方法,自然不會發生互斥地現象。synchronized加在static方法上,只會鎖其他synchronized static方法