修飾非靜態成員方法java
synchronized public void sync(){ }
修飾靜態成員方法安全
synchronized public static void sync(){ }
類鎖代碼塊多線程
synchronized (類.class){ }
對象鎖代碼塊ide
synchronized (this|對象){ }
synchronized 修飾非靜態方法時能夠看作是鎖 this 對象,修飾靜態方法時能夠看作是鎖方法所在的類。函數
各個線程想要訪問被 synchronized 修飾的代碼塊,就要取得 synchronized 聲明的鎖。若是兩個線程的目標是同一個鎖,就會出現阻塞的現象,因此兩個線程不能同時訪問同一個鎖下的代碼,保證了多線程在執行時最終結果不會出錯。這與共享變量是否爲靜態無關。this
public class ThreadDemo extends Thread { @Override public synchronized void run() { for (int i = 0; i < 10000; i++) { Main.i++; } System.out.println("執行完成"); } }
直接將繼承的 run() 方法標記爲 synchronized ,做用是對 Main 類中的 i 變量作 10000 次累加操做。線程
public class Main { static int i = 0; public static void main(String[] args) throws InterruptedException { ThreadDemo threadDemo=new ThreadDemo(); Thread t1 = new Thread(threadDemo); Thread t2 = new Thread(threadDemo); Thread t3 = new Thread(threadDemo); Thread t4 = new Thread(threadDemo); t1.start(); t2.start(); t3.start(); t4.start(); t1.join(); t2.join(); t3.join(); t4.join(); System.out.println(i); } } //輸出結果: //執行完成 //執行完成 //執行完成 //執行完成 //40000
能夠看到當4個線程所有執行完畢以後,變量 i 成功的累加了 40000 次,沒有出現丟失操做的狀況。code
若是咱們將 main() 方法修改以下:對象
public static void main(String[] args) throws InterruptedException { Thread t1 = new ThreadDemo(); Thread t2 = new ThreadDemo(); Thread t3 = new ThreadDemo(); Thread t4 = new ThreadDemo(); t1.start(); t2.start(); t3.start(); t4.start(); t1.join(); t2.join(); t3.join(); t4.join(); System.out.println(i); } //輸出結果: //執行完成 //執行完成 //執行完成 //執行完成 //27579
能夠看到丟失了很多的累加操做。觀察先後兩個 main() 方法建立線程的方式能夠發現,前面的 main() 方法是使用了同一個對象來建立了4個不一樣的線程,然後一個 main() 方法使用了4個不一樣的 ThreadDemo 對象建立了4個線程。咱們用 synchronized 修飾的是一個非靜態成員函數,至關於對該方法建立了 this 的對象鎖。在第一個 main() 方法中使用同一個對象來建立 4 個不一樣線程就會讓 4 個線程爭奪同一個對象鎖,這樣,在同一時間內,僅能有一個線程能訪問 synchronized 修飾的方法。而在第二種 main() 方法中,4 個線程各自對應一個對象鎖,4 個線程之間沒有競爭關係,對象鎖天然沒法生效。繼承
public class ThreadDemo extends Thread { @Override public void run() { synchronized (ThreadDemo.class) { for (int i = 0; i < 10000; i++) { Main.i++; } System.out.println("執行完成"); } } }
將修飾方法的 synchronized 改成對 ThreadDemo.class 上鎖的代碼塊
public class ThreadDemo2 extends Thread { @Override public void run() { synchronized (ThreadDemo2.class) { for (int i = 0; i < 10000; i++) { Main.i++; } System.out.println("執行完成"); } } }
再建立一個相同的類命名爲 ThreadDemo2 ,與 ThreadDemo 不一樣的是,ThreadDemo2 中,synchronized 對 ThreadDemo2.class 上鎖。
public static void main(String[] args) throws InterruptedException { Thread t1 = new ThreadDemo(); Thread t2 = new ThreadDemo(); Thread t3 = new ThreadDemo2(); Thread t4 = new ThreadDemo2(); t1.start(); t2.start(); t3.start(); t4.start(); t1.join(); t2.join(); t3.join(); t4.join(); System.out.println(i); } //輸出結果: //執行完成 //執行完成 //執行完成 //執行完成 //33054
4 個線程分別由 ThreadDemo 和 ThreadDemo2 來建立,顯然獲得的結果與預期的 40000 不符。若是咱們將 ThreadDemo2 中的 synchronized 改成對 ThreadDemo.class 上鎖:
public class ThreadDemo2 extends Thread { @Override public void run() { synchronized (ThreadDemo.class) { for (int i = 0; i < 10000; i++) { Main.i++; } System.out.println("執行完成"); } } } //輸出結果: //執行完成 //執行完成 //執行完成 //執行完成 //40000
能夠看到,雖然是聲明在兩個不一樣的類中的 synchronized 代碼塊,可是因爲都是對 ThreadDemo.class 上鎖,因此 4 個線程之間仍是創建了競爭關係,同時只能有一個線程訪問被 synchronized 修飾的代碼。
因此 synchronized 關鍵字的本質是限制線程訪問一段代碼,而限制的條件就是,在全部被加上相同鎖的代碼上,同一時間,只能有一個線程在運行。這與你要修改什麼樣的共享變量無關。在我剛接觸到的時候覺得類鎖和對象鎖是分別針對靜態共享變量和非靜態共享變量的,但事實上鎖的是要執行的代碼塊,而不是代碼塊將要訪問的共享變量。