1. 程式人生 > >Java執行緒-同步和非同步的區別

Java執行緒-同步和非同步的區別

1.

                                                                同步與非同步

   多執行緒併發時,多個執行緒同時請求同一個資源,必然導致此資源的資料不安全,A執行緒修改了B線

程的處理的資料,而B執行緒又修改了A執行緒處理的數理。顯然這是由於全域性資源造成的,有時為了解

決此問題,優先考慮使用區域性變數,退而求其次使用同步程式碼塊,出於這樣的安全考慮就必須犧牲

系統處理效能,加在多執行緒併發時資源掙奪最激烈的地方,這就實現了執行緒的同步機制


同步:A執行緒要請求某個資源,但是此資源正在被B執行緒使用中,因為同步機制存在,A執行緒請求

      不到,怎麼辦,A執行緒只能等待下去

非同步:A執行緒要請求某個資源,但是此資源正在被B執行緒使用中,因為沒有同步機制存在,A執行緒

      仍然請求的到,A執行緒無需等待


顯然,同步最最安全,最保險的。而非同步不安全,容易導致死鎖,這樣一個執行緒死掉就會導致整個

程序崩潰,但沒有同步機制的存在,效能會有所提升


Java中實現多執行緒

1)繼承Thread,重寫裡面的run方法

2)實現runnable介面

 

舉個例子:普通B/S模式(同步)AJAX技術(非同步)

同步:提交請求->等待伺服器處理->處理完返回這個期間客戶端瀏覽器不能幹任何事

非同步:請求通過事件觸發->伺服器處理(這是瀏覽器仍然可以作其他事情)->處理完畢

可見,彼“同步”非此“同步”——我們說的java中的那個共享資料同步(synchronized)

一個同步的物件是指行為(動作),一個是同步的物件是指物質(共享資料)。


一、關鍵字:

thread(執行緒)、thread-safe(執行緒安全)、intercurrent(併發的)

synchronized(同步的)、asynchronized(非同步的)、

volatile(易變的)、atomic(原子的)、share(共享)

二、概念

1.

要跨執行緒維護正確的可見性,只要在幾個執行緒之間分享非 final 變數,就必須使用 synchronized(或 volatile)

以確保一個執行緒可以看見另一個執行緒做的更改。

2、

為了線上程之間進行可靠的通訊,也為了互斥訪問,同步是必須的。這歸因於java語言規範的記憶體模型,它規定了:

一個執行緒所做的變化何時以及如何變成對其它執行緒可見。

3.

多執行緒將非同步行為引程序序,所以在需要同步時,必須有一種方法強制進行。例如:如果2個線程想要通訊並且要

共享一個複雜的資料結構,如連結串列,此時需要確保它們互不衝突,也就是必須阻止B執行緒在A執行緒讀資料的過程中

向連結串列裡面寫資料(A獲得了鎖,B必須等A釋放了該鎖)

4.同步

java在一箇舊的的程序同步模型——監控器(Monitor)的基礎上實現了一個巧妙的方案:監控器是一個控制機制,

可以認為是一個很小的、只能容納一個執行緒的盒子,一旦一個執行緒進入監控器,其它的執行緒必須等待,直到那個

執行緒退出監控為止。通過這種方式,一個監控器可以保證共享資源在同一時刻只可被一個執行緒使用。這種方式稱

之為同步。(一旦一個執行緒進入一個例項的任何同步方法,別的執行緒將不能進入該同一例項的其它同步方法,

但是該例項的非同步方法仍然能夠被呼叫)。

5.同步和多執行緒關係:沒多執行緒環境就不需要同步;有多執行緒環境也不一定需要同步。


6.鎖提供了兩種主要特性:互斥(mutual exclusion) 和可見性(visibility)

 

互斥即一次只允許一個執行緒持有某個特定的鎖,因此可使用該特性實現對共享資料的協調訪問協議

,這樣,一次就只有一個執行緒能夠使用該共享資料。


