欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入解析Java的線程同步以及線程間通信

 更新時(shí)間:2015年09月29日 16:16:11   投稿:goldensun  
這篇文章主要介紹了Java的線程同步以及線程間通信,多線程編程是Java學(xué)習(xí)中的重點(diǎn)和難點(diǎn),需要的朋友可以參考下

Java線程同步
當(dāng)兩個(gè)或兩個(gè)以上的線程需要共享資源,它們需要某種方法來確定資源在某一刻僅被一個(gè)線程占用。達(dá)到此目的的過程叫做同步(synchronization)。像你所看到的,Java為此提供了獨(dú)特的,語言水平上的支持。

同步的關(guān)鍵是管程(也叫信號量semaphore)的概念。管程是一個(gè)互斥獨(dú)占鎖定的對象,或稱互斥體(mutex)。在給定的時(shí)間,僅有一個(gè)線程可以獲得管程。當(dāng)一個(gè)線程需要鎖定,它必須進(jìn)入管程。所有其他的試圖進(jìn)入已經(jīng)鎖定的管程的線程必須掛起直到第一個(gè)線程退出管程。這些其他的線程被稱為等待管程。一個(gè)擁有管程的線程如果愿意的話可以再次進(jìn)入相同的管程。

如果你用其他語言例如C或C++時(shí)用到過同步,你會知道它用起來有一點(diǎn)詭異。這是因?yàn)楹芏嗾Z言它們自己不支持同步。相反,對同步線程,程序必須利用操作系統(tǒng)源語。幸運(yùn)的是Java通過語言元素實(shí)現(xiàn)同步,大多數(shù)的與同步相關(guān)的復(fù)雜性都被消除。

你可以用兩種方法同步化代碼。兩者都包括synchronized關(guān)鍵字的運(yùn)用,下面分別說明這兩種方法。
使用同步方法

Java中同步是簡單的,因?yàn)樗袑ο蠖加兴鼈兣c之對應(yīng)的隱式管程。進(jìn)入某一對象的管程,就是調(diào)用被synchronized關(guān)鍵字修飾的方法。當(dāng)一個(gè)線程在一個(gè)同步方法內(nèi)部,所有試圖調(diào)用該方法(或其他同步方法)的同實(shí)例的其他線程必須等待。為了退出管程,并放棄對對象的控制權(quán)給其他等待的線程,擁有管程的線程僅需從同步方法中返回。

為理解同步的必要性,讓我們從一個(gè)應(yīng)該使用同步卻沒有用的簡單例子開始。下面的程序有三個(gè)簡單類。首先是Callme,它有一個(gè)簡單的方法call( )。call( )方法有一個(gè)名為msg的String參數(shù)。該方法試圖在方括號內(nèi)打印msg 字符串。有趣的事是在調(diào)用call( ) 打印左括號和msg字符串后,調(diào)用Thread.sleep(1000),該方法使當(dāng)前線程暫停1秒。

下一個(gè)類的構(gòu)造函數(shù)Caller,引用了Callme的一個(gè)實(shí)例以及一個(gè)String,它們被分別存在target 和 msg 中。構(gòu)造函數(shù)也創(chuàng)建了一個(gè)調(diào)用該對象的run( )方法的新線程。該線程立即啟動。Caller類的run( )方法通過參數(shù)msg字符串調(diào)用Callme實(shí)例target的call( ) 方法。最后,Synch類由創(chuàng)建Callme的一個(gè)簡單實(shí)例和Caller的三個(gè)具有不同消息字符串的實(shí)例開始。

Callme的同一實(shí)例傳給每個(gè)Caller實(shí)例。

// This program is not synchronized.
class Callme {
  void call(String msg) {
    System.out.print("[" + msg);
    try {
      Thread.sleep(1000);
    } catch(InterruptedException e) {
      System.out.println("Interrupted");
    }
    System.out.println("]");
  }
}

class Caller implements Runnable {
  String msg;
  Callme target;
  Thread t;
  public Caller(Callme targ, String s) {
    target = targ;
    msg = s;
    t = new Thread(this);
    t.start();
  }
  public void run() {
    target.call(msg);
  }
}

class Synch {
  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");
    // wait for threads to end
    try {
     ob1.t.join();
     ob2.t.join();
     ob3.t.join();
    } catch(InterruptedException e) {
     System.out.println("Interrupted");
    }
  }
}

該程序的輸出如下:

Hello[Synchronized[World]
]
]

