線程安全是併發編程中的重要關注點,應該注意到的是,形成線程安全問題的主要誘因有兩點java
所以爲了解決這個問題,咱們可能須要這樣一個方案,當存在多個線程操做共享數據時,須要保證同一時刻有且只有一個線程在操做共享數據,其餘線程必須等到該線程處理完數據後再進行,這種方式有個高尚的名稱叫互斥鎖,即能達到互斥訪問目的的鎖,也就是說當一個共享數據被當前正在訪問的線程加上互斥鎖後,在同一個時刻,其餘線程只能處於等待的狀態,直到當前線程處理完畢釋放該鎖。
在 Java 中,關鍵字 synchronized 能夠保證在同一個時刻,只有一個線程能夠執行某個方法或者某個代碼塊(主要是對方法或者代碼塊中存在共享數據的操做),同時咱們還應該注意到synchronized另一個重要的做用,synchronized可保證一個線程的變化(主要是共享數據的變化)被其餘線程所看到(保證可見性,徹底能夠替代Volatile功能),這點確實也是很重要的。編程
synchronized關鍵字最主要有如下3種應用方式,下面分別介紹安全
所謂的實例對象鎖就是用synchronized修飾實例對象中的實例方法,注意是實例方法不包括靜態方法併發
package sychronized; import static net.mindview.util.Print.*; import java.util.concurrent.*; public class AccountingSync2 implements Runnable { //共享資源(臨界資源) static int i = 0; /** * synchronized 修飾實例方法 */ synchronized void getI() { if (i % 1000000 == 0) { print(i); } } public synchronized void increase() { i++; getI(); } @Override public void run() { for (int j = 0; j < 1000000; j++) { increase(); } print(i); } public static void main(String[] args) throws InterruptedException { ExecutorService exec = Executors.newCachedThreadPool(); AccountingSync2 accountingSync2 = new AccountingSync2(); exec.execute(accountingSync2); exec.execute(accountingSync2); exec.shutdown(); } } 最後的結果爲: 1000000 1519541 2000000 2000000
package sychronized; import static net.mindview.util.Print.*; import java.util.concurrent.*; public class AccountingSync2 implements Runnable { //共享資源(臨界資源) static int i = 0; /** * synchronized 修飾實例方法 */ synchronized void getI() { if (i % 1000000 == 0) { print(i); } } public synchronized void increase() { i++; getI(); } @Override public void run() { for (int j = 0; j < 1000000; j++) { increase(); } print(i); } public static void main(String[] args) throws InterruptedException { ExecutorService exec = Executors.newCachedThreadPool(); AccountingSync2 accountingSync2 = new AccountingSync2(); exec.execute(accountingSync2); exec.execute(new AccountingSync2()); exec.shutdown(); } } #輸出結果: 1000000 1249050 1329218
當synchronized做用於靜態方法時,其鎖就是當前類的class對象鎖。因爲靜態成員不專屬於任何一個實例對象,是類成員,所以經過class對象鎖能夠控制靜態成員的併發操做。須要注意的是若是一個線程A調用一個實例對象的非static synchronized 方法,而線程B須要調用這個實例對象所屬類的靜態 synchronized方法,是容許的,不會發生互斥現象,由於訪問靜態 synchronized 方法佔用的鎖是當前類的 class 對象,而訪問非靜態 synchronized 方法佔用的鎖是當前實例對象鎖。ide
package sychronized; import static net.mindview.util.Print.*; import java.util.concurrent.*; class OtherTask implements Runnable{ AccountingSyncClass accounting = new AccountingSyncClass(); @Override public void run(){ for (int j = 0; j < 1000000; j++) { accounting.increaseForObject(); } print(accounting.getI()); } } public class AccountingSyncClass implements Runnable { //共享資源(臨界資源) private static int i = 0; /** * synchronized 修飾實例方法 */ public synchronized void increaseForObject() { i++; } public synchronized static void increase() { i++; } @Override public void run() { for (int j = 0; j < 1000000; j++) { increase(); } print(i); } public int getI(){ return i; } public static void main(String[] args) throws InterruptedException { ExecutorService exec = Executors.newCachedThreadPool(); exec.execute(new AccountingSyncClass()); exec.execute(new AccountingSyncClass()); exec.execute(new OtherTask()); // 1 exec.shutdown(); } } 輸出結果爲: 1459696 2692181 2754098 註釋掉代碼中的 1 那一行代碼的輸出結果爲: 1468495 2000000
除了使用關鍵字修飾實例方法和靜態方法外,還可使用同步代碼塊,在某些狀況下,咱們編寫的方法體可能比較大,同時存在一些比較耗時的操做,而須要同步的代碼又只有一小部分,若是直接對整個方法進行同步操做,可能會得不償失,此時咱們可使用同步代碼塊的方式對須要同步的代碼進行包裹,這樣就無需對整個方法進行同步操做了。this
public class AccountingSync implements Runnable{ static AccountingSync instance=new AccountingSync(); static int i=0; @Override public void run() { //省略其餘耗時操做.... //使用同步代碼塊對變量i進行同步操做,鎖對象爲instance synchronized(instance){ for(int j=0;j<1000000;j++){ i++; } } } public static void main(String[] args) throws InterruptedException { Thread t1=new Thread(instance); Thread t2=new Thread(instance); t1.start();t2.start(); t1.join();t2.join(); System.out.println(i); } }
//this,當前實例對象鎖 synchronized(this){ for(int j=0;j<1000000;j++){ i++; } } //class對象鎖 synchronized(AccountingSync.class){ for(int j=0;j<1000000;j++){ i++; } }
package sychronized; import static net.mindview.util.Print.*; import java.util.concurrent.*; class OtherTask implements Runnable{ AccountingSyncClass accounting = new AccountingSyncClass(); @Override public void run(){ for (int j = 0; j < 1000000; j++) { accounting.increaseForObject(); } print(accounting.getAdapterInteger()); } } class AdapterInteger { private int i = 0; public synchronized void increase(){ ++i; } public synchronized int getI(){ return i; } } public class AccountingSyncClass implements Runnable { //共享資源(臨界資源) private static AdapterInteger adapterInteger = new AdapterInteger(); /** * synchronized 修飾實例方法 */ public synchronized void increaseForObject() { adapterInteger.increase(); } public synchronized static void increase() { adapterInteger.increase(); } @Override public void run() { for (int j = 0; j < 1000000; j++) { increase(); } print(getAdapterInteger()); } public static int getAdapterInteger() { return adapterInteger.getI(); } public static void main(String[] args) throws InterruptedException { ExecutorService exec = Executors.newCachedThreadPool(); exec.execute(new AccountingSyncClass()); exec.execute(new AccountingSyncClass()); exec.execute(new OtherTask()); exec.shutdown(); } } #輸出結果爲 1183139 2688189 3000000
這樣三個線程中的任務的鎖都是咱們的共享變量 adapterInteger 對象的鎖,這樣就能夠完成真正的同步,無論哪一個線程都是得到了 adapterInteger 對象的鎖才能運行相應的代碼。線程