可見性要更加複雜一些,它必須確保釋放鎖之前對共享資料做出的更改對於隨後獲得該鎖的另一個

執行緒是可見的 —— 如果沒有同步機制提供的這種可見性保證,執行緒看到的共享變數可能是修改前

的值或不一致的值,這將引發許多嚴重問題


小結:為了防止多個執行緒併發對同一資料的修改,所以需要同步,否則會造成資料不一致(就是所

謂的:執行緒安全。如java集合框架中Hashtable和Vector是執行緒安全的。我們的大部分程式都不是線

程安全的,因為沒有進行同步,而且我們沒有必要,因為大部分情況根本沒有多執行緒環境)


7. 不要搞混了:同步、非同步


舉個例子:普通B/S模式(同步)AJAX技術(非同步)同步:提交請求->等待伺服器處理->處理完返回 這個期間客戶端瀏覽器不能幹任何事

非同步:請求通過事件觸發->伺服器處理(這是瀏覽器仍然可以作其他事情)->處理完畢可見,彼“同步”非此“同步”

——我們說的java中的那個共享資料同步(synchronized)

一個同步的物件是指行為(動作),一個是同步的物件是指物質(共享資料)。

8、

 Java同步機制有4種實現方式:(部分引用網上資源)

①    ThreadLocal ② synchronized( ) ③ wait() 與 notify() ④ volatile

目的:都是為了解決多執行緒中的對同一變數的訪問衝突


ThreadLocal 保證不同執行緒擁有不同例項,相同執行緒一定擁有相同的例項,即為每一個使用該變數的

執行緒提供一     個該變數值的副本,每一個執行緒都可以獨立改變自己的副本,而不是與其它執行緒

副本衝突。

優勢:提供了執行緒安全的共享物件

與其它同步機制的區別:同步機制是為了同步多個執行緒對相同資源的併發訪問,是為了多個執行緒之

間進行通訊;而 ThreadLocal 是隔離多個執行緒的資料共享,從根本上就不在多個執行緒之間共享資源

這樣當然不需要多個執行緒進行同步了。

   volatile
    volatile 修飾的成員變數在每次被執行緒訪問時,都強迫從共享記憶體中重讀該成員變數的值。而且

當成員變數發生變化時,強迫執行緒將變化值回寫到共享記憶體。

優勢:這樣在任何時刻,兩個不同的執行緒總是看到某個成員變數的同一個值。緣由:Java 語言規範

中指出,為了獲得最佳速度,允許執行緒儲存共享成員變數的私有拷貝,而且只當執行緒進入或者離開

同步程式碼塊時才與共享成員變數的原始值對比。這樣當多個執行緒同時與某個物件互動時,就必須要

注意到要讓執行緒及時的得到共享成員變數的變化。而 volatile 關鍵字就是提示 VM :對於這個成

員變數不能儲存它的私有拷貝,而應直接與共享成員變數互動。

使用技巧:在兩個或者更多的執行緒訪問的成員變數上使用 volatile 。當要訪問的變數已在

synchronized 程式碼塊中,或者為常量時,不必使用。


        執行緒為了提高效率,將某成員變數(如A)拷貝了一份(如B),執行緒中對A的訪問其實訪問的

是B。只在某些動作時才進行A和B的同步,因此存在A和B不一致的情況。volatile就是用來避免這種

情況的。 volatile告訴jvm,它所修飾的變數不保留拷貝,直接訪問主記憶體中的(讀操作多時使用

較好;執行緒間需要通訊,本條做不到)

Volatile 變數具有 synchronized 的可見性特性,但是不具備原子特性。這就是說執行緒能夠自

動發現 volatile 變數的最新值。Volatile 變數可用於提供執行緒安全,但是隻能應用於非常有限的

一組用例:多個變數之間或者某個變數的當前值與修改後值之間沒有約束。


您只能在有限的一些情形下使用 volatile 變數替代鎖。要使 volatile 變數提供理