在本例中,通過調(diào)用sleep( ),call( )方法允許執(zhí)行轉(zhuǎn)換到另一個(gè)線程。該結(jié)果是三個(gè)消息字符串的混合輸出。該程序中,沒有阻止三個(gè)線程同時(shí)調(diào)用同一對象的同一方法的方法存在。這是一種競爭,因?yàn)槿齻€(gè)線程爭著完成方法。例題用sleep( )使該影響重復(fù)和明顯。在大多數(shù)情況,競爭是更為復(fù)雜和不可預(yù)知的,因?yàn)槟悴荒艽_定何時(shí)上下文轉(zhuǎn)換會發(fā)生。這使程序時(shí)而運(yùn)行正常時(shí)而出錯(cuò)。

為達(dá)到上例所想達(dá)到的目的,必須有權(quán)連續(xù)的使用call( )。也就是說,在某一時(shí)刻,必須限制只有一個(gè)線程可以支配它。為此,你只需在call( ) 定義前加上關(guān)鍵字synchronized,如下:

class Callme {
  synchronized void call(String msg) {
    ...

這防止了在一個(gè)線程使用call( )時(shí)其他線程進(jìn)入call( )。在synchronized加到call( )前面以后,程序輸出如下:

[Hello]
[Synchronized]
[World]

任何時(shí)候在多線程情況下,你有一個(gè)方法或多個(gè)方法操縱對象的內(nèi)部狀態(tài),都必須用synchronized 關(guān)鍵字來防止?fàn)顟B(tài)出現(xiàn)競爭。記住,一旦線程進(jìn)入實(shí)例的同步方法,沒有其他線程可以進(jìn)入相同實(shí)例的同步方法。然而,該實(shí)例的其他不同步方法卻仍然可以被調(diào)用。
同步語句

盡管在創(chuàng)建的類的內(nèi)部創(chuàng)建同步方法是獲得同步的簡單和有效的方法,但它并非在任何時(shí)候都有效。這其中的原因,請跟著思考。假設(shè)你想獲得不為多線程訪問設(shè)計(jì)的類對象的同步訪問,也就是,該類沒有用到synchronized方法。而且,該類不是你自己,而是第三方創(chuàng)建的,你不能獲得它的源代碼。這樣,你不能在相關(guān)方法前加synchronized修飾符。怎樣才能使該類的一個(gè)對象同步化呢?很幸運(yùn),解決方法很簡單:你只需將對這個(gè)類定義的方法的調(diào)用放入一個(gè)synchronized塊內(nèi)就可以了。

下面是synchronized語句的普通形式:

synchronized(object) {
  // statements to be synchronized
}

其中,object是被同步對象的引用。如果你想要同步的只是一個(gè)語句,那么不需要花括號。一個(gè)同步塊確保對object成員方法的調(diào)用僅在當(dāng)前線程成功進(jìn)入object管程后發(fā)生。

下面是前面程序的修改版本,在run( )方法內(nèi)用了同步塊:

// This program uses a synchronized block.
class Callme {
  void call(String msg) {
    System.out.print("[" + msg);
    try {
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      System.out.println("Interrupted");
    }
    System.out.println("]");
  }
}

class Caller implements Runnable {
  String msg;
  Callme target;
  Thread t;
  public Caller(Callme targ, String s) {
    target = targ;
    msg = s;
    t = new Thread(this);
    t.start();
  }

  // synchronize calls to call()
  public void run() {
    synchronized(target) { // synchronized block
      target.call(msg);
    }
  }
}

class Synch1 {
  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");

    // wait for threads to end
    try {
      ob1.t.join();
      ob2.t.join();
      ob3.t.join();
    } catch(InterruptedException e) {
      System.out.println("Interrupted");
    }
  }
}

這里,call( )方法沒有被synchronized修飾。而synchronized是在Caller類的run( )方法中聲明的。這可以得到上例中同樣正確的結(jié)果,因?yàn)槊總€(gè)線程運(yùn)行前都等待先前的一個(gè)線程結(jié)束。

Java線程間通信
多線程通過把任務(wù)分成離散的和合乎邏輯的單元代替了事件循環(huán)程序。線程還有第二優(yōu)點(diǎn):它遠(yuǎn)離了輪詢。輪詢通常由重復(fù)監(jiān)測條件的循環(huán)實(shí)現(xiàn)。一旦條件成立,就要采取適當(dāng)?shù)男袆?。這浪費(fèi)了CPU時(shí)間。舉例來說,考慮經(jīng)典的序列問題,當(dāng)一個(gè)線程正在產(chǎn)生數(shù)據(jù)而另一個(gè)程序正在消費(fèi)它。為使問題變得更有趣,假設(shè)數(shù)據(jù)產(chǎn)生器必須等待消費(fèi)者完成工作才能產(chǎn)生新的數(shù)據(jù)。在輪詢系統(tǒng),消費(fèi)者在等待生產(chǎn)者產(chǎn)生數(shù)據(jù)時(shí)會浪費(fèi)很多CPU周期。一旦生產(chǎn)者完成工作,它將啟動輪詢,浪費(fèi)更多的CPU時(shí)間等待消費(fèi)者的工作結(jié)束,如此下去。很明顯,這種情形不受歡迎。

