和風(fēng)細(xì)雨

          世上本無(wú)難事,心以為難,斯乃真難。茍不存一難之見于心,則運(yùn)用之術(shù)自出。

          線程的互斥

          多線程操作同一實(shí)例的問(wèn)題

          在多線程環(huán)境中,經(jīng)常有兩個(gè)以上線程操作同一實(shí)例的問(wèn)題,無(wú)論是并行Parallel環(huán)境還是并發(fā)Concurrent環(huán)境,都有發(fā)生有多個(gè)線程修改同一變量的問(wèn)題,如果這個(gè)變量是成員變量,多線程將會(huì)給程序帶來(lái)破壞性的影響。請(qǐng)見以下代碼。

          資源庫(kù)類

          public class ResourceLib {
            private long count1;

            private long count2;

            public ResourceLib(int count) {
              this.count1 = count;     
              this.count2 = count;
            }

            /**
             * 取回資源
             * 加上synchronized才是線程安全
             *
             * @param count
             */
            public void fetch(int count) {
              count1 += count;   
              mockLongTimeProcess();   
              count2 += count;   
              checkTwoCount(count);
            }

            /**
             * 送出資源
             * 加上synchronized才是線程安全
             *
             * @param count
             * @return
             */
            public void send(int count) {
              count1 -= count;   
              mockLongTimeProcess();   
              count2 -= count;
              checkTwoCount(count);
            }

             /**
             * 模擬一個(gè)耗時(shí)過(guò)程
             *
             */
            private void mockLongTimeProcess(){
              try{
                Thread.sleep(1000);
              }
              catch(Exception ex){
                ex.printStackTrace();
              }
            }

            private void checkTwoCount(int borrowCount) {
              if (count1 != count2) {
                System.out.println(count1 + "!= " + count2);
                System.exit(0);
              } else {
                System.out.println(count1 + "==" + count2);
              }
             
              if (Math.abs(count1) > 10000000 || Math.abs(count2) > 10000000) {
                count1 = 0;
                count2 = 0;
              }
            }

            public static void main(String[] args) {
              ResourceLib lib = new ResourceLib(10000);

              for (int i = 1; i < 20; i++) {
                new Supplier(String.valueOf(i), i, lib);
              }

              for (int i = 1; i < 10; i++) {
                new Comsumer(String.valueOf(i), i, lib);
              }
            }
          }

          取資源和給資源的兩個(gè)線程

          public class Comsumer implements Runnable{
            private ResourceLib resourceLib;
            private int count;
           
            public Comsumer(String name,int count,ResourceLib resourceLib){
              this.count=count;
              this.resourceLib=resourceLib;
             
              Thread thread=new Thread(this);
              thread.start();
            }
           
            public void run(){
              while(true){
                resourceLib.send(count);
              }
            }
          }

          public class Supplier implements Runnable{
            private ResourceLib resourceLib;
            private int count;
           
            public Supplier(String name,int count,ResourceLib resourceLib){
              this.count=count;
              this.resourceLib=resourceLib;
             
              Thread thread=new Thread(this);
              thread.start();
            }
           
            public void run(){
              while(true){
                resourceLib.fetch(count);
              }
            }
          }

          運(yùn)行結(jié)果

          在main函數(shù)中,程序啟動(dòng)了多個(gè)消費(fèi)者線程和生產(chǎn)者線程,消費(fèi)者線程在不斷減少count1和count2;生產(chǎn)者線程在不斷增加count1和count2,在單線程環(huán)境中,程序絕不會(huì)出現(xiàn)count1和count2不相等的情況,而多線程環(huán)境中,可能有一個(gè)線程在檢查count1和count2時(shí),其中一個(gè)已經(jīng)被另一個(gè)線程所修改。
          因此導(dǎo)致了兩個(gè)值不相等的情況發(fā)生。

          運(yùn)行結(jié)果之一
          10145!= 10001
          10145!= 10003
          10145!= 10006
          10145!= 10010
          10145!= 10015
          10145!= 10021
          10145!= 10028
          10145!= 10036
          10145!= 10045
          10145!= 10055
          10145!= 10066

          另一個(gè)經(jīng)典多線程實(shí)例:銀行取款

          package com.sitinspring.unsafebank;

          public class Bank{
            private int count;
           
            public Bank(int count){
              this.count=count;
            }
           
            public void withdraw(int money){
              if(count>money){
                mockLongTimeProcess();// 模擬耗時(shí)過(guò)程
                count-=money;
                System.out.println("提走"+money+" 現(xiàn)有"+count);    
              }
              else{
                System.out.println(" 現(xiàn)有數(shù)量"+count+"小于"+money+" 不能提取");
              }
             
              checkCount();
            }
           
            public void checkCount(){
              if(count<0){
                System.out.println(count + "< 0 ");
                System.exit(0);
              }
            }

           /**
             * 模擬一個(gè)耗時(shí)過(guò)程
             *
             */
            private void mockLongTimeProcess(){
              try{
                Thread.sleep(1000);
              }
              catch(Exception ex){
                ex.printStackTrace();
              }
            }
           
            public static void main(String[] args){
              Bank bank=new Bank(1000);
             
              for(int i=1;i<10;i++){
                new Customer(i*i*i,bank);
              }
            }
          }

          客戶類及講述

          public class Customer implements Runnable{
            private Bank bank;
            private int count;
           
            public Customer(int count,Bank bank){
              this.count=count;
              this.bank=bank;
             
              Thread thread=new Thread(this);
              thread.start();
            }
           
            public void run(){
              while(true){
                bank.withdraw(count);
              }
            }
          }

          在單線程環(huán)境中,提款時(shí)銀行的總數(shù)絕不會(huì)是負(fù)數(shù),但在多線程環(huán)境中,有可能在一個(gè)線程A符合條件在進(jìn)行耗時(shí)運(yùn)算和網(wǎng)絡(luò)數(shù)據(jù)傳遞時(shí),另一個(gè)線程B已經(jīng)把錢提走,總數(shù)已經(jīng)發(fā)生變化,結(jié)果A線程再提款時(shí)總錢數(shù)已經(jīng)減小了,因此致使銀行總錢數(shù)小于零。

          解決方法:在對(duì)成員變量進(jìn)行修改的函數(shù)前加上synchronized關(guān)鍵字

          synchronized方法又被成為”同步“方法。當(dāng)一個(gè)方法加上關(guān)鍵字synchronized聲明之后,就可以讓一個(gè)線程操作這個(gè)方法。“讓一個(gè)線程操作”并不是說(shuō)只能讓某一個(gè)特定的線程操作而已,而是指一次只能讓一個(gè)線程執(zhí)行,也就是說(shuō),在一個(gè)線程沒有退出同步方法前,其它線程絕無(wú)可能進(jìn)入這個(gè)同步方法和其它并列的同步方法,只能在外面排隊(duì)等候。
          一個(gè)實(shí)例的synchronized方法只能允許1次一個(gè)線程執(zhí)行。但是非synchronized方法就沒有這個(gè)限制,它可以供2個(gè)以上的線程執(zhí)行。

          修改后的線程安全的Bank類

          public class Bank{
            private int count;
           
            public Bank(int count){
              this.count=count;
            }
           
            public synchronized void withdraw(int money){
              if(count>money){
                mockLongTimeProcess();// 模擬耗時(shí)過(guò)程
                count-=money;
                System.out.println("提走"+money+" 現(xiàn)有"+count);    
              }
              else{
                System.out.println(" 現(xiàn)有數(shù)量"+count+"小于"+money+" 不能提取");
              }
             
              checkCount();
            }
           
            public void checkCount(){
              if(count<0){
                System.out.println(count + "< 0 ");
                System.exit(0);
              }
            }
          。。。、// 部分代碼省略
          }



          修改后的線程安全的ResourceLib類

          public class ResourceLib {
            private long count1;
            private long count2;

            public synchronized void fetch(int count) {
              count1 += count;   
              mockLongTimeProcess();   
              count2 += count;   
              checkTwoCount(count);
            }

            public synchronized void send(int count) {
              count1 -= count;   
              mockLongTimeProcess();   
              count2 -= count;
              checkTwoCount(count);
            }

            public void checkTwoCount(int borrowCount) {
              if (count1 != count2) {
                System.out.println(count1 + "!= " + count2);
                System.exit(0);
              } else {
                System.out.println(count1 + "==" + count2);
              }
             
              if (Math.abs(count1) > 10000000 || Math.abs(count2) > 10000000) {
                count1 = 0;
                count2 = 0;
              }
            }
          }

          注:部分代碼省略



          執(zhí)行之后

          在一個(gè)執(zhí)行synchronized方法的線程執(zhí)行結(jié)束后,鎖定即被釋放, 其它不得其門而入的線程開始爭(zhēng)搶鎖定,一定會(huì)有一個(gè)線程獲取鎖定,沒有搶到的線程只好再繼續(xù)等候.
          注意: 非靜態(tài)的synchronized方法鎖定的對(duì)象是實(shí)例,靜態(tài)的synchronized方法鎖定的對(duì)象是類對(duì)象。

          同步塊

          以下同步方法可用右邊的同步塊代替:
          public synchronized void fun(){
              ………
          }

          與左邊同步方法對(duì)等的同步塊:
          public void fun(){
             synchronized(this){
               ………
             }
          }

          同步塊和同步方法的比較

          1)同步方法鎖定的類的實(shí)例或類對(duì)象,同步塊則可以換成任意實(shí)例,靈活性更高。
          2)有時(shí)需要多個(gè)鎖定而不是一個(gè),如函數(shù)A和函數(shù)B需要鎖定1,函數(shù)B和函數(shù)C需要鎖定2,這時(shí)如果使用同步方法無(wú)疑會(huì)鎖定A和C,造成程序效率的降低。這時(shí)最應(yīng)該使用同步塊。

          什么時(shí)候該加同步synchronized

          如果一個(gè)函數(shù)或代碼塊有可能被多個(gè)線程進(jìn)入,而這個(gè)函數(shù)或代碼塊又修改了類的成員變量,則這個(gè)這個(gè)函數(shù)或代碼塊就應(yīng)該加上同步synchronized。
          如果一個(gè)函數(shù)或代碼有可能被多個(gè)線程進(jìn)入,而這個(gè)函數(shù)或代碼塊只是讀取類的成員變量,則這個(gè)這個(gè)函數(shù)或代碼塊就不該加上同步synchronized。

           

          posted on 2008-02-22 12:43 和風(fēng)細(xì)雨 閱讀(482) 評(píng)論(1)  編輯  收藏 所屬分類: 線程

          評(píng)論

          # re: 線程的互斥 2008-04-13 23:23 javafans_2008@163.com

          注意: 非靜態(tài)的synchronized方法鎖定的對(duì)象是實(shí)例,靜態(tài)的synchronized方法鎖定的對(duì)象是類對(duì)象。

          請(qǐng)問(wèn)下:
          實(shí)例和類對(duì)象有什么區(qū)別呀??   回復(fù)  更多評(píng)論   

          主站蜘蛛池模板: 土默特右旗| 鄱阳县| 道孚县| 天镇县| 哈密市| 中江县| 桐乡市| 奉节县| 万荣县| 苍山县| 宁武县| 犍为县| 鸡泽县| 马鞍山市| 监利县| 会宁县| 和平区| 太仆寺旗| 明水县| 中宁县| 革吉县| 合山市| 远安县| 芜湖县| 金平| 文昌市| 西乌珠穆沁旗| 石城县| 东台市| 九龙城区| 永平县| 嘉荫县| 聂拉木县| 衡阳县| 东乡族自治县| 新安县| 伽师县| 铁岭县| 乡宁县| 赣榆县| 吉林省|