2014-06-15 44 views
0

我有一個由後臺服務調用的Runnable。 Runnable本身在我的FactoryManagerClass中被初始化爲一個SingleTon對象。讓Runnable或Thread只調用一次

在我的Logcat中,我在同一秒內運行了一天之後有幾次連接嘗試。

06-15 12:00:52.665 9374-9656/com.myAppI/﹕ RestPushServiceRunnable: : Requesting url: http://my.ip/lp/053303932; LastModify: Thu, 01 Jan 1970 00:00:00 UTC; ETAG: 
06-15 12:00:52.680 9374-17595/com.myAppI/﹕ RestPushServiceRunnable: : Requesting url:   http://my.ip/lp/053303932; LastModify: Thu, 01 Jan 1970 00:00:00 UTC; ETAG: 
06-15 12:00:52.685 9374-15696/com.myAppI/﹕ RestPushServiceRunnable: : Requesting url: http://my.ip/lp/053303932; LastModify: Thu, 01 Jan 1970 00:00:00 UTC; ETAG: 

這意味着它創建了3次,但至少它應該只有1次可運行的運行。

private static LPRunnable lpRunnable = null; 
private static ExecutorService pushThreadPoolExecutor = null; 

public static LPRunnable getLPRunnable() { 

    if (lpRunnable == null) { 
     synchronized (LPRunnable.class) { 
      lpRunnable = new LPRunnable (CustomService.getContext()); 
     } 
    } 
    return lpRunnable; 
} 

public static ExecutorService getPushThreadPoolExecutor() { 
    if (pushThreadPoolExecutor == null) { 
     pushThreadPoolExecutor = Executors.newSingleThreadExecutor(); 

    } 
    return pushThreadPoolExecutor; 
} 

我運行的類是(幾乎截斷)

public class LPRunnable implements Runnable { 
public static boolean isRunning = false; 


@Override 
public void run() { 
    HttpURLConnection connection = null; 
    try { 
     isRunning = true; 
     URL serverAddress; 

     while (isRunning) { 
      try { 

       MDatabaseManager databaseManager = methodToInitMYDBManager(); 
       PushConnection pushConnection = new PushConnection(); 
       pushConnection.setStatusCode(0); 
       //this is used to store the last connection attempt (time) 
       databaseManager.insertEntry(toContentValues(pushConnection), "pc"); 
       connection = null; 

       serverAddress = new URL(myURLforLongPolling); 
       connection = (HttpURLConnection) serverAddress.openConnection(); 
       connection.setRequestMethod("GET"); 
       connection.setDoOutput(false); 
       connection.setReadTimeout(100000); 
       connection.setRequestProperty("Connection", "Keep-Alive"); 
       connection.setRequestProperty("Content-Type", "application/json"); 
       connection.setRequestProperty("Charset", "UTF-8"); 
       connection.setRequestProperty("Content-Transfer-Encoding", "binary"); 
       connection.connect(); 
       long begin_time = System.currentTimeMillis(); 
       int resCode = connection.getResponseCode(); 

       if (resCode != 200) { 
        throw new IOException("Response Status Code not 200"); 
       } 

       parseInputstream(connection.getInputStream()); 
       long end_time = System.currentTimeMillis(); 
      } catch (Exception e) { 
       e.printStackTrace(); 
       isRunning = false; 
      } finally { 
        if (connection != null) { 
         connection.disconnect(); 
         connection = null; 
        } 
      } 
     } 
    } catch (Exception e) { 
     e.printStackTrace(); 
    } finally { 
     isRunning = false; 
    } 
} 

現在最有趣的部分。我有一個AlarmTimer調用另一個Runnable。 Runnable做了一些事情,最後通過調用這個方法來驗證連接。

Future connectionFuture; 

public void validatePushConnection() { 
     databaseManager = myMethodToInitTheDb(); 
     //here it will get the last sent push 
     PushConnection pushConnection = databaseManager.getLastPushConnectionFromDB(); 
     if (pushConnection != null) { 
      long secondsSinceLastPush = ((System.currentTimeMillis() - pushConnection.getLast_connection_attempt().getTime())/1000); 
      if ((secondsSinceLastPush >= 400 || secondsSinceLastPush == 0) && hasInternet()) { 
         Log.e("CommandManager", "Delay is larger then 400 or and internet is there. Reconnecting"); 
       mFactoryManager.getLPRunnable().isRunning = false; 
       connectionFuture = mFactoryManager.getPushThreadPoolExecutor().submit(mFactoryManager.getLPRunnable()); 
      } 
     } else { 
      Log.d("CommandManager", "No push yet. Waiting for the first push"); 
      if (connectionFuture == null || connectionFuture.isCancelled() || connectionFuture.isDone() || connectionFuture.get() == null) { 
       connectionFuture = mFactoryManager.getPushThreadPoolExecutor().submit(mFactoryMAnager.getLPRunnable()); 
      } 
     } 

    } catch (Exception e) { 
     e.printStackTrace(); 
    } 
} 

的問題是,該連接(可運行)的服務器似乎被創建多次(經過一段時間),並只應具有的可運行一個實例。什麼可能導致多重連接?

回答

2

即使使用​​,以下(兩次使用)模式也不是線程安全的。

private static A a; 

public static A getA() { 
    if (a == null) { 
     a = new A(); 
    } 
    return a; 
} 

正確的模式應該是:

private static class AHolder { // Ensures full initialisation of the class 
    private static A a; 
} 

public static A getA() { 
    if (AHolder.a == null) { 
     synchronized (A.class) { 
      // The first may have filled a 
      if (AHolder.a == null) { 
       AHolder.a = new A(); 
      } 
     } 
    } 
    return AHolder.a; 
} 

這兩個靜態字段。我自己我寧可不要使用static那麼多。


兩個lpRunnable和pushThreadPoolExecutor以上應該是單身(對象只存在一次)。

(如果包含這些字段類是單身,static可以在幾個斑點去除。)只有一個線程的推移,其他等待

現在synchronized (LPRunnable.class) {保證。在你的代碼中,它意味着如果已經不爲空,就不會發生「同步」,並且一切都很快。但是,如果仍然爲空,而線程在同步塊內(所謂的關鍵區域),則其他線程可能會停止在同步中。如果第一個線程離開同步塊(第一個創建了lpRunnable),則第二個線程進入同步塊,再次創建一個新的lpRunnable。

爲此我添加了一個第二if lpRunnable == null

  • 第一if是加速時lpRunnable被填充。
  • 第二個if檢查我們可能沒有做出等待,並且一個較早的線程創建了它。
+0

謝謝,但我完全不明白。你是什​​麼意思,它沒有任何意義?我認爲同步將確保它只被稱爲onec?訪問的正確方式是什麼,確保它只有一個實例而不將其加載到內存中(延遲加載)?我更喜歡在擴展中不初始化應用 –

+0

第一個線程來到if子句並開始創建單例實例。同時第二個線程來到相同的if子句,並且還開始創建單例,因爲第一個線程尚未完成創建它。因此,您需要鎖定/同步,並檢查進入同步塊之前和之後的情況。 – Matthias

+0

謝謝。我已經把它解決了,但是你能否在你的示例中解釋一下: 如果你說「不那麼安全」。什麼是同步用於?我認爲它會鎖定綁定到在同步塊中使用的類?是不是像你發佈的第二個例子一樣?由於Thread和Runnable也是以SingleTon方式加載的,因此不應該有兩個Runnable實例,不應該嗎?有沒有更好的方法做到這一點? –