java非同步轉同步的實現方法的簡單介紹

本文目錄一覽:

java多線程開發的同步機制有哪些

Java同步

標籤: 分類:

一、關鍵字:

thread(線程)、thread-safe(線程安全)、intercurrent(並發的)

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

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

二、總結背景:

一次讀寫共享文件編寫,嚯,好傢夥,竟然揪出這些零碎而又是一路的知識點。於是乎,Google和翻閱了《Java參考大全》、《Effective Java Second Edition》,特此總結一下供日後工作學習參考。

三、概念:

1、 什麼時候必須同步?什麼叫同步?如何同步?

要跨線程維護正確的可見性,只要在幾個線程之間共享非 final 變數,就必須使用 synchronized(或 volatile)以確保一個線程可以看見另一個線程做的更改。

為了在線程之間進行可靠的通信,也為了互斥訪問,同步是必須的。這歸因於java語言規範的內存模型,它規定了:一個線程所做的變化何時以及如何變成對其它線程可見。

因為多線程將非同步行為引進程序,所以在需要同步時,必須有一種方法強制進行。例如:如果2個線程想要通信並且要共享一個複雜的數據結構,如鏈表,此時需要

確保它們互不衝突,也就是必須阻止B線程在A線程讀數據的過程中向鏈表裡面寫數據(A獲得了鎖,B必須等A釋放了該鎖)。

為了達到這個目的,java在一個舊的的進程同步模型——監控器(Monitor)的基礎上實現了一個巧妙的方案:監控器是一個控制機制,可以認為是一個

很小的、只能容納一個線程的盒子,一旦一個線程進入監控器,其它的線程必須等待,直到那個線程退出監控為止。通過這種方式,一個監控器可以保證共享資源在

同一時刻只可被一個線程使用。這種方式稱之為同步。(一旦一個線程進入一個實例的任何同步方法,別的線程將不能進入該同一實例的其它同步方法,但是該實例

的非同步方法仍然能夠被調用)。

錯誤的理解:同步嘛,就是幾個線程可以同時進行訪問。

同步和多線程關係:沒多線程環境就不需要同步;有多線程環境也不一定需要同步。

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

互斥即一次只允許一個線程持有某個特定的鎖,因此可使用該特性實現對共享數據的協調訪問協議,這樣,一次就只有一個線程能夠使用該共享數據。

可見性要更加複雜一些,documents它必須確保釋放鎖之前對共享數據做出的更改對於隨後獲得該鎖的另一個線程是可見的 —— 如果沒有同步機制提供的這種可見性保證,線程看到的共享變數可能是修改前的值或不一致的值,這將引發許多嚴重問題

小結:為了防止多個線程並發對同一數據的修改,所以需要同步,否則會造成數據不一致(就是所謂的:線程安全。如java集合框架中Hashtable和

Vector是線程安全的。我們的大部分程序都不是線程安全的,因為沒有進行同步,而且我們沒有必要,因為大部分情況根本沒有多線程環境)。

2、 什麼叫原子的(原子操作)?

Java原子操作是指:不會被打斷地的操作。(就是做到互斥 和可見性?!)

那難道原子操作就可以真的達到線程安全同步效果了嗎?實際上有一些原子操作不一定是線程安全的。

那麼,原子操作在什麼情況下不是線程安全的呢?也許是這個原因導致的:java線程允許線程在自己的內存區保存變數的副本。允許線程使用本地的私有拷貝進

行工作而非每次都使用主存的值是為了提高性能(本人愚見:雖然原子操作是線程安全的,可各線程在得到變數(讀操作)後,就是各自玩

弄自己的副本了,更新操作(寫操作)因未寫入主存中,導致其它線程不可見)。

那該如何解決呢?因此需要通過java同步機制。

在java中,32位或者更少位數的賦值是原子的。在一個32位的硬體平台上,除了double和long型的其它原始類型通常都

是使用32位進行表示,而double和long通常使用64位表示。另外,對象引用使用本機指針實現,通常也是32位的。對這些32位的類型的操作是原

子的。

這些原始類型通常使用32位或者64位表示,這又引入了另一個小小的神話:原始類型的大小是由語言保證的。這是不對的。java語言保證的是原始類型的表

