2013/12/31

ConcurrentModificationException in java

我們一個專案曾經在某些特殊的狀況下,出現了ConcurrentModificationException,經過搜尋後得知這個錯誤會在下列狀況下發生。

當一個collection物件(包含Map.values())在執行Iterator.next()的同時如果有另一個執行緒對這個collection做add或remove item時會丟出ConcurrentModificationException。

舉例來說:在JVM裡放一個collection物件來存要開會的人。(假設有5個人的物件在collection裡),系統在會議時間開始時使用iterator來對這個collection裡的人逐一取電話來做外撥電話的動作,假設在這外撥電話的過程中(iterator尚未跑完)有另外一個執行緒add了一個開會的人到collection裡,此時iterator.next()就會被中斷且丟出錯誤。

為了避免系統會出現此錯誤我們針對了可能會有多個執行緒同時去add, remove, iterator的collection採取了下列的方式。

使用synchronized來同步Collection物件,並將iterator的邏輯封裝在裡面

public class Meeting{
    private Collection<Attendee> attendee;

        public void addAttendee(Attendee att){
            synchronized (attendee) {
                attendee.add(att);      
            }       
        }

        public int dialOutAll(){
            synchronized(attendee){             
                Iterator<Attendee> iter = attendee.iterator();
                while(iter.hasNext()){
                    Attendee att = iter.next();
                    //do call out here….
                }
                return sum;
            }
        }
}

如果邏輯太過複雜不適合將iterator封裝在java bean 裡的話則使用複製的方式將collection複製出來

public class Meeting{

    public List<Attendee> getAttendeeList(){
        List<Attendee> list = new ArrayList<Attendee>();

        synchronized(attendee){         
            Iterator<Attendee> iter = attendee.iterator();
            while(iter.hasNext()){
                list.add(iter.next());
            }
        }

        return list;
    }   
}
List list = meeting.getAttendeeList();
Iterator<Attendee> iter = attendee.iterator();
while(iter.hasNext()){
    Attendee attendee = iter.next();
    //do something here…
}

使用ConcurrentHashMap來取代Map

ConcurrentHashMap<Key, Value> map = new ConcurrentHashMap<Key, Value>();

註:下列狀況不需考慮多執行緒問題,因為collection的scope在method裡,不會有別的執行緒去存取到

private void test(){
    List<User> list = new ArryList<User>();
    list.add(new User());
    list.add(new User());

    Iterator<User> iter = list.iterator();
        while(iter.hasNext()){
        User user = iter.next();
        //do something here…
    }
}

結論

程式碼如果有使用到iterator時就需考慮到是否會有多執行緒的問題,尤其是singleton裡的collection物件。

2013/12/30

在iOS app中以Objective-C實作Long Polling客戶端 - 伺服器端以Atmosphere為例

Long Polling是實現推送技術(Push technology)的一種方式,搭配伺服器端Atmosphere Framework,我們以完整的實例,說明如何用Objective-C實作Long Polling客戶端的細節。

Long Polling簡介

Long Polling是實現推送技術(Push technology)的一種方式。 所謂推送技術,此處指的是讓HTTP伺服器能主動對HTTP客戶端發送資料的一種技術。

Long Polling的運作方式為:當客戶端向伺服器發送HTTP Request後,若伺服器端當前沒有需要回傳的資料,則不立即回傳HTTP Response,直到伺服器端有資料需要回應給客戶端,或是此次請求已超過一段時間,才將HTTP Response回傳給客戶端。

因此,只要每當客戶端收到HTTP Response後,便立刻向伺服器再次發送HTTP Request,即可模擬由伺服器端主動送資料給客戶端的情境,實現推送技術。

實作Long Polling客戶端非常容易,誠如前言,只要發送一個HTTP Request,並在每次收到HTTP Response後,再次發送HTTP Request即可。以下以簡短的Pseudo Code說明:

