最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

java并發(fā)編程專題(三)----詳解線程的同步

 更新時間:2020年06月30日 15:21:03   作者:rickiyang  
這篇文章主要介紹了JAVA并發(fā)編程 線程同步的的相關(guān)資料,文中示例代碼非常詳細,幫助大家更好的理解和學習,感興趣的朋友可以了解下

有興趣的朋友可以回顧一下前兩篇

java并發(fā)編程專題(一)----線程基礎(chǔ)知識

java并發(fā)編程專題(二)----如何創(chuàng)建并運行java線程

在現(xiàn)實開發(fā)中,我們或多或少的都經(jīng)歷過這樣的情景:某一個變量被多個用戶并發(fā)式的訪問并修改,如何保證該變量在并發(fā)過程中對每一個用戶的正確性呢?今天我們來聊聊線程同步的概念。

一般來說,程序并行化是為了獲得更高的執(zhí)行效率,但前提是,高效率不能以犧牲正確性為代價。如果程序并行化后, 連基本的執(zhí)行結(jié)果的正確性都無法保證, 那么并行程序本身也就沒有任何意義了。因此, 線程安全就是并行程序的根本和根基。解決這些問題從臨界區(qū)的概念開始。臨界區(qū)是訪問一個共享資源在同一時間不能被超過一個線程執(zhí)行的代碼塊。

java為我們提供了同步機制,幫助程序員實現(xiàn)臨界區(qū)。當一個線程想要訪問一個臨界區(qū),它使用其中的一個同步機制來找出是否有任何其他線程執(zhí)行臨界區(qū)。如果沒有,這個線程就進入臨界區(qū)。否則,這個線程通過同步機制暫停直到另一個線程執(zhí)行完臨界區(qū)。當多個線程正在等待一個線程完成執(zhí)行的一個臨界 區(qū),JVM選擇其中一個線程執(zhí)行,其余的線程會等待直到輪到它們。臨界區(qū)有如下的規(guī)則:

  1. 如果有若干進程要求進入空閑的臨界區(qū),一次僅允許一個進程進入。
  2. 任何時候,處于臨界區(qū)內(nèi)的進程不可多于一個。如已有進程進入自己的臨界區(qū),則其它所有試圖進入臨界區(qū)的進程必須等待。
  3. 進入臨界區(qū)的進程要在有限時間內(nèi)退出,以便其它進程能及時進入自己的臨界區(qū)。
  4. 如果進程不能進入自己的臨界區(qū),則應(yīng)讓出CPU,避免進程出現(xiàn)“忙等”現(xiàn)象。

java語言為解決同步問題幫我們提供了兩種機制來實現(xiàn):

1. synchronized關(guān)鍵字;
2.  Lock鎖及其實現(xiàn);

synchronized的作用

關(guān)鍵字synchronized 的作用是實現(xiàn)線程間的同步。它的工作是對同步的代碼加鎖,使得每一次, 只能有一個線程進入同步塊,從而保證線程間的安全性。

關(guān)鍵宇synchronized 可以有多種用法。這里做一個簡單的整理。

· 指定加鎖對象: 對給定對象加鎖,進入同步代碼前要獲得給定對象的鎖。
· 直接作用于實例方法: 相當于對當前實例加鎖,進入同步代碼前要獲得當前實例的鎖。
. 直接作用于靜態(tài)方法: 相當于對當前類加鎖, 進入同步代碼前要獲得當前類的鎖。