數範圍而非JVM中的存儲大小。因此,int型總是有相同的表數範圍。在一個JVM上可能使用32位實現,而在另一個JVM上可能是64位的。在此再次強

調:在所有平台上被保證的是表數範圍,32位以及更小的值的操作是原子的。

3、 不要搞混了:同步、非同步

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

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

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

可見,彼「同步」非此「同步」——我們說的java中的那個共享數據同步(synchronized)

一個同步的對象是指行為(動作),一個是同步的對象是指物質(共享數據)。

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

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

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

ThreadLocal

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,在每次訪問時都會和主存一致;如果變數在同步方法或者同步塊中被訪問,當在方法或者塊的入口處獲得鎖以及方法或者塊退出時釋放鎖時變數被同步。)

java怎麼同步發送及非同步發送簡訊例子解析

發送簡訊的介面

根據自己的情況選擇服務商。

開發文檔

從開發文檔中我們可以看到. 可以直接使用http請求也可以使用WebService請求發送簡訊. 由於DEMO文件夾下的java和jsp文件夾中的代碼都是使用http請求發送簡訊. 所以這裡就不再細說了, 我們使用WebService的方式演示發送簡訊.

生成客戶端代碼

從介面文檔中我們知道它的WebService的WSDL的url為: 那麼我們可以執行下面的命令生成客戶端代碼:

wsimport -keep

其中wsimport是JDK自帶的工具, -keep url選項是”保留生成的文件”. 該命令會在當前目錄下生成sms.cn.ihuyi._106包, 以及眾多的類. 接下來開始編寫我們自己的代碼.

定義介面

為了方便, 這裡我們首先定義一個介面:

Sms.java

public interface Sms {

 /**

  * 向mobile發送簡訊, 內容為message

  * 

  * @param mobile 手機號

  * @param message 簡訊內容

  * @return 成功返回-1, 否則返回其他值

  */

 int sendMessage(String mobile, String message);

}

這個介面很簡單, 只有一個方法. 這個方法用來發送簡訊.

同步發送簡訊

接下來我們首先實現一個同步發送簡訊的類:

IhuyiSmsImpl.java

public class IhuyiSmsImpl implements Sms {

 private String account;

 private String password;

 public void setAccount(String account) {

  this.account = account;

 }

 public void setPassword(String password) {

  this.password = password;

 }

 @Override

 public int sendMessage(String mobile, String message) {

  cn.ihuyi._106.Sms factory = new cn.ihuyi._106.Sms();

  SmsSoap smsSoap = factory.getSmsSoap();

  SubmitResult submit = smsSoap.submit(account, password, mobile, message);

  int code = submit.getCode();

  if(code == 2){

   return -1;

  }

  System.out.println(“發送簡訊失敗, code:” + code);

  return code;

 }

}

非同步發送簡訊

由於發送簡訊涉及到網路通信, 因此sendMessage方法可能會有一些延遲. 為了改善用戶體驗, 我們可以使用非同步發送簡訊的方法. 原理很簡單: 如果用戶請求發送簡訊, 我們不是直接調用IhuyiSmsImpl的sendMessage方法, 而是將請求保存起來(生產者), 然後告訴用戶: 簡訊發送成功. 之後有若干個消費者取出任務, 調用sendMessage方法發送簡訊.

這裡, 我使用線程池完成上面的任務:

AsyncSmsImpl.java

public class AsyncSmsImpl implements Sms {

 public Sms sendSms;

 private ExecutorService executorService = Executors.newFixedThreadPool(3);

 public void setSendSms(Sms sendSms) {

  this.sendSms = sendSms;

 }

 @Override

 public int sendMessage(String mobile, String message) {

  try {

   executorService.submit(() – sendSms.sendMessage(mobile, message));

  }

  catch(Exception e) {

   Sysemt.out.println(“提交任務時發生錯誤” + e);

   return 0;

  }

  return -1;

 }

 public void destroy(){

  try{

   executorService.shutdown();

  }

  catch(Exception e){}

 }

}

在第17行, 我們獲得遠程對象的一個代理對象. 之後就可以通過這個代理對象進行發送簡訊, 查詢賬戶餘額等操作.