function doLongPolling {
    //送出HTTP Request,等待回應
    receivedData = sendHTTPRequest();

    //處理收到的HTTP Response
    handleReceivedData(receivedData);

    //建立新背景執行緒再次呼叫longPolling
    executeNewThread(doLongPolling);
}

惟需特別注意的是:客戶端的請求超時時間應比伺服器端還長,以避免客戶端誤判為伺服器端無回應;此外,也不能忘了網路或伺服器發生錯誤時的處理。

Atmosphere簡介

Atmosphere是一套整合Long Polling, Websocket等非同步網頁應用程式的伺服器端框架(Framework)。針對Atmosphere撰寫客戶端也相當容易,只要了解Atmosphere所訂的protocol即可。

Atmosphere protocol基礎

向Atmosphere發送HTTP Request時,需要在Get方法中設定一些參數:

  1. X-Atmosphere-Transport

    設定使用long-polling或是websocket。在本例中使用long-polling。

  2. X-Atmosphere-tracking-id

    trackingid是Atmosphere用來識別long polling客戶端的。 第一次發送HTTP Request時填0即可,Atmosphere會產生一個trackingid放在HTTP Response的Header並立即回傳。記錄起來並放在下一次Http Request的Get參數即可。

  3. X-Cache-Date

    Atmosphere伺服器端在某些情況下可以設定使用Cache。 第一次發送HTTP Request時填0即可,Atmosphere會在每次的HTTP Response的Header回傳當前的cachedate。記錄起來並放在下一次Http Request的Get參數即可。

以上僅簡短介紹此篇文章會用到的Atmosphere protocol。

關於更完整的Atmosphere Framework介紹請參考Understanding the Atmosphere Framework!

程式範例

以下將以較完整的實例,說明以Objective-C實作Long Polling客戶端的細節。

LongPollingClient class Declaration

首先,我們自訂一個類別,名為LongPollingClient

@interface LongPollingClient : NSObject
@property (strong, nonatomic) NSString* trackingid;
@property (strong, nonatomic) NSString* cachedate;
-(void)startLongPolling;
-(void)doLongPolling;
-(void)retryAfterDelay;
@end

類別中以兩個property儲存Atmosphere回傳的tracking以及cachedate。

而另外有三個method分別負責:

  1. startLongPolling

    初始化需要用到的參數,並呼叫doLongPolling以執行第一次long polling。

  2. doLongPolling

    每一次long polling時,送出HTTP Request與接收HTTP Response時所需要做的工作

  3. retryAfterDelay

    發生問題時,延遲一段時間再執行long polling

startLongPolling method:

初始化trackingid與cachedate

self.trackingid = @"0";
self.cachedate = @"0";

以背景執行緒執行long polling

[self performSelectorInBackground:@selector(doLongPolling) withObject: nil]; 

doLongPolling method:

讀取當前的trackingid與cachedate。

NSString* trackingid = self.trackingid;
NSString* cachedate = self.cachedate;

建立請求物件

NSString* urlString = [NSString stringWithFormat:@"http://www.xxx.net/xxx/xxx?X-Atmosphere-tracking-id=%@&X-Atmosphere-Framework=2.0.6-javascript&X-Atmosphere-Transport=long-polling&X-Cache-Date=%@&X-atmo-protocol=true&socketSeq=0",trackingid,cachedate];

NSURL* requestUrl = [NSURL URLWithString:@""];
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:requestUrl];

設定請求超時時間為75秒

[request setValue:@75 forKey:@"timeoutInterval"];

送出HTTP Request

NSError* error = nil;
NSHTTPURLResponse* response = nil;    
NSData* longpollResData = [NSURLConnection sendSynchronousRequest:request                                  returningResponse:&response error:&error];

網路錯誤時的處理,通常可以等待幾秒後再呼叫long polling

if(error != nil) {
    //retry after delay ...
   return;
}

有需要的話,可針對不同的HTTP Status Code進行處理

