隨筆 - 18  文章 - 96  trackbacks - 0
          <2012年2月>
          2930311234
          567891011
          12131415161718
          19202122232425
          26272829123
          45678910


          常用鏈接

          留言簿(4)

          隨筆檔案

          相冊(cè)

          我的兄弟們

          搜索

          •  

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          在寫(xiě)多線程程序的時(shí)候,你就像個(gè)經(jīng)理,手下有那么或多或少的職員,你負(fù)責(zé)協(xié)調(diào)職員之間的工作,如果你稍不留神,職員之間就陷入了相互等待的尷尬狀態(tài)。還好,大多數(shù)時(shí)候多線程都還在我們掌控之內(nèi),即便是遇到這樣的deadlock情況,我們也能夠去修正,但是有的時(shí)候生活就是那么不盡人意,特別是NIO這種你不能掌控的時(shí)候,且看下面的代碼:

          /**
           * @(#)DeadLock.java  v0.1.0  2007-12-13
           
          */
          package ruislan.rswing.test;

          import java.net.InetSocketAddress;
          import java.nio.channels.ClosedChannelException;
          import java.nio.channels.SelectionKey;
          import java.nio.channels.Selector;
          import java.nio.channels.SocketChannel;
          import java.util.concurrent.Executors;

          /**
           * NIO DeadLock
           * 
           * 
          @author ruislan <a href="mailto:z17520@126.com"/>
           * 
          @version 0.1.0
           
          */
          public class DeadLock {
              
          public static void main(String[] args) throws Exception {
                  Service service 
          = new Service();
                  Executors.newSingleThreadExecutor().execute(service);

                  SocketChannel channel 
          = SocketChannel.open();
                  channel.configureBlocking(
          false);
                  channel.connect(
          new InetSocketAddress("http://www.aygfsteel.com"80));
                  service.addChannel(channel);
              }

              
          static class Service implements Runnable {
                  Selector selector;

                  
          public Service() {
                  }

                  
          public void run() {
                      
          try {
                          selector 
          = Selector.open();
                          
          while (true) {
                              selector.select();
                              System.out.println(selector.selectedKeys().size());
                          }
                      } 
          catch (Exception e) {
                      }
                  }

                  
          public void addChannel(SocketChannel channel) {
                      
          try {
                          channel.register(selector, SelectionKey.OP_CONNECT
                                  
          | SelectionKey.OP_READ);
                          System.out.println(
          "can reach here?when pigs fly!");
                      } 
          catch (ClosedChannelException e) {
                          e.printStackTrace();
                      }
                  }
              }
          }


          乍看之下,我們的代碼沒(méi)有問(wèn)題,但是運(yùn)行之后你會(huì)發(fā)現(xiàn),這句System.out.println("can reach here?when pigs fly!");永遠(yuǎn)無(wú)法執(zhí)行,也就是說(shuō)register()方法被阻塞了!Oh god bless,讓我們看看JavaDoc是怎么說(shuō)的:

          ...
          可在任意時(shí)間調(diào)用此方法。如果調(diào)用此方法的同時(shí)正在進(jìn)行另一個(gè)此方法或 configureBlocking 方法的調(diào)用,則在另一個(gè)操作完成前將首先阻塞該調(diào)用。然后此方法將在選擇器的鍵集上實(shí)現(xiàn)同步,因此如果調(diào)用此方法時(shí)并發(fā)地調(diào)用了涉及同一選擇器的另一個(gè)注冊(cè)或選擇操作,則可能阻塞此方法的調(diào)用。
          ...

          看這句“可在任意時(shí)間調(diào)用此方法。”,也就是說(shuō)我們調(diào)用的時(shí)間沒(méi)有任何限制,而阻塞的情況只會(huì)出現(xiàn)在“如果調(diào)用此方法的同時(shí)正在進(jìn)行另一個(gè)此方法或 configureBlocking 方法的調(diào)用”的情況下,即便是阻塞了,我相信“正在進(jìn)行另一個(gè)此方法或configureBlocking”也不會(huì)花掉太多的時(shí)間,況且這里沒(méi)有上面這樣的情況出現(xiàn)。那register()是被誰(shuí)擋住了?或者是BUG?

          我們來(lái)分析一下程序,程序有兩個(gè)線程主線程和Service線程,主線程啟動(dòng)后啟動(dòng)了Service線程,Service線程啟動(dòng)Selector然后Service線程陷入select()的阻塞中,同時(shí),主線程調(diào)用Service的addChannel()方法來(lái)添加一個(gè)SocketChannel,嗯,兩個(gè)線程之間唯一的聯(lián)系就是selector,看來(lái)要從selector尋找線索,很可惜,selector的實(shí)現(xiàn)沒(méi)有源代碼可查,不過(guò)可以肯定是channel的register()會(huì)調(diào)用selector的register(),雖然此時(shí)持有selector的Service線程被select()方法所阻塞,但是并不影響其他線程對(duì)其操作吧?那么,剩下的解釋就是Selector的select()方法和register()方法公用了一個(gè)鎖,select()方法阻塞住了,所以register()拿不到這個(gè)鎖了,那么這樣一來(lái)我們就只能保證讓select()或者register()不能同時(shí)調(diào)用或者register()調(diào)用的時(shí)候select()不持有這個(gè)鎖,也就是說(shuō)我們要用Service線程自己來(lái)執(zhí)行addChannel()方法,所以改進(jìn)如下:

          /**
           * @(#)DeadLock.java  v0.1.0  2007-12-13
           
          */
          package ruislan.rswing.test;

          import java.net.InetSocketAddress;
          import java.nio.channels.ClosedChannelException;
          import java.nio.channels.SelectionKey;
          import java.nio.channels.Selector;
          import java.nio.channels.SocketChannel;
          import java.util.Queue;
          import java.util.concurrent.LinkedBlockingQueue;

          /**
           * NIO DeadLock
           * 
           * 
          @author ruislan <a href="mailto:z17520@126.com"/>
           * 
          @version 0.1.0
           
          */
          public class DeadLock {
              
          public static void main(String[] args) {
                  Service service 
          = new Service();
                  
          new Thread(service).start();
                  
          for (int i = 0; i < 5; i++) {
                      
          new Thread(new ChannelAdder(service)).start();
                  }
              }

              
          static class ChannelAdder implements Runnable {
                  
          private Service service;

                  
          public ChannelAdder(Service service) {
                      
          this.service = service;
                  }

                  @Override
                  
          public void run() {
                      
          try {
                          SocketChannel channel 
          = SocketChannel.open();
                          channel.configureBlocking(
          false);
                          channel.connect(
          new InetSocketAddress(
                                  
          "http://www.aygfsteel.com"80));
                          service.addChannel(channel);
                      } 
          catch (Exception e) {
                          e.printStackTrace();
                      }
                  }
              }

              
          static class Service implements Runnable {
                  
          private Selector selector;
                  
          private Queue<SocketChannel> pendingRegisters;

                  
          public Service() {
                      pendingRegisters 
          = new LinkedBlockingQueue<SocketChannel>();
                  }

                  
          public void run() {
                      
          try {
                          selector 
          = Selector.open();
                          
          while (true) {
                              selector.select();
                              System.out.println(selector.selectedKeys().size());
                              handlePendingRegisters();
                          }
                      } 
          catch (Exception e) {
                      }
                  }

                  
          public void handlePendingRegisters() {
                      
          while (!pendingRegisters.isEmpty()) {
                          SocketChannel channel 
          = pendingRegisters.poll();
                          
          try {
                              channel.register(selector, SelectionKey.OP_CONNECT);
                              System.out.println(
          "can reach here?yeah!");
                          } 
          catch (ClosedChannelException e) {
                              e.printStackTrace();
                          }
                      }
                  }

                  
          public void addChannel(SocketChannel channel) {
                      pendingRegisters.offer(channel);
                      selector.wakeup();
                  }
              }
          }


          新的代碼,我們?cè)赟ervice的線程提供了一個(gè)待處理Channel隊(duì)列,然后在添加一個(gè)SocketChannel到隊(duì)列中時(shí)喚醒這個(gè)selector,取消阻塞,然后在Service的循環(huán)中處理這個(gè)pendingChannel,這樣就避免這個(gè)Deadlock的發(fā)生了。當(dāng)然我們亦可以在那個(gè)代碼上將select的超時(shí)時(shí)間設(shè)置非常的短,然后讓兩個(gè)線程去競(jìng)爭(zhēng),這樣做有太多的不可控性,不推薦了。

          posted on 2007-12-13 18:31 ruislan 閱讀(1355) 評(píng)論(3)  編輯  收藏

          FeedBack:
          # re: NIO的DeadLock 2007-12-16 17:16 或潛于淵
          正常情況下能用到j(luò)ava.nio時(shí)當(dāng)然要用java.util.concurrent包處理并發(fā)了……  回復(fù)  更多評(píng)論
            
          # re: NIO的DeadLock 2008-02-19 17:01 wan
          幫了大忙。謝謝!  回復(fù)  更多評(píng)論
            
          # re: NIO的DeadLock 2012-02-15 14:37 balancejia
          改進(jìn)后增加ChannelAdder的用意何在?我覺(jué)得沒(méi)有必要引入ChannelAdder ,只要解決select和register的鎖問(wèn)題就可以了  回復(fù)  更多評(píng)論
            

          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 府谷县| 房产| 闸北区| 时尚| 乳山市| 巨鹿县| 岑巩县| 星子县| 潼南县| 綦江县| 蓝山县| 永川市| 铜梁县| 沈丘县| 浦东新区| 奉贤区| 沽源县| 阿克陶县| 尼木县| 定陶县| 芷江| 桑日县| 黑水县| 湖口县| 澄江县| 新绛县| 金昌市| 双城市| 年辖:市辖区| 资中县| 阳城县| 射洪县| 英吉沙县| 潼南县| 蓝田县| 武山县| 河西区| 长治县| 丰台区| 宜君县| 安宁市|