想的執行緒安全,必須同時滿足下面兩個條件:

對變數的寫操作不依賴於當前值;該變數沒有包含在具有其他變數的不變式中。

 

  sleep() vs wait()
    sleep是執行緒類(Thread)的方法,導致此執行緒暫停執行指定時間,把執行機會給其他執行緒,但是監

控狀態依然保持,到時後會自動恢復。呼叫sleep不會釋放物件鎖。
    wait是Object類的方法,對此物件呼叫wait方法導致本執行緒放棄物件鎖,進入等待此物件的等待鎖

定池,只有針對此物件發出notify方法(或notifyAll)後本執行緒才進入物件鎖定池準備獲得物件鎖

進入執行狀態。

(如果變數被宣告為volatile,在每次訪問時都會和主存一致;如果變數在同步方法或者同步塊中

被訪問,當在方法或者塊的入口處獲得鎖以及方法或者塊退出時釋放鎖時變數被同步。)


三、例子:

Demo1:

package test.thread;

class SynTest{


//非同步

static void method(Thread thread){

System.out.println("begin "+thread.getName());

try{

Thread.sleep(2000);

}catch(Exception ex){

ex.printStackTrace();

}

System.out.println("end "+thread.getName());

}

 

//同步方式一:同步方法

synchronized static void method1(Thread thread){//這個方法是同步的方法,每次只有一

個執行緒可以進來

System.out.println("begin "+thread.getName());

try{

Thread.sleep(2000);

}catch(Exception ex){

ex.printStackTrace();

}

System.out.println("end "+thread.getName());

}

 

//同步方式二:同步程式碼塊

static void method2(Thread thread){

synchronized(SynTest.class) {

System.out.println("begin "+thread.getName());

try{

Thread.sleep(2000);

}catch(Exception ex){

ex.printStackTrace();

}

System.out.println("end "+thread.getName());

}

}

 

 

//同步方式三:使用同步物件鎖

private static Object _lock1=new Object();

private static byte _lock2[]={};//據說,此鎖更可提高效能。源於:鎖的物件越小越好

static void method3(Thread thread){

synchronized(_lock1) {

System.out.println("begin "+thread.getName());

try{

Thread.sleep(2000);

}catch(Exception ex){

ex.printStackTrace();

}

System.out.println("end "+thread.getName());

}

}

 

public static void main(String[] args){

//啟動3個執行緒,這裡用了匿名類

for(int i=0;i<3;i++){

new Thread(){

public void run(){

method(this);

//method1(this);

//method2(this);

//method3(this);

}

}.start();

}

}

}

 

 

 

 

 

 

Demo2:

package test.thread;

 

import com.util.LogUtil;

 

 

public class SynTest2 {

 

public static void main(String[] args){

Callme target=new Callme();

Caller ob1=new Caller(target,"Hello");

Caller ob2=new Caller(target,"Synchronized");

Caller ob3=new Caller(target,"World");

}

}

 

class Callme{

 

 

synchronized void test(){

LogUtil.log("測試是否是:一旦一個執行緒進入一個例項的任何同步方法,別的執行緒將不能

進入該同一例項的其它同步方法,但是該例項的非同步方法仍然能夠被呼叫");

}

 

void nonsynCall(String msg){

LogUtil.log("["+msg);

LogUtil.log("]");

}

 

synchronized void synCall(String msg){

LogUtil.logPrint("["+msg);

LogUtil.log("]");

}

}

 

class Caller implements Runnable{

String msg;

Callme target;

Thread t;

 

Caller(Callme target,String msg){

this.target=target;

this.msg=msg;

t=new Thread(this);

t.start();

}

 

public void run() {

// TODO Auto-generated method stub

//target.nonsynCall(msg);

target.synCall(msg);

target.test();

}

 

}


摘自:http://blog.csdn.net/zhaoshaolei123/article/details/26386119    侵刪