if(response.statusCode == 403) {
    //DO something
}

由HTTP Response Header中讀取trackingid與cachedate

if([response respondsToSelector:@selector(allHeaderFields)]) {
    NSDictionary * dic = [response allHeaderFields];
    NSString* tid = [dic objectForKey:@"X-Atmosphere-tracking-id"];
    NSString* cdate = [dic objectForKey:@"X-Cache-Date"];
    //do something with trackingid and cachedate...
}

將收到的資料轉為字串,並進行處理

NSString* responseString = [[NSString alloc] initWithData:longpollResData encoding:NSUTF8StringEncoding];
//do something with responseString

以背景執行緒執行下次的HTTP Request

    [self performSelectorInBackground:@selector(doLongPolling) withObject: nil];   

retryAfterDelay method:

等待5秒後再執行long polling

[self performSelector:@selector(doLongPolling) withObject:nil afterDelay:5.0f];

使用LongPollingClient類別

完成了LongPollingClient類別後,若要在其他程式碼中使用此類別, 只要建立LongPollingClient的實例,並呼叫startLongPolling方法即可。 舉例來說,在某個UIViewController的viewDidLoad:

- (void)viewDidLoad
{
    LongPollingClient* client = [[LongPollingClient alloc] init];
    [client startLongPolling];
}

DSL(Domain Specific language) vs GPL(General Purpose Language)

DSL 不是 Digital Subscriber Line,也跟家裡面 ADSL 沒有關係,而是 Domain Specific language 的縮寫。GPL 也不是 General Public License,而是 General Purpose Language 的縮寫。

專有名詞的縮寫

學習專業知識,一開始都會被專業的縮寫搞亂,也要能將這些專有名詞縮寫脫口而出,才能嚇唬人,突顯自己的專業。DSL 不是比較常聽到的 Digital Subscriber Line,而是 Domain-specific language 中文有些人翻譯為領域特定語言,但依照中文的習慣,要念成特定領域語言,為了遵循英文原文的順序,還是應該改成「領域專用語言」比較順口。

跟 DSL 對應的語言為 GPL,GPL 也不是常常聽到的 General Public License ,而是 General Purpose Language 的縮寫,舉個簡單的例子來說,Java、C 都屬於 GPL,而 HTML、SQL、MathML、Unix Shell Script、groovy 等等語言都算是 DSL。

其實大家最常遇到的,最常見的,會是各式各樣的 DSL,而不是 GPL,嚴格地來說,沒有一種語言會是 GPL,形成某一種語言之後,就會馬上變成是 DSL了,因為總會有某一些應用,是無法簡單地用 Java、C 這樣的 GPL 所描述的,那究竟怎麼判斷,那一種語言是屬於 GPL 呢?

GPL 如何分類?

開發人員的逆襲: Domain-Specific Languages 裡面提到:Karl Frank 認為,DSL泛指任何特定領域的語言,甚至連 C#、Java 都算是特定領域的語言,因為它們都是針對特定目的(軟體開發),用於特定場合的語言。就軟體開發這塊領域而言,C#、Java 可運用於各類型的軟體開發,所以我們通常將它們視為GPL。

但其實在分辨 GPL 的時候,必須還要再加上子分類,必須先知道是那一種類別的 GPL,從 Is UML a domain specific language (DSL)? 的討論得知,當我們在討論 Programming Language 的時候,C、Java 就是一種 General Purpsose Programming Language,而討論 Modeling Langage 的時候,UML就是一種 General Purpose Modeling Language,而 XML 是一種 General Purpose Markup Language。

Java 跟 Groovy 的關係,就像是 Erlang 跟 Erlang OTP 的關係一樣,Erlang 可類比 Java,而 OTP 並不屬於 GPL,他是一種 DSL,適用的領域是 high concurrency 與 distributed 的伺服器運算環境。

Compiler

