2017-06-22 55 views
1

通常我会锁定类似下面的关键部分。使用密钥的同步访问的代码块

public class Cache { 
    private Object lockObject = new Object(); 

    public Object getFromCache(String key) { 
     synchronized(lockObject) { 

      if (cache.containsKey(key)) { 
       // key found in cache - return cache value 
      } 
      else { 
       // retrieve cache value from source, cache it and return 
      } 
     } 
    } 
} 

的想法是我避免出现竞争状况可能导致数据源被击中多次与键被添加到缓存中多次。

现在如果两个线程在大约相同的时间不同缓存键进来,我仍然会阻碍之一。

假设键独一无二的 - 将锁仍然锁定在重点工作?

我认为它不会工作,因为我知道对象引用应该是相同的锁才能生效。我想这归结于它如何检查平等。

public class Cache { 

    public Object getFromCache(String key) { 
     synchronized(key) { 

      if (cache.containsKey(key)) { 
       // key found in cache - return cache value 
      } 
      else { 
       // retrieve cache value from source, cache it and return 
      } 
     } 
    } 
} 
+0

号你需要保护的资源的缓存,而不是重点。你的建议没有意义。 – EJP

+1

通常,密钥会散列到锁对象的数组中以避免实例问题。这被称为条带化,其中条带(资源)通过该锁保护免受任何突变。在你的缓存的例子中,这被称为memoization来避免[缓存踩踏](https://en.wikipedia.org/wiki/Cache_stampede),通过锁定内部散列表项来完成。为了避免锁定读取,这将使用乐观的获取和回退(双重检查锁定)。 [咖啡因](https://github.com/ben-manes/caffeine)和其他缓存实现这种技术。 –

+0

谢谢Ben,你的解释很有用。我将介绍在密钥上散列的锁数组。 – Avner

回答

-1

每个对象都有一个同步工作的隐式监视器。字符串对象可能在堆中创建,并且对于同一组字符(如果使用new创建的)或者可能来自池,也可能不同。只有在同一个对象上进行同步时,两个线程才会使用同步块访问关键部分。

同步上字符串文字实在是一个糟糕的主意。来自池的字符串文字是共享的。试想一下,如果在代码的两个不同部分中,您有两个同步部分,并且您在两个String引用上同步,但是使用同一组字符来初始化字符串,如果使用了来自池的String,则两个位置都将是同一个对象。尽管这两个地方可能有不同的业务环境,但您的应用程序可能会被挂起。调试也很困难。

对于特定的意志为目的的问题,如果同步上键来完成解决。

你要避免两个线程试图不读缓存的最新值来写。每个条目您都有不同的密钥。假设一个线程1和线程2想要访问相同的密钥,那么同一个密钥对象上的同步将阻止它们进入同步的块。同时,如果一个thread3想要访问另一个不同的密钥,那么它可以很好地实现。在这里,我们看到,与单个通用对象相比,所有密钥的读写操作都更快。到目前为止这么好,但如果你要保存一个数组或其他类似的非线程安全数据结构来存储缓存值,问题就会出现。同时写入(对于两个或更多不同的键)可能会导致一个写操作被另一个写操作在相同索引处覆盖。

因此,它取决于缓存数据结构的实现方式,以便如何在多线程环境中更好地进行读写准备。

+0

不知道为什么这是downvoted,但我认为它证实了我的期望,该实例必须是相同的。 – Avner

+1

@awi我低估了它,因为它没有解决这个问题,这是我在我的评论中提到的。你会错误地认为这是你实际询问的正确答案。 – EJP

+0

@avvi从某种程度上说,我自己同意EJP的观点,在修改之后,我发现我之前错过了几件事情。我编辑了答案,并尝试先前触摸错过的区域。 –