併發編程的原則:設計併發編程的目的是爲了使程序得到更高的執行效率,但毫不能出現數據一致性(數據準確)問題,若是併發程序連最基本的執行結果準確性都沒法保證,那併發編程就沒有任何意義。數據庫
爲何會出現數據不正確:編程
若是一個資源(變量,對象,文件,數據庫)能夠同時被不少線程使用就會出現數據不一致問題,也就是咱們說的線程安全問題。這樣的資源被稱爲共享資源或臨界區。安全
舉個例子:多線程
一個共享變量m,如今有兩個線程同時對它進行累加操做,各執行10000次,那麼我麼期待的結果是20000,但實際上並非這樣的。看代碼:併發
package com.jalja.base.threadTest; public class SynchronizedTest implements Runnable{ private static volatile int m=0; public static void main(String[] args) { Runnable run=new SynchronizedTest(); Thread thread1=new Thread(run); Thread thread2=new Thread(run); thread1.start(); thread2.start(); try { //join() 使main線程等待這連個線程執行結束後繼續執行下面的代碼 thread1.join(); thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("m的最終結果:"+m); } public void run() { for(int i=0;i<10000;i++){ m++; } } }
不管運行多少次 m老是小於20000。爲何會出現這樣的結果呢?當線程thread1在將m++的結果寫入內存以前,線程thread2已經從內存中讀取了m的值,並在這個值(過期值)上進行++操做,最後將m=1寫入內存中(可能就覆蓋了thread1計算的m=1的值,也多是出現thread1覆蓋了thread2的值)。出現這樣的結果是必然的。
如何控制多線程操做共享數據引發的數據準確性問題呢?使用「序列化訪問臨界資源」的方案,即在同一時刻,只能有一個線程訪問臨界資源,也稱做同步互斥訪問,也就是保證咱們的共享資源每次只能被一個線程使用,一旦該資源被線程使用,其餘線程將不得擁有使用權。在Java中,提供了兩種方式來實現同步互斥訪問:synchronized和Lock。函數
互斥鎖:顧名思義,就是互斥訪問目的的鎖。post
舉個簡單的例子:若是對臨界資源加上互斥鎖,當一個線程在訪問該臨界資源時,其餘線程便只能等待。this
在Java中,每個對象都擁有一個鎖標記(monitor),也稱爲監視器,多線程同時訪問某個對象時,只有擁有該對象鎖的線程才能訪問。spa
在Java中,能夠使用synchronized關鍵字來標記一個須要同步的方法或者同步代碼塊,當某個線程調用該對象的synchronized方法或者訪問synchronized代碼塊時,這個線程便得到了該對象的鎖,其餘線程暫時沒法訪問這個方法,只有等待這個方法執行完畢或者代碼塊執行完畢,這個線程纔會釋放該對象的鎖,其餘線程才能執行這個方法或者代碼塊。經過這種方式達到咱們上面提到的在同一時刻,只能有一個線程訪問臨界資源。線程
synchronized用法:
一、同步代碼塊
synchronized(synObject) { }
用法:將synchronized做用於一個給定的對象或類的一個屬性,因此每當有線程執行這段代碼塊,該線程會先請求獲取對象synObject的鎖,若是該鎖已被其餘線程佔有,那麼新的線程只能等待,從而使得其餘線程沒法同時訪問該代碼塊。
package com.jalja.base.threadTest; public class SynchronizedTest implements Runnable{ private static volatile int m=0; public static void main(String[] args) { Runnable run=new SynchronizedTest(); Thread thread1=new Thread(run); Thread thread2=new Thread(run); thread1.start(); thread2.start(); try { //join() 使main線程等待這連個線程執行結束後繼續執行下面的代碼 thread1.join(); thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("m的最終結果:"+m); } public void run() { synchronized (this) { for(int i=0;i<10000;i++){ m++; } } } }
該代碼是使用當前對象做爲互斥鎖,下面咱們使用類的一個屬性做爲互斥鎖。
package com.jalja.base.threadTest; public class SynchronizedTest implements Runnable{ private static volatile int m=0; private Object object=new Object(); public static void main(String[] args) { Runnable run=new SynchronizedTest(); Thread thread1=new Thread(run); Thread thread2=new Thread(run); thread1.start(); thread2.start(); try { //join() 使main線程等待這連個線程執行結束後繼續執行下面的代碼 thread1.join(); thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("m的最終結果:"+m); } public void run() { synchronized (object) { for(int i=0;i<10000;i++){ m++; } } } }
一、同步方法
package com.jalja.base.threadTest; public class SynchronizedTest implements Runnable{ private static int m=0; public static void main(String[] args) { Runnable run=new SynchronizedTest(); Thread thread1=new Thread(run); Thread thread2=new Thread(run); thread1.start(); thread2.start(); try { //join() 使main線程等待這連個線程執行結束後繼續執行下面的代碼 thread1.join(); thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("m的最終結果:"+m); } public synchronized void run() { for(int i=0;i<10000;i++){ m++; } } }
這段代碼中,synchronzied做用於一個實例方法,就是說當線程在進入run()方法前,必須獲取當前對象實例鎖,本例中對象實例鎖就是run。在這裏提醒你們認真看這三段代碼中main函數的實現,在這裏咱們使用Runnable建立兩個線程,而且這兩個線程都指向同一個Runnable接口實例,這樣才能保證兩個線程在工做中,使用同一個對象鎖,從而保證線程安全。
一種錯誤的理解:
package com.jalja.base.threadTest; public class SynchronizedTest implements Runnable{ private static int m=0; public static void main(String[] args) { Thread thread1=new Thread(new SynchronizedTest()); Thread thread2=new Thread(new SynchronizedTest()); thread1.start(); thread2.start(); try { //join() 使main線程等待這連個線程執行結束後繼續執行下面的代碼 thread1.join(); thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("m的最終結果:"+m); } public synchronized void run() { for(int i=0;i<10000;i++){ m++; } } }
這段代碼的運行結果是錯誤的,請看main函數的實現方式,使用Runnable建立兩個線程,可是兩個線程擁有各自的Runnable實例,因此當thread1線程進入同步方法時加的是本身的對象實例鎖,而thread2在進入同步方法時關注的是本身的實例鎖,兩個線程擁有不一樣的對象實例鎖,所以沒法達到互斥的要求。
略做改動:
package com.jalja.base.threadTest; public class SynchronizedTest implements Runnable{ private static int m=0; public static void main(String[] args) { Thread thread1=new Thread(new SynchronizedTest()); Thread thread2=new Thread(new SynchronizedTest()); thread1.start(); thread2.start(); try { //join() 使main線程等待這連個線程執行結束後繼續執行下面的代碼 thread1.join(); thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("m的最終結果:"+m); } public void run() { for(int i=0;i<10000;i++){ count(); } } public static synchronized void count(){ m++; } }
這樣處理結果就是我麼想要的了,在這裏咱們將處理業務的代碼封裝成一個靜態的同步方法,那如今訪問該同步方法須要的是當前類的鎖,而類在內存中只有一份,因此不管如何,他們使用的都是同一個鎖(class級別的鎖)。