講到程式語言,就馬上會回想起十幾年前學習的 Compiler,當時老師教的是 Lex(Lexical Analyzar) 與 Yacc(Yet Another Compiler Compiler) ,雖然是有學過,也可以很順口地落出 lex and yacc,但也僅只於此,再深入的東西也都忘光沒有了。你可以自行參閱 Lex 與 Yacc 介紹以lex/yacc實作算式計算機

如果想要做個新的語言編譯器,a list of compiler books — 汗牛充棟的編譯器參考資料 列出了一堆相關書籍,但我想我應該不會跳進那個大坑洞裡。

在 Java 領域中,就必須要認識 ANTLR,ANTLR 是 ANother Tool for Language Recognition 的縮寫,從 使用 Antlr 開發領域語言 得知,ANTLR 是用來開發一種 DSL 的工具。

大概的過程就是先撰寫語法定義 Compiler.g,定義好語法之後,用 ANTLR 產生詞法分析器和語法分析器,使用這個分析器,就可以用來驗證,我們輸入的語言expression,到底有沒有符合這個語言的定義規則,ANTLR支援,可以將分析器生成為 Java,C#,C,Python,JavaScript 等多種語言。

另外還有個工具 Antlrworks,可將 .g 的語法定義檔案,用視覺化的方式呈現出來。

為什麼要了解 DSL?

當你進入 IT 的領域,想要學習使用關聯式資料庫時,別人就會建議你去學SQL,想要當個 SE,就會建議你要學習 shell script,想要學習網頁程式設計,就會建議要學習 Javascript、HTML、CSS 等等東西,每一種語言都是一種工具,是前人為了特定的目的,而定義出來的一種特殊的語言。

重要的是,當我們學習某一種語言的時候,要很清楚的知道,這個語言的使用範圍跟目的。雖然也會有意外的創新發生,例如原本設定在網頁上使用的 JavaScript,轉身一變,成了運作在 nodejs 之下的一種熱門的 Server Side 語言。

DSL 的目標是要為了解決商務業務邏輯與特殊應用領域,跟GPL之間的距離,對於系統設計者來說,先了解 DSL 的理念與技術,才能以最適當的方式,設計出一套最適合的 DSL 語言,當然,所有的設計,都必須符合「恰當」的要求,不能為了設計而設計,以免變成了畫蛇添足,甚至是增加負擔的 DSL。

2013/12/27

Android 調用照相機功能,取得照片原圖、縮圖的方法

最近在研究可以開啟手機相機功能,拍完照取出照片的方法。爬了許多文章,發現光是開啟相機前置處理就會影響取出相片時的處理方法。

最基本的調用相機方法:
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); 
startActivityForResult(intent, 0);


Intent intent = new Intent(MediaStore.ACTIONIMAGECAPTURE); 這行就是利用intent去開啟Android的照相機介面,再然後拍完照,即呼叫onActivityResult

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
      super.onActivityResult(requestCode, resultCode, data);

     if (resultCode == RESULT_OK) {
          Bitmap mbmp = (Bitmap) data.getExtras().get("data");
          imgMicro.setImageBitmap(mbmp);
     }
}


(Bitmap) data.getExtras().get("data"); 就可以取到照片的"縮圖",沒有錯!只是縮圖。官方文件有說明,android系統分配給每個應用程式的最大內存記憶體是16M,為了防止佔用內存記憶體過大(OutOfMemory),所以把相機拍完回傳的照片經過壓縮。但是我想要再拍完照後可以直接取到原圖,有許多前輩對於這樣情況,對於叫用相機前startActivityForResult(intent, 0); 做了一些修改。
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
File tmpFile = new File(Environment.getExternalStorageDirectory(),"image.jpg");
Uri outputFileUri = Uri.fromFile(tmpFile);
intent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri);
startActivityForResult(intent, 0);

