隨筆 - 312, 文章 - 14, 評(píng)論 - 1393, 引用 - 0
          數(shù)據(jù)加載中……

          Java多線(xiàn)程初學(xué)者指南(10):使用Synchronized關(guān)鍵字同步類(lèi)方法

          本文為原創(chuàng),如需轉(zhuǎn)載,請(qǐng)注明作者和出處,謝謝!

          上一篇:Java多線(xiàn)程初學(xué)者指南(9):為什么要進(jìn)行數(shù)據(jù)同步

          要想解決“臟數(shù)據(jù)”的問(wèn)題,最簡(jiǎn)單的方法就是使用synchronized關(guān)鍵字來(lái)使run方法同步,代碼如下:

          public synchronized void run()
          {
               
          }

          從上面的代碼可以看出,只要在voidpublic之間加上synchronized關(guān)鍵字,就可以使run方法同步,也就是說(shuō),對(duì)于同一個(gè)Java類(lèi)的對(duì)象實(shí)例,run方法同時(shí)只能被一個(gè)線(xiàn)程調(diào)用,并當(dāng)前的run執(zhí)行完后,才能被其他的線(xiàn)程調(diào)用。即使當(dāng)前線(xiàn)程執(zhí)行到了run方法中的yield方法,也只是暫停了一下。由于其他線(xiàn)程無(wú)法執(zhí)行run方法,因此,最終還是會(huì)由當(dāng)前的線(xiàn)程來(lái)繼續(xù)執(zhí)行。先看看下面的代碼:

          sychronized關(guān)鍵字只和一個(gè)對(duì)象實(shí)例綁定

            class Test
            {
                  
          public synchronized void method()
                 {
                      
                 }
            }
             
            
          public class Sync implements Runnable
            {
                 
          private Test test;
                 
          public void run()
                 {
                      test.method();
                 }
                
          public Sync(Test test)
                 {
                    
          this.test = test;
                 }
                
          public static void main(String[] args) throws Exception
                 {
                     Test test1 
          =  new Test();
                     Test test2 
          =  new Test();
                     Sync sync1 
          = new Sync(test1);
                     Sync sync2 
          = new Sync(test2);
                    
          new Thread(sync1).start();
                    
          new Thread(sync2).start(); 
                 }
             }

          Test類(lèi)中的method方法是同步的。但上面的代碼建立了兩個(gè)Test類(lèi)的實(shí)例,因此,test1test2method方法是分別執(zhí)行的。要想讓method同步,必須在建立Sync類(lèi)的實(shí)例時(shí)向它的構(gòu)造方法中傳入同一個(gè)Test類(lèi)的實(shí)例,如下面的代碼所示:

          Sync sync1 = new Sync(test1);
              不僅可以使用synchronized來(lái)同步非靜態(tài)方法,也可以使用synchronized來(lái)同步靜態(tài)方法。如可以按如下方式來(lái)定義method方法:
          class Test 
          {
              public static synchronized void method() {   }
          }

          建立Test類(lèi)的對(duì)象實(shí)例如下:

          Test test = new Test();

          對(duì)于靜態(tài)方法來(lái)說(shuō),只要加上了synchronized關(guān)鍵字,這個(gè)方法就是同步的,無(wú)論是使用test.method(),還是使用Test.method()來(lái)調(diào)用method方法,method都是同步的,并不存在非靜態(tài)方法的多個(gè)實(shí)例的問(wèn)題。

          23種設(shè)計(jì)模式中的單件(Singleton)模式如果按傳統(tǒng)的方法設(shè)計(jì),也是線(xiàn)程不安全的,下面的代碼是一個(gè)線(xiàn)程不安全的單件模式。

          package test;

          // 線(xiàn)程安全的Singleton模式
          class Singleton
          {
              
          private static Singleton sample;

              
          private Singleton()
              {
              }
              
          public static Singleton getInstance()
              {
                  
          if (sample == null)
                  {
                      Thread.yield(); 
          // 為了放大Singleton模式的線(xiàn)程不安全性
                      sample = new Singleton();
                  }
                  
          return sample;
              }
          }
          public class MyThread extends Thread
          {
              
          public void run()
              {
                  Singleton singleton 
          = Singleton.getInstance();
                  System.out.println(singleton.hashCode());
              }
              
          public static void main(String[] args)
              {
                  Thread threads[] 
          = new Thread[5];
                  
          for (int i = 0; i < threads.length; i++)
                      threads[i] 
          = new MyThread();
                  
          for (int i = 0; i < threads.length; i++)
                      threads[i].start();
              }
          }

          在上面的代碼調(diào)用yield方法是為了使單件模式的線(xiàn)程不安全性表現(xiàn)出來(lái),如果將這行去掉,上面的實(shí)現(xiàn)仍然是線(xiàn)程不安全的,只是出現(xiàn)的可能性小得多。

          程序的運(yùn)行結(jié)果如下:

          25358555
          26399554
          7051261
          29855319
          5383406

          上面的運(yùn)行結(jié)果可能在不同的運(yùn)行環(huán)境上有所有同,但一般這五行輸出不會(huì)完全相同。從這個(gè)輸出結(jié)果可以看出,通過(guò)getInstance方法得到的對(duì)象實(shí)例是五個(gè),而不是我們期望的一個(gè)。這是因?yàn)楫?dāng)一個(gè)線(xiàn)程執(zhí)行了Thread.yield()后,就將CPU資源交給了另外一個(gè)線(xiàn)程。由于在線(xiàn)程之間切換時(shí)并未執(zhí)行到創(chuàng)建Singleton對(duì)象實(shí)例的語(yǔ)句,因此,這幾個(gè)線(xiàn)程都通過(guò)了if判斷,所以,就會(huì)產(chǎn)生了建立五個(gè)對(duì)象實(shí)例的情況(可能創(chuàng)建的是四個(gè)或三個(gè)對(duì)象實(shí)例,這取決于有多少個(gè)線(xiàn)程在創(chuàng)建Singleton對(duì)象之前通過(guò)了if判斷,每次運(yùn)行時(shí)可能結(jié)果會(huì)不一樣)。

          要想使上面的單件模式變成線(xiàn)程安全的,只要為getInstance加上synchronized關(guān)鍵字即可。代碼如下:

          public static synchronized Singleton getInstance() {   }

          當(dāng)然,還有更簡(jiǎn)單的方法,就是在定義Singleton變量時(shí)就建立Singleton對(duì)象,代碼如下:

          private static final Singleton sample = new Singleton();

          然后在getInstance方法中直接將sample返回即可。這種方式雖然簡(jiǎn)單,但不知在getInstance方法中創(chuàng)建Singleton對(duì)象靈活。讀者可以根據(jù)具體的需求選擇使用不同的方法來(lái)實(shí)現(xiàn)單件模式。

          在使用synchronized關(guān)鍵字時(shí)有以下四點(diǎn)需要注意:

          1.  synchronized關(guān)鍵字不能繼承。

          雖然可以使用synchronized來(lái)定義方法,但synchronized并不屬于方法定義的一部分,因此,synchronized關(guān)鍵字不能被繼承。如果在父類(lèi)中的某個(gè)方法使用了synchronized關(guān)鍵字,而在子類(lèi)中覆蓋了這個(gè)方法,在子類(lèi)中的這個(gè)方法默認(rèn)情況下并不是同步的,而必須顯式地在子類(lèi)的這個(gè)方法中加上synchronized關(guān)鍵字才可以。當(dāng)然,還可以在子類(lèi)方法中調(diào)用父類(lèi)中相應(yīng)的方法,這樣雖然子類(lèi)中的方法不是同步的,但子類(lèi)調(diào)用了父類(lèi)的同步方法,因此,子類(lèi)的方法也就相當(dāng)于同步了。這兩種方式的例子代碼如下:

          在子類(lèi)方法中加上synchronized關(guān)鍵字

          class Parent
          {
             
          public synchronized void method() {   }
          }
          class Child extends Parent
          {
             
          public synchronized void method() {   }
          }

          在子類(lèi)方法中調(diào)用父類(lèi)的同步方法

          class Parent
          {
              public synchronized void method() {   }
          }
          class Child extends Parent
          {
              
          public void method() { super.method();   }
          }

          2.  在定義接口方法時(shí)不能使用synchronized關(guān)鍵字。

          3.  構(gòu)造方法不能使用synchronized關(guān)鍵字,但可以使用下節(jié)要討論的synchronized塊來(lái)進(jìn)行同步。

          4.  synchronized可以自由放置。

          在前面的例子中使用都是將synchronized關(guān)鍵字放在方法的返回類(lèi)型前面。但這并不是synchronized可放置唯一位置。在非靜態(tài)方法中,synchronized還可以放在方法定義的最前面,在靜態(tài)方法中,synchronized可以放在static的前面,代碼如下:

          public synchronized void method();
          synchronized public void method();
          public static synchronized void method();
          public synchronized static void method();
          synchronized public static void method();

          但要注意,synchronized不能放在方法返回類(lèi)型的后面,如下面的代碼是錯(cuò)誤的:

          public void synchronized method();
          public static void synchronized method();

          synchronized關(guān)鍵字只能用來(lái)同步方法,不能用來(lái)同步類(lèi)變量,如下面的代碼也是錯(cuò)誤的。

          public synchronized int n = 0;
          public static synchronized int n = 0;

          雖然使用synchronized關(guān)鍵字同步方法是最安全的同步方式,但大量使用synchronized關(guān)鍵字會(huì)造成不必要的資源消耗以及性能損失。雖然從表面上看synchronized鎖定的是一個(gè)方法,但實(shí)際上synchronized鎖定的是一個(gè)類(lèi)。也就是說(shuō),如果在非靜態(tài)方法method1method2定義時(shí)都使用了synchronized,在method1未執(zhí)行完之前,method2是不能執(zhí)行的。靜態(tài)方法和非靜態(tài)方法的情況類(lèi)似。但靜態(tài)和非靜態(tài)方法不會(huì)互相影響。看看如下的代碼:

          package test;

          public class MyThread1 extends Thread
          {
              
          public String methodName;

              
          public static void method(String s)
              {
                  System.out.println(s);
                  
          while (true)
                      ;
              }
              
          public synchronized void method1()
              {
                  method(
          "非靜態(tài)的method1方法");
              }
              
          public synchronized void method2()
              {
                  method(
          "非靜態(tài)的method2方法");
              }
              
          public static synchronized void method3()
              {
                  method(
          "靜態(tài)的method3方法");
              }
              
          public static synchronized void method4()
              {
                  method(
          "靜態(tài)的method4方法");
              }
              
          public void run()
              {
                  
          try
                  {
                      getClass().getMethod(methodName).invoke(
          this);
                  }
                  
          catch (Exception e)
                  {
                  }
              }
              
          public static void main(String[] args) throws Exception
              {
                  MyThread1 myThread1 
          = new MyThread1();
                  
          for (int i = 1; i <= 4; i++)
                  {
                      myThread1.methodName 
          = "method" + String.valueOf(i);
                      
          new Thread(myThread1).start();
                      sleep(
          100);
                  }
              }
          }

          運(yùn)行結(jié)果如下:

          非靜態(tài)的method1方法
          靜態(tài)的method3方法

              從上面的運(yùn)行結(jié)果可以看出,method2method4method1method3未結(jié)束之前不能運(yùn)行。因此,我們可以得出一個(gè)結(jié)論,如果在類(lèi)中使用synchronized關(guān)鍵字來(lái)定義非靜態(tài)方法,那將影響這個(gè)中的所有使用synchronized關(guān)鍵字定義的非靜態(tài)方法。如果定義的是靜態(tài)方法,那么將影響類(lèi)中所有使用synchronized關(guān)鍵字定義的靜態(tài)方法。這有點(diǎn)象數(shù)據(jù)表中的表鎖,當(dāng)修改一條記錄時(shí),系統(tǒng)就將整個(gè)表都鎖住了,因此,大量使用這種同步方式會(huì)使程序的性能大幅度下降。

          下一篇:Java多線(xiàn)程初學(xué)者指南(11):使用Synchronized塊同步方法





          Android開(kāi)發(fā)完全講義(第2版)(本書(shū)版權(quán)已輸出到臺(tái)灣)

          http://product.dangdang.com/product.aspx?product_id=22741502



          Android高薪之路:Android程序員面試寶典 http://book.360buy.com/10970314.html


          新浪微博:http://t.sina.com.cn/androidguy   昵稱(chēng):李寧_Lining

          posted on 2009-03-20 13:05 銀河使者 閱讀(9786) 評(píng)論(4)  編輯  收藏 所屬分類(lèi): java 原創(chuàng)多線(xiàn)程

          評(píng)論

          # re: Java多線(xiàn)程初學(xué)者指南(10):使用Synchronized關(guān)鍵字同步類(lèi)方法  回復(fù)  更多評(píng)論   

          我想問(wèn)一下:

          MyThread myThread = new MyThread();
          new Thread(myThread).start();

          這里可不可以直接寫(xiě)成:
          MyThread myThread = new MyThread();
          myThread.start();

          我試了一下,這種方法可以直接運(yùn)行,請(qǐng)問(wèn)這兩種方法有什么不同?
          2009-03-20 16:23 | lveyo

          # re: Java多線(xiàn)程初學(xué)者指南(10):使用Synchronized關(guān)鍵字同步類(lèi)方法  回復(fù)  更多評(píng)論   

          @lveyo
          這兩種方法沒(méi)什么太大的區(qū)別,如果用第一種myThread相當(dāng)于一個(gè)回調(diào)對(duì)象,調(diào)用start方法時(shí)調(diào)用了myThread對(duì)象中的run方法。但有一個(gè)區(qū)別,如果用第一種方法,MyThread類(lèi)可以實(shí)現(xiàn)Runnable接口,也可以繼承Thread類(lèi),而使用第二種方法,MyThread只能是Thread類(lèi)的只類(lèi),如果只實(shí)現(xiàn)Runnable接口的類(lèi)是不能用第二種方法的。
          2009-03-20 16:39 | 銀河使者

          # re: Java多線(xiàn)程初學(xué)者指南(10):使用Synchronized關(guān)鍵字同步類(lèi)方法  回復(fù)  更多評(píng)論   

          @銀河使者
          明白了,謝謝,一直看你關(guān)于線(xiàn)程的介紹呢,明白了很多,概念清晰了
          2009-03-20 20:14 | lveyo

          # re: Java多線(xiàn)程初學(xué)者指南(10):使用Synchronized關(guān)鍵字同步類(lèi)方法  回復(fù)  更多評(píng)論   

          >>要想解決“臟數(shù)據(jù)”的問(wèn)題,最簡(jiǎn)單的方法就是使用synchronized關(guān)鍵字來(lái)使run方法同步,
          這個(gè)方法是不行的,synchronized =synchronized(this)只對(duì)當(dāng)前對(duì)象有效,所以還是會(huì)產(chǎn)生臟數(shù)據(jù)。
          2014-04-30 15:21 | wsh
          主站蜘蛛池模板: 枣强县| 胶南市| 依兰县| 龙井市| 丰城市| 清水河县| 北票市| 鹤壁市| 荥阳市| 特克斯县| 隆回县| 南投县| 普定县| 乌海市| 正安县| 威信县| 灵武市| 类乌齐县| 信丰县| 中山市| 始兴县| 信阳市| 称多县| 内江市| 天全县| 苍梧县| 正阳县| 塘沽区| 民丰县| 威海市| 永康市| 龙江县| 雷山县| 绥芬河市| 克拉玛依市| 临江市| 西吉县| 大同市| 枣庄市| 绥芬河市| 云梦县|