1.給指定對象加鎖:

  public class AccountingSync implements Runnable{
    static AccountingSync instance=new AccountingSync() ;
    static int i =O;
    @Override
    public void run() (
    for(int j=O; j<lOOOOOOO; j++) {
      synchronized (instance) {  //對象鎖
        i++ ;
      }
    }
  }

  public static void main(String[] args) throws InterruptedException (
    Thread t1=new Thread(instance);
    Thread t2=new Thread(instance);
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    System.out.println(i);
  }

  /*

  public static void main(String[] args) throws InterruptedException (
    Thread t1=new Thread(new AccountingSync());
    Thread t2=new Thread(new AccountingSync());
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    System.out.println(i);
  }

  */

知道我為什么要給出兩個main方法讓大家參考嗎?上述鎖對象是鎖定AccountingSync實例對象。第一個main方法中t1 和 t2 兩個線程同時指向了instance實例,所以第7行的鎖對象synchronized (instance)在線程t1 和 線程 t2 獲得鎖的時候是獲取同一個對象的,這個時候的鎖是同一把鎖。但是在第二個main方法中我們可以看到線程t1 和 線程 t2分別對應(yīng)的是兩個不同的AccountingSync對象,這時候鎖對象獲得的是不同的AccountingSync實例,安全性是沒有保證的,大家可以動手嘗試一下。

2.直接作用于實例方法:

  public class TestSynchronized {
    public static void main(String[] args) {
      Tester2 a1 = new Tester2();
      Th t1 = new Th(a1);
      t1.start();
      Th t2 = new Th(a1);
      t2.start();
    }

  }
  class Tester2 {
    public synchronized void say(String name) throws InterruptedException{
      for(int i = 0;i<5;i++){
        Thread.sleep(1000);
        System.out.println();
        System.out.println(name +","+i+new Date().toLocaleString() );
      }
    }
  }
  class Th extends Thread{
    Tester2 test;
    public Th(Tester2 test1){
      test = test1;
    } 
    public void run(){
      try {
        test.say(Thread.currentThread().getName());
      } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    }
  }

對Tester2類中的方法使用synchronized很好理解,同一時刻如果t1正在調(diào)用say()方法,在他沒有執(zhí)行完畢并退出方法之前其余的線程是無法獲得該方法的。只能排隊等待知道t1執(zhí)行完畢。

3.作用于靜態(tài)方法:

 public class Test1 {
    public static void main(String[] args) {
      for(int i=0;i<50;i++){
        Thread t1 = new Thread(new Sale(5));
        Thread t2 = new Thread(new Producted(5));
        t1.start();
        t2.start();
      }
    }
  }

  class Shop{
    static int a = 40;
    synchronized static void shopping(int b){
      a -= b;
      System.out.println("售出 "+b+" 張大餅,"+"還剩 "+a+" 張大餅");
    }

    synchronized static void factory(int c){
      a += c;
      System.out.println("倉庫還有 "+a+" 張大餅");
    }
  }

  class Sale implements Runnable{
    int b = 0;
    public Sale(int b){
      this.b = b;
    }

    @Override
    public void run() {
      if(b<0){
        Thread.interrupted();
      }
      Shop.shopping(b);
      try {
        Thread.sleep(1000);
        Shop.factory(b-5);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }

  class Producted implements Runnable{
    int b = 0;
    public Producted(int b){
      this.b = b;
    }

    @Override
    public void run() {
      Shop.factory(b);
      try {
        Thread.sleep(1000);
        Shop.shopping(b-5);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }

靜態(tài)方法前加synchronized這個鎖等價于鎖住了當前類的class對象,因為靜態(tài)方法或者是靜態(tài)關(guān)鍵字在本質(zhì)上是一個類對象,而不是成員對象,在內(nèi)存中位于方法區(qū)被所有的實例共享。即等同于synchronized(Shop.class)。我們需要注意的是鎖住了類并不代表鎖住了類所在的對象,類本身也是一種對象。它與類的實例是完全不同的兩個對象,在加鎖時不是相互依賴的,即對類加鎖并不與上面例子中的加鎖互斥,鎖住了子類或子類的對象與鎖住父類或父類的對象是不相關(guān)的。

synchronized的使用其實主要是前面兩種,對象鎖和方法鎖,靜態(tài)方法鎖我們并不常用到。其余的操作方式都是在這兩種的基礎(chǔ)上演變而來,比如大家經(jīng)常說的“塊級鎖”:

  synchronized(object){
    //代碼內(nèi)容
  }

鎖住的其實并不是代碼塊,而是object這個對象,所以如果在其他的代碼中
也發(fā)生synchronized(object)時就會發(fā)生互斥。我們?yōu)槭裁匆芯窟@些呢,因為如果我們不知道我們鎖住的是什么,就不清楚鎖住了多大范圍的內(nèi)容,自然就不知道是否鎖住了想要得到互斥的效果,同時也不知道如何去優(yōu)化鎖的使用。

因此java中的synchronized就真正能做到臨界區(qū)的效果,在臨界區(qū)內(nèi)多個線程的操作絕對是串行的,這一點java絕對可以保證。同時synchronized造成的開銷也是很大的,我們?nèi)绻麩o法掌握好他的粒度控制,就會導致頻繁的鎖征用,進入悲觀鎖狀態(tài)。

volatile—-輕量級的synchronized

既然我們說到了synchronized那就不得不提到volatile,在java中synchronized是控制并發(fā)的,我們知道在我們對一個變量執(zhí)行賦值操作的時候比如:i++,在執(zhí)行完畢之后i的結(jié)果其實是寫到緩存中的它并沒有及時的寫入到內(nèi)存,后續(xù)在某些情況下(比如cpu緩存不夠)再將cpu緩存寫入內(nèi)存,假設(shè)A線程正在執(zhí)行i++操作,而此時B線程也來執(zhí)行。B在執(zhí)行i++之前是不會自己跑到緩存中去取變量的值的,它只會去內(nèi)存中讀取i,很顯然i的值是沒有被更新的,為了防止這種情況出現(xiàn),volatile應(yīng)運而生。

Java語言規(guī)范第三版中對volatile的定義如下: java編程語言允許線程訪問共享變量,為了確保共享變量能被準確和一致的更新,線程應(yīng)該確保通過排他鎖單獨獲得這個變量。Java語言提供了volatile,在某些情況下比鎖更加方便。如果一個字段被聲明成volatile,java線程內(nèi)存模型確保所有線程看到這個變量的值是一致的。

我們來看一個例子:

  public class TestWithoutVolatile {
    private static boolean bChanged; 

    public static void main(String[] args) throws InterruptedException { 
      new Thread() { 
        @Override 
        public void run() { 
          for (;;) { 
            if (bChanged == !bChanged) { 
              System.out.println("!="); 
              System.exit(0); 
            } 
          } 
        } 
      }.start(); 
      Thread.sleep(1); 
      new Thread() { 
        @Override 
        public void run() { 
          for (;;) { 
            bChanged = !bChanged; 
          } 
        } 
      }.start(); 
     } 
  }

在上例中我們?nèi)绻啻芜\行會出現(xiàn)兩種結(jié)果,一種是正常打?。骸?=”,還有一種就是程序會陷入死循環(huán)。但是我們?nèi)绻obChanged前面加上volatile的話則每次都會打印出”!=”,請讀者朋友們下去可以嘗試。
在此處沒有加volatile之前之所以會出現(xiàn)有時可以出現(xiàn)正確結(jié)果有時則卡死的原因就在于兩個線程同時在運行的過程中雙方都在操作bChanged變量,但是該變量的值對于同時在使用它的另一個線程來說并不總是可見的,運氣好的時候線程修改完值之后就寫入主存,運氣不好的時候線程只在緩存中更新了值并未寫入主存。但是在加了volatile修飾之后效果則不同,因為volatile可以保證變量的可見性。
說到可見性,我們來看一幅圖:

每一個線程都有相應(yīng)的工作內(nèi)存,工作內(nèi)存中有一份主內(nèi)存變量的副本,線程對變量的操作都在工作內(nèi)存中進行(避免再次訪問主內(nèi)存,提高性能),不同線程不能訪問彼此的工作內(nèi)存,而通過將操作后的值刷新到主內(nèi)存來進行彼此的交互,這就會帶來一個變量值對其他線程的可見性問題。當一個任務(wù)在工作內(nèi)存中變量值進行改變,其他任務(wù)對此是不可見的,導致每一個線程都有一份不同的變量副本。而volatile恰恰可以解決這個可見性的問題,當變量被volatile修飾,如private volatile int stateFlag = 0; 它將直接通過主內(nèi)存中被讀取或者寫入,線程從主內(nèi)存中加載的值將是最新的。

但是volatile的使用有著嚴格的限制,當對變量的操作依賴于以前值(如i++),或者其值被其他字段的值約束,這個時候volatile是無法實現(xiàn)線程安全的。被volatile修飾的變量必須獨立于程序的其他狀態(tài)。因為volatile只是保證了變量的可見性,并不能保證操作的原子性,所謂原子性,即有“不可分”的意思,如對基本數(shù)據(jù)類型(java中排除long和double)的賦值操作a=6,如返回操作return a,這些操作都不會被線程調(diào)度器中斷,同一時刻只有一個線程對它進行操作。
看以下代碼:

public class Counter {
    public volatile static int count = 0;
    public static void inc() {

      //這里延遲1毫秒,使得結(jié)果明顯
      try {
        Thread.sleep(1);
      } catch (InterruptedException e) {

      }
      count++;
    }

    public static void main(String[] args) {
      //同時啟動1000個線程,去進行i++計算,看看實際結(jié)果
      for (int i = 0; i < 1000; i++) {
        new Thread(new Runnable() {
          @Override
          public void run() {
            Counter.inc();
          }
        }).start();
      }
      //這里每次運行的值都有可能不同,可能為1000
      System.out.println("運行結(jié)果:Counter.count=" + Counter.count);
    }
  }

運行上面的例子我們可以發(fā)現(xiàn)每次運行的結(jié)果都不一樣,預(yù)期結(jié)果應(yīng)該是1000,盡管counter被volatile修飾,保證了可見性,但是counter++并不是一個原子性操作,它被拆分為讀取和寫入兩部分操作,我們需要用synchronized修飾:

  publicstaticsynchronizedvoid incNum() {
    counter++;
  }

此時每次運行結(jié)果都是1000,實現(xiàn)了線程安全。synchronized是一種獨占鎖,它對一段操作或內(nèi)存進行加鎖,當線程要操作被synchronized修飾的內(nèi)存或操作時,必須首先獲得鎖才能進行后續(xù)操作;但是在同一時刻只能有一個線程獲得相同的一把鎖,所以它只允許一個線程進行操作。synchronized同樣能夠?qū)⒆兞孔钚轮邓⑿碌街鲀?nèi)存,當一個變量只被synchronized方法操作時,是沒有必要用volatile修飾的,所以我們接著把變量聲明修改為:

  private static int counter;