File tmpFile = new File(Environment.getExternalStorageDirectory(),"image.jpg"); 這行是利用tmpFile先新增一張照片,在開啟Android的照相機介面時,把這張照片指定為輸出檔案位置。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
     super.onActivityResult(requestCode, resultCode, data);
     if (resultCode == RESULT_OK) {
          Bitmap bitmap = BitmapFactory.decodeFile(Environment
          .getExternalStorageDirectory() + "/image.jpg");
          imgLarge.setImageBitmap(bitmap);
     }
}


拍完照將結果回傳時,可以透過 Environment.getExternalStorageDirectory() + "/image.jpg" 取得我們指定的圖片路徑。這時侯Intent data則取不到縮圖資料了,若想要取得縮圖就要自己用bitmap原圖去產生縮圖。
Bitmap minibm = ThumbnailUtils.extractThumbnail(bitmap, minWidth, minHeight);
  • minWidth 是縮圖的寬度
  • minHeight 是縮圖的長度

2013/12/25

DSL - Domain Specific Language 簡介

DSL - Domain Specific Language 簡介

Martin Fowler 針對 DSL 的主題,寫了一本書 Domain Specific Language(有篇文章提供了讀後心得:領域特定語言,被忽視多年的編程利器),另外也有一篇短文 Language Workbenches: The Killer-App for Domain Specific Languages?,根據這篇文章,在 InfoQ 還有一個演講影片 Introduction to Domain Specific Languages

以下以自己的理解,來解釋 Martin Fowler 想要說明的東西。

Language Oriented Programming

DSL 並不一種嶄新的概念,Martin Fowler 認為的新趨勢是 Language Oriented Programming,也就是要用設計一個新的 DSL 的想法,嘗試去做一個更具有彈性的程式實作,他先以一個例子來說明 DSL 的發展過程。

  1. 一開始因為有一份 Evet Data 純文字文件,它有特定的格式,因此產生了撰寫程式處理這個文字資料的需求。

    SVCLFOWLER 10101MS0120050313.........................
    SVCLHOHPE  10201DX0320050315........................
  2. 接下來他用了一個 class diagram,strategy pattern 說明他寫的Java處理程式,基本的想法,就是根據 Data 前面四個字元的識別 keyword,來判斷要使用那一種 strategy,以處理某種特定的Event。

    public void Configure(Reader target) {
     target.AddStrategy(ConfigureServiceCall());
     target.AddStrategy(ConfigureUsage());
    }
    private ReaderStrategy ConfigureServiceCall() {
     ReaderStrategy result = new ReaderStrategy("SVCL", typeof (ServiceCall));
     result.AddFieldExtractor(4, 18, "CustomerName");
     result.AddFieldExtractor(19, 23, "CustomerID");
     result.AddFieldExtractor(24, 27, "CallTypeCode");
     result.AddFieldExtractor(28, 35, "DateOfCallString");
     return result;
    }
  3. 後來他發現,可以將程式抽象化,拆分成兩個部份,就是用固定的程式碼,搭配一個 XML 設定檔。可以用某個 XML Reader 將設定讀進來,然後再對應到上面那個步驟的 strategy class,這樣的好處是,不需要重新編譯程式碼,改寫設定,就可以直接執行。

    <ReaderConfiguration>
     <Mapping Code = "SVCL" TargetClass = "dsl.ServiceCall">
         <Field name = "CustomerName" start = "4" end = "18"/>
         <Field name = "CustomerID" start = "19" end = "23"/>
         <Field name = "CallTypeCode" start = "24" end = "27"/>
         <Field name = "DateOfCallString" start = "28" end = "35"/>
     </Mapping>
    </ReaderConfiguration>
  4. XML 的標籤看起來太礙眼了,可以用另一種更簡潔的語法,來描述這個設定。

    mapping SVCL dsl.ServiceCall
     4-18: CustomerName
     19-23: CustomerID
     24-27: CallTypeCode
     28-35: DateOfCallString
  5. 最後,我們可用另一種自訂的 syntax 語法來改寫,因為要是一種語言,就必須要符合 EBNF 的定義規則。

    mapping('SVCL', ServiceCall) do
     extract 4..18, 'customer_name'
     extract 19..23, 'customer_ID'
     extract 24..27, 'call_type_code'
     extract 28..35, 'date_of_call_string'
    end

Language Oriented Programming 就是一種開發模式,嘗試用創造與使用 DSL 的方式,來完成一個軟體系統。基本上,所有用 DSL 設計的系統,都可以用 GSL 的方式完成。

Internal vs External DSL

接下來,Martin Fowler 說明了 Inernal 跟 External DSL 的差異。

他問了一個問題:以下這個設定的程式碼,是不是一種 DSL?

public void Configure(Reader target) {
    target.AddStrategy(ConfigureServiceCall());
    target.AddStrategy(ConfigureUsage());
}
private ReaderStrategy ConfigureServiceCall() {
    ReaderStrategy result = new ReaderStrategy("SVCL", typeof (ServiceCall));
    result.AddFieldExtractor(4, 18, "CustomerName");
    result.AddFieldExtractor(19, 23, "CustomerID");
    result.AddFieldExtractor(24, 27, "CallTypeCode");
    result.AddFieldExtractor(28, 35, "DateOfCallString");
    return result;
}

答案要根據你在那一種 GSL 上運作,上面這些 code ,如果放到 Ruby,那就是 DSL,但如果在 Java,就不是 DSL,因為 Java 本身並沒有提供任何語言的擴充工具,讓我們可以直接處理這樣的 Configuration Code。

External DSL

  1. 跟運行環境的程式語言完全不同
  2. 需要有 compiler/interpreter 才能運作

缺點:

  1. 缺少IDE
  2. parser/generator 技術太複雜
  3. 自創的語法,產生了很多語言,會出現學習上的困擾及 gap,雖然用Inernal的方式,不需要學很多語言,但還是要學習使用特定的 API

Internal DSL

  1. 以運行環境的程式語言撰寫
  2. 直接使用該程式語言的 syntax

缺點:

  1. 跟運行環境的程式語言緊密結合在一起,例如 因為 ruby 語法的限制 4..18 就不能寫成 4-18
  2. Java, C# 這種 mainstream languages,很難做出 Ineternal DSL,因為當初設計時,就不支援 (不過Java7已經支援 scripting 了)

跳脫 XML,將視野放在 DSL

Martin Fowler 提醒我們,多數的 Java Programmer 會使用 XML,會知道怎麼製作設定檔,但是不知道其實設定檔,也可以再往前推進,形成一種 DSL,下一次當我們面對這麼多繁雜的設定檔時,或許該換個角度想想,是不是能用某種 script,設計出一種領域專用的 DSL 來解決問題。

2013/12/23

夢想的起飛

麥司奇科技 Maxkit 成立已經五年了!    在台灣創業尤其是在軟體業,還能存活至今,我們已經是心存感激,就像公司的名字"麥司奇"台語的諧音 "不會死去" 一樣,勇敢並謙卑的面對嚴峻無情的市場考驗。


五年來,我們一直聚焦在語音與影音通訊的訊息整合系統,從自有產品的開發到為大型企業客戶專案客製整合系統,大大小小戰役也磨練了一些經驗出來。老祖宗常說飲水要思源,知識取之於社群之後,有了能力當然也要回饋到社群。這讓我們興起念頭,希望能夠將平時公司內部夥伴的分享技術課程或是我們在解決產品與專案開發上的問題的過程,能透過這個技術Blog與所有的喜愛技術的朋友們,分享與切磋討論。


這一步是一小步,如同五年前的我們,一群夥伴手牽手勇敢的踏出創業這條路,未來更希望認同我們追求夢想理念與技術領域的朋友,也能夠一起加入我們,一起來打造更棒的世界!


很高興能夠透過這一篇文章,大聲的向大家說  Maxkit technology program blog opening.