為避免輪詢,Java包含了通過wait( ),notify( )和notifyAll( )方法實(shí)現(xiàn)的一個(gè)進(jìn)程間通信機(jī)制。這些方法在對象中是用final方法實(shí)現(xiàn)的,所以所有的類都含有它們。這三個(gè)方法僅在synchronized方法中才能被調(diào)用。盡管這些方法從計(jì)算機(jī)科學(xué)遠(yuǎn)景方向上來說具有概念的高度先進(jìn)性,實(shí)際中用起來是很簡單的:
wait( ) 告知被調(diào)用的線程放棄管程進(jìn)入睡眠直到其他線程進(jìn)入相同管程并且調(diào)用notify( )。
notify( ) 恢復(fù)相同對象中第一個(gè)調(diào)用 wait( ) 的線程。
notifyAll( ) 恢復(fù)相同對象中所有調(diào)用 wait( ) 的線程。具有最高優(yōu)先級的線程最先運(yùn)行。

這些方法在Object中被聲明,如下所示:

  final void wait( ) throws InterruptedException
  final void notify( )
  final void notifyAll( )


wait( )存在的另外的形式允許你定義等待時(shí)間。

下面的例子程序錯(cuò)誤的實(shí)行了一個(gè)簡單生產(chǎn)者/消費(fèi)者的問題。它由四個(gè)類組成:Q,設(shè)法獲得同步的序列;Producer,產(chǎn)生排隊(duì)的線程對象;Consumer,消費(fèi)序列的線程對象;以及PC,創(chuàng)建單個(gè)Q,Producer,和Consumer的小類。

// An incorrect implementation of a producer and consumer.
class Q {
  int n;
  synchronized int get() {
    System.out.println("Got: " + n);
    return n;
  }
  synchronized void put(int n) {
    this.n = n;
    System.out.println("Put: " + n);
  }
}
class Producer implements Runnable {
  Q q;
  Producer(Q q) {
    this.q = q;
    new Thread(this, "Producer").start();
  }
  public void run() {
    int i = 0;
    while(true) {
      q.put(i++);
    }
  }
}
class Consumer implements Runnable {
  Q q;
  Consumer(Q q) {
    this.q = q;
    new Thread(this, "Consumer").start();
  }
  public void run() {
    while(true) {
      q.get();
    }
  }
}
class PC {
  public static void main(String args[]) {
    Q q = new Q();
    new Producer(q);
    new Consumer(q);
    System.out.println("Press Control-C to stop.");
  }
}

盡管Q類中的put( )和get( )方法是同步的,沒有東西阻止生產(chǎn)者超越消費(fèi)者,也沒有東西阻止消費(fèi)者消費(fèi)同樣的序列兩次。這樣,你就得到下面的錯(cuò)誤輸出(輸出將隨處理器速度和裝載的任務(wù)而改變):

Put: 1
Got: 1
Got: 1
Got: 1
Got: 1
Got: 1
Put: 2
Put: 3
Put: 4
Put: 5
Put: 6
Put: 7
Got: 7

生產(chǎn)者生成1后,消費(fèi)者依次獲得同樣的1五次。生產(chǎn)者在繼續(xù)生成2到7,消費(fèi)者沒有機(jī)會獲得它們。

用Java正確的編寫該程序是用wait( )和notify( )來對兩個(gè)方向進(jìn)行標(biāo)志,如下所示:

// A correct implementation of a producer and consumer.
class Q {
  int n;
  boolean valueSet = false;
  synchronized int get() {
    if(!valueSet)
      try {
        wait();
      } catch(InterruptedException e) {
        System.out.println("InterruptedException caught");
      }
      System.out.println("Got: " + n);
      valueSet = false;
      notify();
      return n;
    }
    synchronized void put(int n) {
      if(valueSet)
      try {
        wait();
      } catch(InterruptedException e) {
        System.out.println("InterruptedException caught");
      }
      this.n = n;
      valueSet = true;
      System.out.println("Put: " + n);
      notify();
    }
  }
  class Producer implements Runnable {
    Q q;
    Producer(Q q) {
    this.q = q;
    new Thread(this, "Producer").start();
  }
  public void run() {
    int i = 0;
    while(true) {
      q.put(i++);
    }
  }
}
class Consumer implements Runnable {
  Q q;
  Consumer(Q q) {
    this.q = q;
    new Thread(this, "Consumer").start();
  }
  public void run() {
    while(true) {
      q.get();
    }
  }
}
class PCFixed {
  public static void main(String args[]) {
    Q q = new Q();
    new Producer(q);
    new Consumer(q);
    System.out.println("Press Control-C to stop.");
  }
}