多次運行結(jié)果依舊是1000。

說明:

上例中如果你按照上面這樣改完之后其實結(jié)果并是不1000,我多次運行的結(jié)果都是先打印出”運行結(jié)果:Counter.count=0”,然后線程卡死。究其原因,我猜可能是第一個線程等待一秒再執(zhí)行count++,然后后面的線程在這個等待過程中等不及的原因。java線程的運行具有不確定性,不能保證線程會按部就班的順序執(zhí)行,所以會出現(xiàn)什么樣的后果很難預(yù)測。
正確結(jié)果代碼如下:

public class Counter {
    public static int count = 0;
    public synchronized static void inc() {   
      count++;
    }

    public static void main(String[] args) {
      //同時啟動1000個線程,去進行i++計算,看看實際結(jié)果
      for (int i = 0; i < 1000; i++) {
        new Thread(new Runnable() {
          @Override
          public void run() {
            Counter.inc();
          }
        }).start();
      }
      //這里每次運行的值都有可能不同,可能為1000
      System.out.println("運行結(jié)果:Counter.count=" + Counter.count);
    }
  }

綜上所述,由于volatile只能保證變量對多個線程的可見性,但不能保證原子性,它的同步機制是比較脆弱的,它在使用過程中有著諸多限制,對使用者也有更高的要求,相對而言,synchronized鎖機制是比較安全的同步機制,有時候出于提高性能的考慮,可以利用volatile對synchronized進行代替和優(yōu)化,但前提是你必須充分理解其使用場景和涵義。

