java 線程安全 synchronized

lhjq780 8年前發布 | 7K 次閱讀 線程 Java Java開發

一、線程安全問題:

并發編程的原則:設計并發編程的目的是為了使程序獲得更高的執行效率,但絕不能出現數據一致性(數據準確)問題,如果并發程序連最基本的執行結果準確性都無法保證,那并發編程就沒有任何意義。

為什么會出現數據不正確:

如果一個資源(變量,對象,文件,數據庫)可以同時被很多線程使用就會出現數據不一致問題,也就是我們說的線程安全問題。這樣的資源被稱為共享資源或臨界區。

舉個例子:

一個共享變量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++; } } } </code></pre>

無論運行多少次 m總是小于20000。為什么會出現這樣的結果呢?當線程thread1在將m++的結果寫入內存之前,線程thread2已經從內存中讀取了m的值,并在這個值(過時值)上進行++操作,最后將m=1寫入內存中(可能就覆蓋了thread1計算的m=1的值,也可能是出現thread1覆蓋了thread2的值)。出現這樣的結果是必然的。

如何控制多線程操作共享數據引起的數據準確性問題呢?使用“序列化訪問臨界資源”的方案,即在同一時刻,只能有一個線程訪問臨界資源,也稱作同步互斥訪問,也就是保證我們的共享資源每次只能被一個線程使用,一旦該資源被線程使用,其他線程將不得擁有使用權

。在Java中,提供了兩種方式來實現同步互斥訪問:synchronized和Lock。

二、互斥訪問之synchronized(同步方法或者同步塊)

互斥鎖:顧名思義,就是互斥訪問目的的鎖。

舉個簡單的例子:如果對臨界資源加上互斥鎖,當一個線程在訪問該臨界資源時,其他線程便只能等待。

在Java中,每一個對象都擁有一個鎖標記(monitor),也稱為監視器,多線程同時訪問某個對象時,只有擁有該對象鎖的線程才能訪問。

在Java中,可以使用synchronized關鍵字來標記一個需要同步的方法或者同步代碼塊,當某個線程調用該對象的synchronized方法或者訪問synchronized代碼塊時,這個線程便獲得了該對象的鎖,其他線程暫時無法訪問這個方法,只有等待這個方法執行完畢或者代碼塊執行完畢,這個線程才會釋放該對象的鎖,其他線程才能執行這個方法或者代碼塊。通過這種方式達到我們上面提到的在同一時刻,只能有一個線程訪問臨界資源 。

synchronized用法:

1、同步代碼塊

synchronized(synObject) {

}

</code></pre>

用法:將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++; } } } } </code></pre>

該代碼是使用當前對象作為互斥鎖,下面我們使用類的一個屬性作為互斥鎖。

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++; } } } } </code></pre>

1、同步方法

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++; } } } </code></pre>

這段代碼中,synchronzied作用于一個實例方法,就是說當線程在進入run()方法前,必須獲取當前對象實例鎖,本例中對象實例鎖就是run。在這里提醒大家認真看這三段代碼中main函數的實現,在這里我們使用Runnable創建兩個線程,并且這兩個線程都指向同一個Runnable接口實例,這樣才能保證兩個線程在工作中,使用同一個對象鎖,從而保證線程安全。

 

來自:http://www.cnblogs.com/jalja/p/5862449.html

 

 本文由用戶 lhjq780 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!