java 異步機制與同步機制的區別


所謂異步輸入輸出機制,是指在進行輸入輸出處理時,不必等到輸入輸出處理完畢才返回。所以異步的同義語是非阻塞(None Blocking)。
 
網上有很多網友用很通俗的比喻  把同步和異步講解的很透徹 轉過來
 
舉個例子:普通B/S模式(同步)AJAX技術(異步) 
          同步:提交請求->等待服務器處理->處理完畢返回 這個期間客戶端瀏覽器不能干任何事 
          異步:  請求通過事件觸發->服務器處理(這是瀏覽器仍然可以作其他事情)->處理完畢
 
同步就是你叫我去吃飯,我聽到了就和你去吃飯;如果沒有聽到,你就不停的叫,直到我告訴你聽到了,才一起去吃飯。 
異步就是你叫我,然后自己去吃飯,我得到消息后可能立即走,也可能等到下班才去吃飯。 
所以,要我請你吃飯就用同步的方法,要請我吃飯就用異步的方法,這樣你可以省錢。
 
 
以通訊為例 
          同步:發送一個請求,等待返回,然后再發送下一個請求 
          異步:發送一個請求,不等待返回,隨時可以再發送下一個請求 
          並發:同時發送多個請求
 
 
 
下面再轉一段關於java異步應用的文章
 
       用異步輸入輸出流編寫Socket進程通信程序
    在Merlin中加入了用於實現異步輸入輸出機制的應用程序接口包:java.nio(新的輸入輸出包,定義了很多基本類型緩沖(Buffer)),java.nio.channels(通道及選擇器等,用於異步輸入輸出),java.nio.charset(字符的編碼解碼)。通道(Channel)首先在選擇器(Selector)中注冊自己感興趣的事件,當相應的事件發生時,選擇器便通過選擇鍵(SelectionKey)通知已注冊的通道。然后通道將需要處理的信息,通過緩沖(Buffer)打包,編碼/解碼,完成輸入輸出控制。
          通道介紹:
    這里主要介紹ServerSocketChannel和SocketChannel.它們都是可選擇的(selectable)通道,分別可以工作在同步和異步兩種方式下(注意,這里的可選擇不是指可以選擇兩種工作方式,而是指可以有選擇的注冊自己感興趣的事件)。可以用channel.configureBlocking(Boolean )來設置其工作方式。與以前版本的API相比較,ServerSocketChannel就相當於ServerSocket (ServerSocketChannel封裝了ServerSocket),而SocketChannel就相當於Socket(SocketChannel封裝了Socket)。當通道工作在同步方式時,編程方法與以前的基本相似,這里主要介紹異步工作方式。
所謂異步輸入輸出機制,是指在進行輸入輸出處理時,不必等到輸入輸出處理完畢才返回。所以異步的同義語是非阻塞(None Blocking)。在服務器端,ServerSocketChannel通過靜態函數open()返回一個實例serverChl。然后該通道調用serverChl.socket().bind()綁定到服務器某端口,並調用register(Selector sel, SelectionKey.OP_ACCEPT)注冊OP_ACCEPT事件到一個選擇器中(ServerSocketChannel只可以注冊OP_ACCEPT事件)。當有客戶請求連接時,選擇器就會通知該通道有客戶連接請求,就可以進行相應的輸入輸出控制了;在客戶端,clientChl實例注冊自己感興趣的事件后(可以是OP_CONNECT,OP_READ,OP_WRITE的組合),調用clientChl.connect (InetSocketAddress )連接服務器然后進行相應處理。注意,這里的連接是異步的,即會立即返回而繼續執行后面的代碼。
          選擇器和選擇鍵介紹:
    選擇器(Selector)的作用是:將通道感興趣的事件放入隊列中,而不是馬上提交給應用程序,等已注冊的通道自己來請求處理這些事件。換句話說,就是選擇器將會隨時報告已經准備好了的通道,而且是按照先進先出的順序。那么,選擇器是通過什么來報告的呢?選擇鍵(SelectionKey)。選擇鍵的作用就是表明哪個通道已經做好了准備,准備干什么。你也許馬上會想到,那一定是已注冊的通道感興趣的事件。不錯,例如對於服務器端serverChl來說,可以調用key.isAcceptable()來通知serverChl有客戶端連接請求。相應的函數還有:SelectionKey.isReadable(),SelectionKey.isWritable()。一般的,在一個循環中輪詢感興趣的事件(具體可參照下面的代碼)。如果選擇器中尚無通道已注冊事件發生,調用Selector.select()將阻塞,直到有事件發生為止。另外,可以調用selectNow()或者select(long timeout)。前者立即返回,沒有事件時返回0值;后者等待timeout時間后返回。一個選擇器最多可以同時被63個通道一起注冊使用。
 
           應用實例:
    下面是用異步輸入輸出機制實現的客戶/服務器實例程序�D�D程序清單1(限於篇幅,只給出了服務器端實現,讀者可以參照着實現客戶端代碼):