下一節(jié)我們接著分析Lock鎖。

以上就是java并發(fā)編程專題(三)----詳解線程的同步的詳細內(nèi)容,更多關(guān)于JAVA 線程同步的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • java學習筆記_關(guān)于字符串概述

    java學習筆記_關(guān)于字符串概述

    下面小編就為大家?guī)硪黄猨ava學習筆記_關(guān)于字符串概述。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-05-05
  • maven 指定version不生效的問題

    maven 指定version不生效的問題

    這篇文章主要介紹了maven 指定version不生效的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • JVM?jstack實戰(zhàn)之死鎖問題詳解

    JVM?jstack實戰(zhàn)之死鎖問題詳解

    如果在生產(chǎn)環(huán)境發(fā)生了死鎖,我們將看到的是部署的程序沒有任何反應(yīng)了,這個時候我們可以借助?jstack進行分析,下面我們實戰(zhàn)操作查找死鎖的原因
    2022-10-10
  • SpringBoot整合Security實現(xiàn)權(quán)限控制框架(案例詳解)

    SpringBoot整合Security實現(xiàn)權(quán)限控制框架(案例詳解)

    Spring Security是一個能夠為基于Spring的企業(yè)應(yīng)用系統(tǒng)提供聲明式的安全訪問控制解決方案的安全框,是一個重量級的安全管理框架,本文給大家介紹的非常詳細,需要的朋友參考下吧
    2021-08-08
  • java 出現(xiàn)Zipexception 異常的解決辦法

    java 出現(xiàn)Zipexception 異常的解決辦法

    這篇文章主要介紹了java 出現(xiàn)Zipexception 異常的解決辦法的相關(guān)資料,出現(xiàn) java.util.zip.ZipException: error in opening zip file 異常的原因及解決方法,需要的朋友可以參考下
    2017-08-08
  • 一文解決pom.xml報錯Dependency "xxx" not found的問題

    一文解決pom.xml報錯Dependency "xxx" not f

    我們在使用maven進行jar包管理時有時會遇到pom.xml中報錯Dependency “XXX” not found,所以在本文中將給大家介紹一下pom.xml報錯Dependency "xxx" not found的解決方案,需要的朋友可以參考下
    2024-01-01
  • 淺談靜態(tài)變量、成員變量、局部變量三者的區(qū)別

    淺談靜態(tài)變量、成員變量、局部變量三者的區(qū)別

    下面小編就為大家?guī)硪黄獪\談靜態(tài)變量、成員變量、局部變量三者的區(qū)別。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-09-09
  • mybatis-plus 返回部分字段的解決方式

    mybatis-plus 返回部分字段的解決方式

    這篇文章主要介紹了mybatis-plus 返回部分字段的解決方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-10-10
  • Spring中任務(wù)調(diào)度之解讀@Scheduled和@Schedules注解的使用

    Spring中任務(wù)調(diào)度之解讀@Scheduled和@Schedules注解的使用

    這篇文章主要介紹了Spring中任務(wù)調(diào)度之解讀@Scheduled和@Schedules注解的使用,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-04-04
  • Java動態(tài)循環(huán)隊列是如何實現(xiàn)的

    Java動態(tài)循環(huán)隊列是如何實現(xiàn)的

    今天帶大家學習java隊列的相關(guān)知識,文章圍繞著如何實現(xiàn)Java動態(tài)循環(huán)隊列展開,文中有非常詳細的介紹及代碼示例,需要的朋友可以參考下
    2021-06-06

最新評論

高要市| 台江县| 沙洋县| 龙南县| 三原县| 扬州市| 佛冈县| 平潭县| 慈溪市| 柳河县| 布尔津县| 沈阳市| 策勒县| 资源县| 泌阳县| 宿州市| 顺义区| 曲麻莱县| 临夏县| 剑阁县| 岢岚县| 宜城市| 托克逊县| 奇台县| 遂川县| 新巴尔虎左旗| 庐江县| 黔西县| 成安县| 金乡县| 丰镇市| 汕尾市| 疏勒县| 涿鹿县| 保康县| 清镇市| 彭阳县| 鄂伦春自治旗| 饶平县| 华宁县| 泾川县|