让Runnable或Thread只调用一次

时间:2014-06-15 10:39:01

标签: java android multithreading sockets runnable

我是一个由后台服务调用的Runnable。 Runnable本身在我的FactoryManagerClass中初始化为SingleTon-Object。

在我的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;
}

My Runnable Class(几乎没有截断)

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();
    } 
}

问题是,服务器的连接(Runnable)似乎多次创建(一段时间后),并且应该只有一个runnable实例。什么可能导致多重连接?

1 个答案:

答案 0 :(得分:2)

即使使用synchronized,以下(两次使用)模式也不是那么线程安全。

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) {保证只有一个线程通过,另一个线程等待。在你的代码中,它意味着如果已经不为空,则不会发生“同步”并且一切都很快。但是如果仍然为null,则在线程处于同步块内(所谓的关键区域),其他线程可能会停止在synchronized,如果第一个线程离开同步块(首先创建lpRunnable),第二个线程进入synchronized块,再次创建一个新的lpRunnable。

出于这个原因,我添加了第二个if lpRunnable == null

  • 第一个if是填充lpRunnable时的加速。
  • 第二个if检查我们是否可能没有等待,而早期的线程创建了它。