第18行, 使用該代理對象的submit方法提交了簡訊內容. 該方法的參數信息及返回值含義在介面文檔中有詳細的說明.

第19行我們獲得了結果的狀態碼. 根據文檔上的說明, 狀態碼為2說明提交成功. 簡單起見, 這裡我們只關注提交成功的情況. 需要注意的是, 狀態碼為2隻是說明提交成功. 根據官網上的”3-5秒內響應、100%到達”, 我們可以推測. 如果提交成功, 那麼基本上3-5秒內,簡訊就會發送成功, 根據用戶的網路情況, 可能稍有延遲用戶就可以收到簡訊.

使用這段代碼發送簡訊也很簡單, 直接new一個對象, 設置好賬號和密碼就可以發送簡訊了.

代碼很簡單, 直接將Sms介面的sendMessage(mobile, message)方法作為一個任務加到線程池的任務隊列中. 這樣等到有空閑線程時, 就會執行sendSms.sendMessage(mobile, message)發送簡訊. 這裡我們假設只要保存到線程池就可以成功發送簡訊. 因為發送失敗的情況實際上很罕見.

java常見的幾種調用機制(同步調用,非同步調用

1、同步調用

同步調用是最基本的調用方式,對象b中的方法直接調用對象a的方法,這個時候程序會等待對象a的方法執行完返回結果之後才會繼續往下走。

代碼如下:

public class A {

public void methodA()

{

System.out.println(“this is class A method”);

}

}

public class B {

public void methodB()

{

A a = new A();

a.methodA();

System.out.println(“this is class B method”);

}

}

public class Test {

public static void main(String[] args) {

B b = new B();

b.methodB();

}

}

結果:

this is class A method

this is class B method

2、非同步調用

對象b中的方法調用對象a的方法,程序並不需要等待對象a的方法返回結果值,直接繼續往下走。

代碼如下:

public class A extends Thread{

@Override

public void run() {

try {

Thread.sleep(3000);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(“this is class A method”);

}

}

public class B {

public void methodB()

{

A a = new A();

a.start();

System.out.println(“this is class B method”);

}

}

public class Test {

public static void main(String[] args) {

B b = new B();

b.methodB();

}

}

結果:

this is class B method

this is class A method

說明:非同步調用我們通常採用多線程的方法來達到目的

3、回調

對象a的方法methodA()中調用對象b的methodB()方法,在對象b的methodB()方法中反過來調用對象a的callBack()方法,這個callBack()方法稱為回調函數,這種調用方法稱為回調。

代碼如下:

public class A {

public void methodA()

{

B b = new B();

b.methodB(new A());

System.out.println(“this is class A method : methodA”);

}

public void callBack()

{

System.out.println(“this is class A method : callBack”);

}

}

public class B {

public void methodB(A a)

{

System.out.println(“this is class B method : methodB”);

a.callBack();

}

}

public class Test {

public static void main(String[] args) {

A a = new A();

a.methodA();

}

}

運行結果:

this is class B method : methodB

this is class A method : callBack

this is class A method : methodA

注意:這裡如果為了代碼的擴展性更好,可以把類A與類B抽象出一個介面出來,然後用實現類去實現著兩個介面,這樣代碼的擴展性會更好,也能滿足更多的業務場景。

回調的核心在於:回調方將本身對象傳給調用方,調用方在本身代碼邏輯執行完之後,調用回調方的回調方法。

在java中如何實現同步和非同步?

同步和非同步一般是指多線程中對資源的訪問的。最簡單的例子是在多線程中對一個靜態整數進行遞增操作,然後在線程run方法上加synchronizied關鍵字試試。

非同步類 同步類 java

類裡面的方法會自動同步,

比如你new 一個vector

Vector ve=new Vector();

ve.add(“1”);

ve.add(“2”);

如果有兩個線程同時調用這個ve對象,那麼第一個線程調用修改時候就會把ve加鎖,那麼第二個線程沒法操作這個ve對象。

如果用ArrayList list=new ArrayList();

list.add(“1”);

就不同步,如果一個線程調用時候用list.remove(“1”);把1移走的話,那麼,第二個線程訪問這個list的對象「1」就會出錯。就是這樣

java的同步非同步機制

基本概念:

每個Object都會有1個鎖.

同步就是串列使用一些資源.

(說明:以下有些例子為了突出重點,省略了不必要的代碼.非凡是省掉了一些成員變數,就是需要同步的對象.)

1. 多線程中對共享、可變的數據進行同步.

對於函數中的局部變數沒必要進行同步.

對於不可變數據,也沒必要進行同步.

多線程中訪問共享可變數據才有必要.

2. 單個線程中可以使用synchronized,而且可以嵌套,但無意義.

class Test {

public static void main(String[] args) {

Test t = new Test();

synchronized(t) {

synchronized(t) {

System.out.println(“ok!”);

}

}

}

}

3. 對象實例的鎖

class Test{

public synchronized void f1(){

//do something here

}

public void f2(){

synchronized(this){

//do something here

}

}

}

上面的f1()和f2()效果一致, synchronized取得的鎖都是Test某個實列(this)的鎖.

比如: Test t = new Test();

線程A調用t.f2()時, 線程B無法進入t.f1(),直到t.f2()結束.

作用: 多線程中訪問Test的同一個實例的同步方法時會進行同步.

原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/272212.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
小藍的頭像小藍
上一篇 2024-12-17 13:55
下一篇 2024-12-17 13:55

相關推薦

  • Java JsonPath 效率優化指南

    本篇文章將深入探討Java JsonPath的效率問題,並提供一些優化方案。 一、JsonPath 簡介 JsonPath是一個可用於從JSON數據中獲取信息的庫。它提供了一種DS…

    編程 2025-04-29
  • java client.getacsresponse 編譯報錯解決方法

    java client.getacsresponse 編譯報錯是Java編程過程中常見的錯誤,常見的原因是代碼的語法錯誤、類庫依賴問題和編譯環境的配置問題。下面將從多個方面進行分析…

    編程 2025-04-29
  • Java騰訊雲音視頻對接

    本文旨在從多個方面詳細闡述Java騰訊雲音視頻對接,提供完整的代碼示例。 一、騰訊雲音視頻介紹 騰訊雲音視頻服務(Cloud Tencent Real-Time Communica…

    編程 2025-04-29
  • Java Bean載入過程

    Java Bean載入過程涉及到類載入器、反射機制和Java虛擬機的執行過程。在本文中,將從這三個方面詳細闡述Java Bean載入的過程。 一、類載入器 類載入器是Java虛擬機…

    編程 2025-04-29
  • Java Milvus SearchParam withoutFields用法介紹

    本文將詳細介紹Java Milvus SearchParam withoutFields的相關知識和用法。 一、什麼是Java Milvus SearchParam without…

    編程 2025-04-29
  • 解決.net 6.0運行閃退的方法

    如果你正在使用.net 6.0開發應用程序,可能會遇到程序閃退的情況。這篇文章將從多個方面為你解決這個問題。 一、代碼問題 代碼問題是導致.net 6.0程序閃退的主要原因之一。首…

    編程 2025-04-29
  • ArcGIS更改標註位置為中心的方法

    本篇文章將從多個方面詳細闡述如何在ArcGIS中更改標註位置為中心。讓我們一步步來看。 一、禁止標註智能調整 在ArcMap中設置標註智能調整可以自動將標註位置調整到最佳顯示位置。…

    編程 2025-04-29
  • Python中init方法的作用及使用方法

    Python中的init方法是一個類的構造函數,在創建對象時被調用。在本篇文章中,我們將從多個方面詳細討論init方法的作用,使用方法以及注意點。 一、定義init方法 在Pyth…

    編程 2025-04-29
  • Python創建分配內存的方法

    在python中,我們常常需要創建並分配內存來存儲數據。不同的類型和數據結構可能需要不同的方法來分配內存。本文將從多個方面介紹Python創建分配內存的方法,包括列表、元組、字典、…

    編程 2025-04-29
  • Python中讀入csv文件數據的方法用法介紹

    csv是一種常見的數據格式,通常用於存儲小型數據集。Python作為一種廣泛流行的編程語言,內置了許多操作csv文件的庫。本文將從多個方面詳細介紹Python讀入csv文件的方法。…

    編程 2025-04-29

發表回復

登錄後才能評論