public class NBlockingServer {
    int port = 8000;
    int BUFFERSIZE = 1024;
    Selector selector = null;
    ServerSocketChannel serverChannel = null;
    HashMap clientChannelMap = null;//用來存放每一個客戶連接對應的套接字和通道
    public NBlockingServer( int port ) {
        this.clientChannelMap = new HashMap();
        this.port = port;
    }
    public void initialize() throws IOException {
      //初始化,分別實例化一個選擇器,一個服務器端可選擇通道
      this.selector = Selector.open();
      this.serverChannel = ServerSocketChannel.open();
      this.serverChannel.configureBlocking(false);
      InetAddress localhost = InetAddress.getLocalHost();
      InetSocketAddress isa = new InetSocketAddress(localhost, this.port );
      this.serverChannel.socket().bind(isa);//將該套接字綁定到服務器某一可用端口
    }
    //結束時釋放資源
    public void finalize() throws IOException {
        this.serverChannel.close();
        this.selector.close();
    }
    //將讀入字節緩沖的信息解碼
    public String decode( ByteBuffer byteBuffer ) throws 
CharacterCodingException {
        Charset charset = Charset.forName( "ISO-8859-1" );
        CharsetDecoder decoder = charset.newDecoder();
        CharBuffer charBuffer = decoder.decode( byteBuffer );
        String result = charBuffer.toString();
        return result;
    }
    //監聽端口,當通道准備好時進行相應操作
    public void portListening() throws IOException, InterruptedException {
      //服務器端通道注冊OP_ACCEPT事件
      SelectionKey acceptKey =this.serverChannel.register( this.selector,
                                           SelectionKey.OP_ACCEPT );
        //當有已注冊的事件發生時,select()返回值將大於0
        while (acceptKey.selector().select() > 0 ) {
            System.out.println("event happened");
            //取得所有已經准備好的所有選擇鍵
            Set readyKeys = this.selector.selectedKeys();
            //使用迭代器對選擇鍵進行輪詢
            Iterator i = readyKeys.iterator();
            while (i.hasNext()) {
                SelectionKey key = (SelectionKey)i.next();
                i.remove();//刪除當前將要處理的選擇鍵
                if ( key.isAcceptable() ) {//如果是有客戶端連接請求
                    System.out.println("more client connect in!");
                    ServerSocketChannel nextReady =
                        (ServerSocketChannel)key.channel();
                    //獲取客戶端套接字
                    Socket s = nextReady.accept();
                    //設置對應的通道為異步方式並注冊感興趣事件
                    s.getChannel().configureBlocking( false );
                    SelectionKey readWriteKey =
                        s.getChannel().register( this.selector,
                            SelectionKey.OP_READ|SelectionKey.OP_WRITE  );
                    //將注冊的事件與該套接字聯系起來
readWriteKey.attach( s );
//將當前建立連接的客戶端套接字及對應的通道存放在哈希表//clientChannelMap中
                    this.clientChannelMap.put( s, new 
ClientChInstance( s.getChannel() ) );
                    }
                else if ( key.isReadable() ) {//如果是通道讀准備好事件
                    System.out.println("Readable");
                    //取得選擇鍵對應的通道和套接字
                    SelectableChannel nextReady =
                        (SelectableChannel) key.channel();
                    Socket socket = (Socket) key.attachment();
                    //處理該事件,處理方法已封裝在類ClientChInstance中
                    this.readFromChannel( socket.getChannel(),
                    (ClientChInstance)
this.clientChannelMap.get( socket ) );
                }
                else if ( key.isWritable() ) {//如果是通道寫准備好事件
                    System.out.println("writeable");
                    //取得套接字后處理,方法同上
                    Socket socket = (Socket) key.attachment();
                    SocketChannel channel = (SocketChannel) 
socket.getChannel();
                    this.writeToChannel( channel,"This is from server!");
                }
            }
        }
    }
    //對通道的寫操作
    public void writeToChannel( SocketChannel channel, String message ) 
throws IOException {
        ByteBuffer buf = ByteBuffer.wrap( message.getBytes()  );
        int nbytes = channel.write( buf );
    }
     //對通道的讀操作
    public void readFromChannel( SocketChannel channel, ClientChInstance clientInstance )
    throws IOException, InterruptedException {
        ByteBuffer byteBuffer = ByteBuffer.allocate( BUFFERSIZE );
        int nbytes = channel.read( byteBuffer );
        byteBuffer.flip();
        String result = this.decode( byteBuffer );
        //當客戶端發出”@exit”退出命令時,關閉其通道
        if ( result.indexOf( "@exit" ) >= 0 ) {
            channel.close();
        }
        else {
                clientInstance.append( result.toString() );
                //讀入一行完畢,執行相應操作
                if ( result.indexOf( "\n" ) >= 0 ){
                System.out.println("client input"+result);
                clientInstance.execute();
                }
        }
    }
    //該類封裝了怎樣對客戶端的通道進行操作,具體實現可以通過重載execute()方法
    public class ClientChInstance {
        SocketChannel channel;
        StringBuffer buffer=new StringBuffer();
        public ClientChInstance( SocketChannel channel ) {
            this.channel = channel;
        }
        public void execute() throws IOException {
            String message = "This is response after reading from channel!";
            writeToChannel( this.channel, message );
            buffer = new StringBuffer();
        }
        //當一行沒有結束時,將當前字竄置於緩沖尾
        public void append( String values ) {
            buffer.append( values );
        }
    }
    //主程序
    public static void main( String[] args ) {
        NBlockingServer nbServer = new NBlockingServer(8000);
        try {
            nbServer.initialize();
        } catch ( Exception e ) {
            e.printStackTrace();
            System.exit( -1 );
        }
        try {
            nbServer.portListening();
        }
        catch ( Exception e ) {
            e.printStackTrace();
        }
    }
}
 
小結:
從以上程序段可以看出,服務器端沒有引入多余線程就完成了多客戶的客戶/服務器模式。該程序中使用了回調模式(CALLBACK),細心的讀者應該早就看出來了。需要注意的是,請不要將原來的輸入輸出包與新加入的輸入輸出包混用,因為出於一些原因的考慮,這兩個包並不兼容。即使用通道時請使用緩沖完成輸入輸出控制。該程序在Windows2000,J2SE1.4下,用telnet測試成功


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM