熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java高級技術 >> 正文

構建Java並發模型框架

2022-06-13   來源: Java高級技術 

  Java的多線程特性為構建高性能的應用提供了極大的方便但是也帶來了不少的麻煩線程間同步數據一致性等煩瑣的問題需要細心的考慮一不小心就會出現一些微妙的難以調試的錯誤另外應用邏輯和線程邏輯糾纏在一起會導致程序的邏輯結構混亂難以復用和維護本文試圖給出一個解決這個問題的方案通過構建一個並發模型框架(framework)使得開發多線程的應用變得容易
  基礎知識
  Java語言提供了對於線程很好的支持實現方法小巧優雅對於方法重入的保護信號量(semaphore)和臨界區(critical section)機制的實現都非常簡潔可以很容易的實現多線程間的同步操作從而保護關鍵數據的一致性這些特點使得Java成為面向對象語言中對於多線程特性支持方面的佼佼者(C++正在試圖把boost庫中的對於線程的支持部分納入語言標准)
  Java中內置了對於對象並發訪問的支持每一個對象都有一個監視器(monitor)同時只允許一個線程持有監視器從而進行對對象的訪問那些沒有獲得監視器的線程必須等待直到持有監視器的線程釋放監視器對象通過synchronized關鍵字來聲明線程必須獲得監視器才能進行對自己的訪問
  synchronized聲明僅僅對於一些較為簡單的線程間同步問題比較有效對於哪些復雜的同步問題比如帶有條件的同步問題Java提供了另外的解決方法wait/notify/notifyAll獲得對象監視器的線程可以通過調用該對象的wait方法主動釋放監視器等待在該對象的線程等待隊列上此時其他線程可以得到監視器從而訪問該對象之後可以通過調用notify/notifyAll方法來喚醒先前因調用wait方法而等待的線程一般情況下對於wait/notify/notifyAll方法的調用都是根據一定的條件來進行的比如經典的生產者/消費者問題中對於隊列空滿的判斷熟悉POSIX的讀者會發現使用wait/notify/notifyAll可以很容易的實現POSIX中的一個線程間的高級同步技術條件變量
  有很多的書籍資料對於synchronizedwait/notify/notifyAll進行了詳細的介紹參考文獻〔〕中對於synchronized關鍵字以及和線程有關的Java內存模型有深入詳細的論述有興趣的讀者可以自行學習不在此贅述
  簡單例子
  本文將圍繞一個簡單的例子展開論述這樣可以更容易突出我們解決問題的思路方法本文想向讀者展現的正是這些思路方法這些思路方法更加適用於解決大規模復雜應用中的並發問題
  考慮一個簡單的例子我們有一個服務提供者它通過一個接口對外提供服務服務內容非常簡單就是在標准輸出上打印Hello World類結構圖如下
   
  代碼如下 
  interface Service
  {
  public void sayHello();
  }
  
  class ServiceImp implements Service
  {
  public void sayHello() {
    Systemoutprintln(Hello World!);
  }
  }
  
  class Client
  {
  public Client(Service s) {
    _service = s;
  }  
  
  public void requestService() {
    _servicesayHello();
  }
  
  private Service _service;
  }
  如果現在有新的需求要求該服務必須支持Client的並發訪問一種簡單的方法就是在ServicImp類中的每個方法前面加上synchronized聲明來保證自己內部數據的一致性(當然對於本例來說目前是沒有必要的因為ServiceImp沒有需要保護的數據但是隨著需求的變化以後可能會有的)但是這樣做至少會存在以下幾個問題
  現在要維護ServiceImp的兩個版本多線程版本和單線程版本(有些地方比如其他項目可能沒有並發的問題)容易帶來同步更新和正確選擇版本的問題給維護帶來麻煩
  如果多個並發的Client頻繁調用該服務由於是直接同步調用會造成Client阻塞降低服務質量
  很難進行一些靈活的控制比如根據Client的優先級進行排隊等等
  這些問題對於大型的多線程應用服務器尤為突出對於一些簡單的應用(如本文中的例子)可能根本不用考慮本文正是要討論這些問題的解決方案文中的簡單的例子只是提供了一個說明問題展示思路方法的平台
  如何才能較好的解決這些問題有沒有一個可以重用的解決方案呢?讓我們先把這些問題放一放先來談談和框架有關的一些問題
  框架概述
  熟悉 面向對象的讀者一定知道面向對象的最大的優勢之一就是軟件復用通過復用可以減少很多的工作量提高軟件開發生產率復用本身也是分層次的代碼級的復用和設計架構的復用
  大家可能非常熟悉C語言中的一些標准庫它們提供了一些通用的功能讓你的程序使用但是這些標准庫並不能影響你的程序結構和設計思路僅僅是提供一些機能幫助你的程序完成工作它們使你不必重頭編寫一般性的通用功能(比如printf)它們強調的是程序代碼本身的復用性而不是設計架構的復用性
  那麼什麼是框架呢?所謂框架它不同於一般的標准庫是指一組緊密關聯的(類)classes強調彼此的配合以完成某種可以重復運用的設計概念這些類之間以特定的方式合作彼此不可或缺它們相當程度的影響了你的程序的形貌框架本身規劃了應用程序的骨干讓程序遵循一定的流程和動線展現一定的風貌和功能這樣就使程序員不必費力於通用性的功能的繁文缛節集中精力於專業領域
  有一點必須要強調放之四海而皆准的框架是不存在的也是最沒有用處的框架往往都是針對某個特定應用領域的是在對這個應用領域進行深刻理解的基礎上抽象出該應用的概念模型在這些抽象的概念上搭建的一個模型是一個有形無體的框架不同的具體應用根據自身的特點對框架中的抽象概念進行實現從而賦予框架生命完成應用的功能
  基於框架的應用都有兩部分構成框架部分和特定應用部分要想達到框架復用的目標必須要做到框架部分和特定應用部分的隔離使用面向對象的一個強大功能多態可以實現這一點在框架中完成抽象概念之間的交互關聯把具體的實現交給特定的應用來完成其中一般都會大量使用了Template Method設計模式
  Java中的Collection Framework以及微軟的MFC都是框架方面很好的例子有興趣的讀者可以自行研究
  構建框架
  如何構建一個Java並發模型框架呢?讓我們先回到原來的問題先來分析一下原因造成要維護多線程和單線程兩個版本的原因是由於把應用邏輯和並發邏輯混在一起如果能夠做到把應用邏輯和並發模型進行很好的隔離那麼應用邏輯本身就可以很好的被復用而且也很容易把並發邏輯添加進來而不會對應用邏輯造成任何影響造成Client阻塞性能降低以及無法進行額外的控制的原因是由於所有的服務調用都是同步的解決方案很簡單改為異步調用方式把服務的調用和服務的執行分離
  
  首先來介紹一個概念活動對象(Active Object)所謂活動對象是相對於被動對象(passive object)而言的被動對象的方法的調用和執行都是在同一個線程中的被動對象方法的調用是同步的阻塞的一般的對象都屬於被動對象主動對象的方法的調用和執行是分離的主動對象有自己獨立的執行線程主動對象的方法的調用是由其他線程發起的但是方法是在自己的線程中執行的主動對象方法的調用是異步的非阻塞的
  本框架的核心就是使用主動對象來封裝並發邏輯然後把Client的請求轉發給實際的服務提供者(應用邏輯)這樣無論是Client還是實際的服務提供者都不用關心並發的存在不用考慮並發所帶來的數據一致性問題從而實現應用邏輯和並發邏輯的隔離服務調用和服務執行的隔離下面給出關鍵的實現細節
  本框架有如下幾部分構成
  一個ActiveObject類從Thread繼承封裝了並發邏輯的活動對象
  一個ActiveQueue類主要用來存放調用者請求
  一個MethodRequest接口主要用來封裝調用者的請求Command設計模式的一種實現方式
  它們的一個簡單的實現如下
  //MethodRequest接口定義
   interface MethodRequest
  {
  public void call();
  }
  
  //ActiveQueue定義其實就是一個producer/consumer隊列
  class ActiveQueue
  {
   public ActiveQueue() {
    _queue = new Stack();
   }
  
  public synchronized void enqueue(MethodRequest mr) {
    while(_queuesize() > QUEUE_SIZE) {
      try {
          wait();
      }catch (InterruptedException e) {
          eprintStackTrace();
      } 
    }
    
    _queuepush(mr);
    notifyAll();
    Systemoutprintln(Leave Queue);
  }
  
  public synchronized MethodRequest dequeue() {
    MethodRequest mr;
    
    while(_queueempty()) {
      try {
        wait();
      }catch (InterruptedException e) {
        eprintStackTrace();
      }
    }
    mr = (MethodRequest)_queuepop();
    notifyAll();
    
   return mr;
  }  
  
  private Stack _queue;
  pri
From:http://tw.wingwit.com/Article/program/Java/gj/201311/27286.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.