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

在我的Logcat中,我在同一秒内运行1天后进行了几次连接尝试。

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


问题在于,与服务器的连接(可运行)似乎已创建多次(一段时间后),并且应该仅具有一个可运行实例。是什么引起多重连接?

最佳答案

即使使用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,则不会发生“同步”,并且一切都很快。但是,如果仍然为null,则当线程在同步块内(所谓的关键区域)时,其他线程可能会在同步块处停止。如果第一个线程离开了同步块(创建了第一个lpRunnable),则第二个线程进入了同步块,再次创建一个新的lpRunnable。

因此,我添加了第二个if lpRunnable == null


填充if时,第一个lpRunnable是加速。
第二个if检查我们是否不必等待,并且先前的线程已创建它。

关于java - 让一次Runnable或Thread只调用一次,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/24228778/

10-10 18:58