我正确使用同步块吗?

时间:2014-08-23 11:03:24

标签: java multithreading synchronization final swingworker

我有一个SwingWorker调用twitter API并返回一些结果,每次收到结果我首先更新成员变量_latestValidResult,然后从结果中获取推文列表并添加它到我的列表然后我发布该列表的大小。然后在发布中我需要使用_latestValidResult来访问某些API计时器限制,以便我可以更新我的GUI。

我在更新doInBackground()的{​​{1}}和我使用_latestValidResult process()的{​​{1}}中有一个同步块来获取计时器限制。

我在问我是否正确使用了synchronized块,因为我收到来自IDE的警告我正在同步非最终变量。

这是带有一些伪代码的准系统代码,所以它不会让你头疼:

_latestValidResult

以下是public class ProduceStatusWorker extends SwingWorker<Void, Integer> { private QueryResult _latestValidResult; @Override protected Void doInBackground() { QueryResult result = null; Set<Status> allStatuses = new HashSet<>(); do { try { if (isCancelled()) { return null; } result = making_api_call_here; // If the call succeeded (no exception) then copy the result to the _latestValidResult synchronized (_latestValidResult) { _latestValidResult = result; } allStatuses.addAll(result.getTweets()); publish(allStatuses.size()); } catch (TwitterException te) { // Handle exceptions here } } while (more_statuses_can_be_retrieved); return null; } @Override protected void process(List<Integer> chunks) { final int apiCallsTotal; final int apiCallsLeft; // Get the variables I need from _latestValidResult synchronized (_latestValidResult) { apiCallsTotal = _latestValidResult.getRateLimitStatus().getLimit(); apiCallsLeft = _latestValidResult.getRateLimitStatus().getRemaining(); } // Update GUI according to the variables jAPICallsLeftLabel.setText(Integer.toString(apiCallsLeft) + "/" + Integer.toString(apiCallsTotal)); // Update GUI according to the publish jTweetsInMemory.setText(Integer.toString(chunks.get(chunks.size() - 1))); } } 类的完整代码:

ProduceStatusWorker

4 个答案:

答案 0 :(得分:3)

您无法获得所需的效果。锁仅在所有参与者使用相同的一个锁时才有用。使用您的代码,您将尝试控制的一件事会有很多锁。相反,只要它是同一个锁,它用作锁的重要性并不重要。

执行此操作的一种简单方法是执行synchronized (this)。但是,推荐的方法是:

private final Object lock = new Object();
//
void method(){
    synchronized(lock){
        // stuff
    }
}

这样不相关的参与者就不会意外地使用你的锁(这个代码看起来很愚蠢,IMO是Java的设计缺陷)。

但是,在你的情况下,你可以这样做:

volatile QueryResult latestValidResult;

并完成它。这是因为QueryResult实际上是不可变的(从我可以猜到的),并且因为有多个读者但只有一个编写器,您只需要使其他线程可以看到最新的有效结果。在这种情况下,volatile就足够了,IMO更容易理解你想要做的事情。

答案 1 :(得分:2)

  

我在问我是否正确使用了synchronized块

简单回答:没有

synchronized (_latestValidResult) {

这不会锁定成员,而是锁定此成员引用的对象。 (实际上,它可能会在第一次抛出NullPointerException)

一步一步

假设_latestValidResult中的当前对象是 FirstResult

  1. doInBackground()

    synchronized (_latestValidResult) {
        _latestValidResult = result; //we're setting SecondResult
        //still in synchronized block
    
  2. 在这个确切的时间,当我们在同步块中时,另一个线程运行process()函数:

    synchronized (_latestValidResult) {
    

    这会锁定 SecondResult ,线程同时运行。我不会认为使用synchronized关键字

  3. 是您的期望

    使用锁

    使用特定的最终对象作为锁:

    private QueryResult _latestValidResult;
    private final Object _latestValidResultLock = new Object();
    
    //...
    
        synchronized (_latestValidResultLock ) {
            _latestValidResult = result;
        }
    

    也许你甚至不需要同步

    我没有深入研究你的代码,看看它是否可行,但也许你可以这样做:

    private volatile QueryResult _latestValidResult;
    
    //...
    
        _latestValidResult = result;
    
    //...
    
        QueryResult latestValidResult = _latestValidResult;
        apiCallsTotal = latestValidResult.getRateLimitStatus().getLimit();
        apiCallsLeft = latestValidResult.getRateLimitStatus().getRemaining();
    

答案 2 :(得分:1)

你应该做这样的事情,这样_latestValidResult就不会被修改,直到process函数读取它为止。使用最终对象作为锁将确保一次只有一个线程持有锁,并且正在读取或修改它。

private QueryResult _latestValidResult;
private final Object lock = new Object();

protected void doInBackground(){

    synchronized(lock){
        _latestValidResult = result;
    }
}

protected void process(List<Integer> chunks){
    synchronized(lock){
         apiCallsTotal = _latestValidResult.getRateLimitStatus().getLimit();
         apiCallsLeft = _latestValidResult.getRateLimitStatus().getRemaining();
    }
}

答案 3 :(得分:0)

不要同步您正在修改的变量。而是使用Lock或单独的锁定变量进行同步(您甚至可以将其设置为静态最终以防止IDE抱怨)。