java 線程安全 synchronized
一、線程安全問題:
并發編程的原則:設計并發編程的目的是為了使程序獲得更高的執行效率,但絕不能出現數據一致性(數據準確)問題,如果并發程序連最基本的執行結果準確性都無法保證,那并發編程就沒有任何意義。
為什么會出現數據不正確:
如果一個資源(變量,對象,文件,數據庫)可以同時被很多線程使用就會出現數據不一致問題,也就是我們說的線程安全問題。這樣的資源被稱為共享資源或臨界區。
舉個例子:
一個共享變量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