內(nèi)部get( ), wait( )被調(diào)用。這使執(zhí)行掛起直到Producer 告知數(shù)據(jù)已經(jīng)預(yù)備好。這時(shí),內(nèi)部get( ) 被恢復(fù)執(zhí)行。獲取數(shù)據(jù)后,get( )調(diào)用notify( )。這告訴Producer可以向序列中輸入更多數(shù)據(jù)。在put( )內(nèi),wait( )掛起執(zhí)行直到Consumer取走了序列中的項(xiàng)目。當(dāng)執(zhí)行再繼續(xù),下一個(gè)數(shù)據(jù)項(xiàng)目被放入序列,notify( )被調(diào)用,這通知Consumer它應(yīng)該移走該數(shù)據(jù)。

下面是該程序的輸出,它清楚的顯示了同步行為:

Put: 1
Got: 1
Put: 2
Got: 2
Put: 3
Got: 3
Put: 4
Got: 4
Put: 5
Got: 5

相關(guān)文章

  • Java ArrayList.add 的實(shí)現(xiàn)方法

    Java ArrayList.add 的實(shí)現(xiàn)方法

    這篇文章主要介紹了Java ArrayList.add 的實(shí)現(xiàn)方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-11-11
  • 帶著新人看java虛擬機(jī)01(推薦)

    帶著新人看java虛擬機(jī)01(推薦)

    這篇文章主要介紹了java虛擬機(jī),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • mybatis注解之@Mapper和@MapperScan的使用

    mybatis注解之@Mapper和@MapperScan的使用

    這篇文章主要介紹了mybatis注解之@Mapper和@MapperScan的使用,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • elasticsearch+logstash并使用java代碼實(shí)現(xiàn)日志檢索

    elasticsearch+logstash并使用java代碼實(shí)現(xiàn)日志檢索

    這篇文章主要介紹了elasticsearch+logstash并使用java代碼實(shí)現(xiàn)日志檢索,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-02-02
  • Java中Scanner使用方式:單行/多行輸入

    Java中Scanner使用方式:單行/多行輸入

    這篇文章主要介紹了Java中Scanner使用方式:單行/多行輸入,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-05-05
  • SpringBoot讀寫xml上傳到AWS存儲服務(wù)S3的示例

    SpringBoot讀寫xml上傳到AWS存儲服務(wù)S3的示例

    這篇文章主要介紹了SpringBoot讀寫xml上傳到S3的示例,幫助大家更好的理解和使用springboot框架,感興趣的朋友可以了解下
    2020-10-10
  • Mybatis Mapper接口和xml綁定的多種方式、內(nèi)部實(shí)現(xiàn)原理和過程解析

    Mybatis Mapper接口和xml綁定的多種方式、內(nèi)部實(shí)現(xiàn)原理和過程解析

    在Mybatis中,我們需要?jiǎng)?chuàng)建一個(gè)與實(shí)體類對應(yīng)的Mapper接口,然后在該接口上添加方法,這些方法對應(yīng)著SQL語句,這篇文章主要介紹了Mybatis Mapper接口和xml綁定的多種方式、內(nèi)部實(shí)現(xiàn)原理和過程,需要的朋友可以參考下
    2023-11-11
  • Java中SSM框架實(shí)現(xiàn)增刪改查功能代碼詳解

    Java中SSM框架實(shí)現(xiàn)增刪改查功能代碼詳解

    這篇文章主要介紹了Java中SSM框架實(shí)現(xiàn)增刪改查功能代碼詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • spring boot validation參數(shù)校驗(yàn)實(shí)例分析

    spring boot validation參數(shù)校驗(yàn)實(shí)例分析

    這篇文章主要介紹了spring boot validation參數(shù)校驗(yàn),結(jié)合實(shí)例形式分析了spring boot validation進(jìn)行數(shù)據(jù)有效性驗(yàn)證的相關(guān)操作技巧,需要的朋友可以參考下
    2019-11-11
  • springboot?通過博途獲取plc點(diǎn)位的數(shù)據(jù)代碼實(shí)現(xiàn)

    springboot?通過博途獲取plc點(diǎn)位的數(shù)據(jù)代碼實(shí)現(xiàn)

    這篇文章主要介紹了springboot?通過博途獲取plc點(diǎn)位的數(shù)據(jù)的代碼實(shí)現(xiàn),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-08-08

最新評論