答案 0 :(得分:38)
没有完全放下我的大脑,从快速扫描你所说的内容看起来好像你需要实习()你的字符串:
final String firstkey = "Data-" + email;
final String key = firstkey.intern();
具有相同值的两个字符串不一定是同一个对象。
请注意,这可能会引入新的争用点,因为在VM的深处,intern()可能必须获取锁。我不知道现代虚拟机在这个领域是什么样的,但人们希望它们能够进行极端优化。
我假设您知道StaticCache仍然需要是线程安全的。但是,如果你在调用getSomeDataForEmail时锁定缓存而不仅仅是密钥,那么那里的争论应该是微不足道的。
对问题更新的回复:
我认为这是因为字符串文字总是产生相同的对象。戴夫·科斯塔在评论中指出它甚至比这更好:一个文字总是产生规范表示。因此,程序中任何位置具有相同值的所有String文字都将产生相同的对象。
修改强>
其他人已经指出同步实习生字符串实际上是一个非常糟糕的主意 - 部分原因是因为创建实习生字符串是允许它们永久存在的,部分是因为如果不止一位程序中任何地方的代码在实习字符串上同步,你在这些代码位之间存在依赖关系,防止死锁或其他错误可能是不可能的。
在我键入的其他答案中,正在开发通过为每个键字符串存储锁定对象来避免这种情况的策略。
这是一个替代方案 - 它仍然使用单一锁,但我们知道无论如何我们将需要其中一个用于缓存,而你谈论的是50个线程,而不是5000个,所以这可能不是致命的。我还假设这里的性能瓶颈是在DoSlowThing()中阻塞I / O很慢,因此不会被序列化。如果这不是瓶颈,那么:
显然,这种方法需要在使用前进行可靠性测试 - 我保证不会。
此代码不要求StaticCache同步或以其他方式线程安全。如果任何其他代码(例如预定的旧数据清理)触及缓存,则需要重新访问。
IN_PROGRESS是一个虚拟值 - 不完全干净,但代码很简单,它节省了两个哈希表。它不处理InterruptedException,因为在这种情况下我不知道你的应用程序想要做什么。此外,如果DoSlowThing()对于给定键始终失败,则此代码不是很完美,因为每个线程都将重试它。由于我不知道失败的标准是什么,以及它们是否可能是临时的或永久性的,我也不会处理这个问题,我只是确保线程不会永远阻塞。在实践中,您可能希望在缓存中放置一个数据值,表示“不可用”,可能有原因,以及何时重试超时。
// do not attempt double-check locking here. I mean it.
synchronized(StaticObject) {
data = StaticCache.get(key);
while (data == IN_PROGRESS) {
// another thread is getting the data
StaticObject.wait();
data = StaticCache.get(key);
}
if (data == null) {
// we must get the data
StaticCache.put(key, IN_PROGRESS, TIME_MAX_VALUE);
}
}
if (data == null) {
// we must get the data
try {
data = server.DoSlowThing(key);
} finally {
synchronized(StaticObject) {
// WARNING: failure here is fatal, and must be allowed to terminate
// the app or else waiters will be left forever. Choose a suitable
// collection type in which replacing the value for a key is guaranteed.
StaticCache.put(key, data, CURRENT_TIME);
StaticObject.notifyAll();
}
}
}
每次向缓存添加任何内容时,所有线程都会唤醒并检查缓存(无论它们在哪个密钥之后),因此可以通过较少争用的算法获得更好的性能。但是,大部分工作将在I / O上大量空闲CPU时间阻塞期间进行,因此可能不是问题。
如果为缓存及其关联的锁定定义合适的抽象,返回的数据,IN_PROGRESS虚拟以及执行速度慢的操作,则此代码可以与多个缓存一起使用。将整个事物整理到缓存中的方法可能不是一个坏主意。
答案 1 :(得分:25)
在intern'd String上进行同步可能根本不是一个好主意 - 通过实习,String变成一个全局对象,如果你在应用程序的不同部分同步interned字符串,你可能会得到真的很奇怪,基本上是不可解决的同步问题,比如死锁。这似乎不太可能,但是当它发生时,你真的被搞砸了。作为一般规则,只能在本地对象上进行同步,您绝对可以确定模块外部的代码不会锁定它。
在您的情况下,您可以使用同步哈希表来存储密钥的锁定对象。
E.g:
Object data = StaticCache.get(key, ...);
if (data == null) {
Object lock = lockTable.get(key);
if (lock == null) {
// we're the only one looking for this
lock = new Object();
synchronized(lock) {
lockTable.put(key, lock);
// get stuff
lockTable.remove(key);
}
} else {
synchronized(lock) {
// just to wait for the updater
}
data = StaticCache.get(key);
}
} else {
// use from cache
}
此代码具有竞争条件,其中两个线程可能会将对象彼此放入锁定表中。这应该不是问题,因为那时你只有一个线程调用webservice并更新缓存,这应该不是问题。
如果您在一段时间后使缓存失效,则应在从锁定!= null情况下从缓存中检索数据后再检查数据是否为空。
或者,更容易,您可以使整个缓存查找方法(“getSomeDataByEmail”)同步。这意味着所有线程在访问缓存时都必须进行同步,这可能是性能问题。但是一如既往,首先尝试这个简单的解决方案,看看它是否真的是一个问题!在许多情况下它不应该是,因为你可能花费更多的时间处理结果而不是同步。
答案 2 :(得分:9)
字符串不是同步的良好候选者。如果必须在String ID上进行同步,则可以使用该字符串来创建互斥锁(请参阅“synchronizing on an ID”)。该算法的成本是否值得,取决于调用您的服务是否涉及任何重要的I / O.
此外:
答案 3 :(得分:5)
其他人建议实习字符串,这将有效。
问题是Java必须保持内部字符串。有人告诉我,即使你没有持有引用也会这样做,因为下次有人使用该字符串时,该值必须相同。这意味着实习所有字符串可能会开始占用内存,而你所描述的负载可能是一个很大的问题。
我已经看到了两个解决方案:
您可以在另一个对象上进行同步
创建一个保存电子邮件(例如User对象)的对象,而不是电子邮件,该对象将电子邮件的值保存为变量。如果您已经有另一个代表该人的对象(比如您已根据他们的电子邮件从数据库中提取了某些内容),则可以使用该对象。通过实现equals方法和hashcode方法,可以确保Java在执行静态cache.contains()时认为对象是相同的,以确定数据是否已经在缓存中(您必须在缓存上进行同步) )。
实际上,您可以为要锁定的对象保留第二个Map。像这样:
Map<String, Object> emailLocks = new HashMap<String, Object>();
Object lock = null;
synchronized (emailLocks) {
lock = emailLocks.get(emailAddress);
if (lock == null) {
lock = new Object();
emailLocks.put(emailAddress, lock);
}
}
synchronized (lock) {
// See if this email is in the cache
// If so, serve that
// If not, generate the data
// Since each of this person's threads synchronizes on this, they won't run
// over eachother. Since this lock is only for this person, it won't effect
// other people. The other synchronized block (on emailLocks) is small enough
// it shouldn't cause a performance problem.
}
这样可以防止同一个电子邮件地址中的15次提取。您需要一些东西来阻止太多条目在emailLocks地图中结束。使用Apache Commons的LRUMap可以做到这一点。
这需要一些调整,但它可以解决您的问题。
使用其他密钥
如果您愿意忍受可能的错误(我不知道这有多重要),您可以使用String的哈希码作为键。整理不需要实习。
<强>摘要强>
我希望这会有所帮助。线程很有趣,不是吗?您还可以使用会话来设置一个值,意思是“我已经在寻找这个”并检查是否需要尝试创建第二个(第三个,第N个)线程或者只是等待结果显示在缓存中。我想我有三个建议。
答案 4 :(得分:5)
您可以使用1.5并发实用程序来提供旨在允许多个并发访问的缓存,以及单个添加点(即只有一个线程执行昂贵的对象“创建”):
private ConcurrentMap<String, Future<SomeData[]> cache;
private SomeData[] getSomeDataByEmail(final WebServiceInterface service, final String email) throws Exception {
final String key = "Data-" + email;
Callable<SomeData[]> call = new Callable<SomeData[]>() {
public SomeData[] call() {
return service.getSomeDataForEmail(email);
}
}
FutureTask<SomeData[]> ft; ;
Future<SomeData[]> f = cache.putIfAbsent(key, ft= new FutureTask<SomeData[]>(call)); //atomic
if (f == null) { //this means that the cache had no mapping for the key
f = ft;
ft.run();
}
return f.get(); //wait on the result being available if it is being calculated in another thread
}
显然,这并不像你想要的那样处理异常,并且缓存没有内置的驱逐。也许你可以用它作为改变你的StaticCache类的基础。
答案 5 :(得分:3)
这是一个安全的短Java 8解决方案,它使用专用锁对象的映射进行同步:
async
它有一个缺点,即键和锁定对象将永远保留在地图中。
这可以解决这个问题:
private static final Map<String, Object> keyLocks = new ConcurrentHashMap<>();
private SomeData[] getSomeDataByEmail(WebServiceInterface service, String email) {
final String key = "Data-" + email;
synchronized (keyLocks.computeIfAbsent(key, k -> new Object())) {
SomeData[] data = StaticCache.get(key);
if (data == null) {
data = service.getSomeDataForEmail(email);
StaticCache.set(key, data);
}
}
return data;
}
但随后流行的密钥将不断重新插入地图中,并重新分配锁定对象。
更新:当两个线程同时进入同一个部分但同一个密钥但具有不同的锁时,这会留下竞争条件。
因此使用expiring Guava Cache可能更安全有效:
private SomeData[] getSomeDataByEmail(WebServiceInterface service, String email) {
final String key = "Data-" + email;
synchronized (keyLocks.computeIfAbsent(key, k -> new Object())) {
try {
SomeData[] data = StaticCache.get(key);
if (data == null) {
data = service.getSomeDataForEmail(email);
StaticCache.set(key, data);
}
} finally {
keyLocks.remove(key); // vulnerable to race-conditions
}
}
return data;
}
请注意,这里假设private static final LoadingCache<String, Object> keyLocks = CacheBuilder.newBuilder()
.expireAfterAccess(10, TimeUnit.MINUTES) // max lock time ever expected
.build(CacheLoader.from(Object::new));
private SomeData[] getSomeDataByEmail(WebServiceInterface service, String email) {
final String key = "Data-" + email;
synchronized (keyLocks.getUnchecked(key)) {
SomeData[] data = StaticCache.get(key);
if (data == null) {
data = service.getSomeDataForEmail(email);
StaticCache.set(key, data);
}
}
return data;
}
是线程安全的,并且不会受到不同密钥的并发读写操作的影响。
答案 6 :(得分:3)
使用合适的缓存框架,例如ehcache。
实现好的缓存并不像有些人认为的那么容易。
关于String.intern()是内存泄漏源的注释,实际上并非如此。 Interned Strings 垃圾收集,它可能需要更长的时间,因为在某些JVM'S(SUN)上它们存储在Perm空间中,只有完整的GC触及它。
答案 7 :(得分:2)
电话:
final String key = "Data-" + email;
每次调用方法时,都会创建一个新对象。因为该对象是您用来锁定的对象,并且每次调用此方法都会创建一个新对象,所以您实际上并不是基于该键同步对地图的访问。
这进一步说明了您的编辑。如果你有一个静态字符串,那么它将起作用。
使用intern()解决了这个问题,因为它从String类保存的内部池中返回字符串,这确保了如果两个字符串相等,则将使用池中的字符串。参见
http://java.sun.com/j2se/1.4.2/docs/api/java/lang/String.html#intern()
答案 8 :(得分:2)
您的主要问题不仅仅是可能存在多个具有相同值的String实例。主要问题是您只需要一个用于同步的监视器来访问StaticCache对象。否则,多个线程可能最终同时修改StaticCache(尽管在不同的密钥下),这很可能不支持并发修改。
答案 9 :(得分:2)
这个问题在我看来有点过于宽泛,因此它煽动了同样广泛的答案。因此,我将尝试回复the question我已被重定向,不幸的是,其中一个已被关闭。
public class ValueLock<T> {
private Lock lock = new ReentrantLock();
private Map<T, Condition> conditions = new HashMap<T, Condition>();
public void lock(T t){
lock.lock();
try {
while (conditions.containsKey(t)){
conditions.get(t).awaitUninterruptibly();
}
conditions.put(t, lock.newCondition());
} finally {
lock.unlock();
}
}
public void unlock(T t){
lock.lock();
try {
Condition condition = conditions.get(t);
if (condition == null)
throw new IllegalStateException();// possibly an attempt to release what wasn't acquired
conditions.remove(t);
condition.signalAll();
} finally {
lock.unlock();
}
}
在(外部)lock
操作时,获取(内部)锁以在短时间内获得对地图的独占访问权,并且如果对应的对象已经在地图中,则当前线程将等待,
否则它会将新的Condition
放到地图上,释放(内部)锁并继续,
并且(外部)锁被认为是获得的。
(外部)unlock
操作,首先获取(内部)锁定,将在Condition
上发出信号,然后从地图中删除该对象。
该类不使用Map
的并发版本,因为对它的每次访问都受到单个(内部)锁的保护。
请注意,此类的lock()
方法的语义与ReentrantLock.lock()
的语义不同,重复的lock()
调用没有配对unlock()
将无限期挂起当前线程。< / p>
可能适用于该情况的使用示例,OP描述
ValueLock<String> lock = new ValueLock<String>();
// ... share the lock
String email = "...";
try {
lock.lock(email);
//...
} finally {
lock.unlock(email);
}
答案 10 :(得分:1)
这已经相当晚了,但这里提供了很多不正确的代码。
在这个例子中:
private SomeData[] getSomeDataByEmail(WebServiceInterface service, String email) {
SomeData[] data = null;
final String key = "Data-" + email;
synchronized(key) {
data =(SomeData[]) StaticCache.get(key);
if (data == null) {
data = service.getSomeDataForEmail(email);
StaticCache.set(key, data, CACHE_TIME);
}
else {
logger.debug("getSomeDataForEmail: using cached object");
}
}
return data;
}
同步的范围不正确。对于支持get / put API的静态缓存,至少应该围绕get和getIfAbsentPut类型操作进行同步,以便安全访问缓存。同步范围将是缓存本身。
如果必须对数据元素本身进行更新,则会增加一个额外的同步层,该层应该在各个数据元素上。
可以使用SynchronizedMap代替显式同步,但仍必须注意。如果使用了错误的API(get和put而不是putIfAbsent),那么尽管使用了synchronized映射,但操作将没有必要的同步。注意使用putIfAbsent引入的复杂性:要么,即使在不需要的情况下也必须计算put值(因为put不知道在检查缓存内容之前是否需要put值),或者需要小心使用委托(例如,使用Future,它有效,但有些不匹配;见下文),如果需要,可以根据需要获得put值。
使用期货是可能的,但似乎相当尴尬,也许有点过度工程。 Future API是异步操作的核心,特别是对于可能无法立即完成的操作。涉及未来很可能会增加一层线程创建 - 额外可能是不必要的复杂性。
将Future用于此类操作的主要问题是Future在多线程中具有内在联系。在没有必要使用新线程时使用Future意味着忽略了Future的许多机制,使其成为过度使用的API。
答案 11 :(得分:0)
以其他方式同步字符串对象:
String cacheKey = ...;
Object obj = cache.get(cacheKey)
if(obj==null){
synchronized (Integer.valueOf(Math.abs(cacheKey.hashCode()) % 127)){
obj = cache.get(cacheKey)
if(obj==null){
//some cal obtain obj value,and put into cache
}
}
}
答案 12 :(得分:0)
如果你不需要,我还建议完全摆脱字符串连接。
final String key = "Data-" + email;
缓存中是否有其他东西/类型的对象使用您需要在密钥开头添加额外“数据 - ”的电子邮件地址?
如果没有,我只是做那个
final String key = email;
并且你也避免了所有额外的字符串创建。
答案 13 :(得分:0)
如果其他人遇到类似的问题,就我所知,以下代码有效:
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
public class KeySynchronizer<T> {
private Map<T, CounterLock> locks = new ConcurrentHashMap<>();
public <U> U synchronize(T key, Supplier<U> supplier) {
CounterLock lock = locks.compute(key, (k, v) ->
v == null ? new CounterLock() : v.increment());
synchronized (lock) {
try {
return supplier.get();
} finally {
if (lock.decrement() == 0) {
// Only removes if key still points to the same value,
// to avoid issue described below.
locks.remove(key, lock);
}
}
}
}
private static final class CounterLock {
private AtomicInteger remaining = new AtomicInteger(1);
private CounterLock increment() {
// Returning a new CounterLock object if remaining = 0 to ensure that
// the lock is not removed in step 5 of the following execution sequence:
// 1) Thread 1 obtains a new CounterLock object from locks.compute (after evaluating "v == null" to true)
// 2) Thread 2 evaluates "v == null" to false in locks.compute
// 3) Thread 1 calls lock.decrement() which sets remaining = 0
// 4) Thread 2 calls v.increment() in locks.compute
// 5) Thread 1 calls locks.remove(key, lock)
return remaining.getAndIncrement() == 0 ? new CounterLock() : this;
}
private int decrement() {
return remaining.decrementAndGet();
}
}
}
在OP的情况下,它将像这样使用:
private KeySynchronizer<String> keySynchronizer = new KeySynchronizer<>();
private SomeData[] getSomeDataByEmail(WebServiceInterface service, String email) {
String key = "Data-" + email;
return keySynchronizer.synchronize(key, () -> {
SomeData[] existing = (SomeData[]) StaticCache.get(key);
if (existing == null) {
SomeData[] data = service.getSomeDataForEmail(email);
StaticCache.set(key, data, CACHE_TIME);
return data;
}
logger.debug("getSomeDataForEmail: using cached object");
return existing;
});
}
如果从同步代码中返回任何内容,则可以像这样编写同步方法:
public void synchronize(T key, Runnable runnable) {
CounterLock lock = locks.compute(key, (k, v) ->
v == null ? new CounterLock() : v.increment());
synchronized (lock) {
try {
runnable.run();
} finally {
if (lock.decrement() == 0) {
// Only removes if key still points to the same value,
// to avoid issue described below.
locks.remove(key, lock);
}
}
}
}
答案 14 :(得分:0)
为什么不渲染一个静态的html页面,该页面被提供给用户并每隔x分钟重新生成一次?
答案 15 :(得分:0)
我添加了一个小锁类,可以锁定/同步任何键,包括字符串。
请参阅Java 8,Java 6的实现和一个小测试。
Java 8:
docker inspect $(docker ps -q) --format '{{.Config.User}} {{.Name}}'
Java 6:
公共类DynamicKeyLock实现Lock { private final static ConcurrentHashMap locksMap = new ConcurrentHashMap(); 私人最终T键;
public class DynamicKeyLock<T> implements Lock
{
private final static ConcurrentHashMap<Object, LockAndCounter> locksMap = new ConcurrentHashMap<>();
private final T key;
public DynamicKeyLock(T lockKey)
{
this.key = lockKey;
}
private static class LockAndCounter
{
private final Lock lock = new ReentrantLock();
private final AtomicInteger counter = new AtomicInteger(0);
}
private LockAndCounter getLock()
{
return locksMap.compute(key, (key, lockAndCounterInner) ->
{
if (lockAndCounterInner == null) {
lockAndCounterInner = new LockAndCounter();
}
lockAndCounterInner.counter.incrementAndGet();
return lockAndCounterInner;
});
}
private void cleanupLock(LockAndCounter lockAndCounterOuter)
{
if (lockAndCounterOuter.counter.decrementAndGet() == 0)
{
locksMap.compute(key, (key, lockAndCounterInner) ->
{
if (lockAndCounterInner == null || lockAndCounterInner.counter.get() == 0) {
return null;
}
return lockAndCounterInner;
});
}
}
@Override
public void lock()
{
LockAndCounter lockAndCounter = getLock();
lockAndCounter.lock.lock();
}
@Override
public void unlock()
{
LockAndCounter lockAndCounter = locksMap.get(key);
lockAndCounter.lock.unlock();
cleanupLock(lockAndCounter);
}
@Override
public void lockInterruptibly() throws InterruptedException
{
LockAndCounter lockAndCounter = getLock();
try
{
lockAndCounter.lock.lockInterruptibly();
}
catch (InterruptedException e)
{
cleanupLock(lockAndCounter);
throw e;
}
}
@Override
public boolean tryLock()
{
LockAndCounter lockAndCounter = getLock();
boolean acquired = lockAndCounter.lock.tryLock();
if (!acquired)
{
cleanupLock(lockAndCounter);
}
return acquired;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException
{
LockAndCounter lockAndCounter = getLock();
boolean acquired;
try
{
acquired = lockAndCounter.lock.tryLock(time, unit);
}
catch (InterruptedException e)
{
cleanupLock(lockAndCounter);
throw e;
}
if (!acquired)
{
cleanupLock(lockAndCounter);
}
return acquired;
}
@Override
public Condition newCondition()
{
LockAndCounter lockAndCounter = locksMap.get(key);
return lockAndCounter.lock.newCondition();
}
}
测试:
public DynamicKeyLock(T lockKey) {
this.key = lockKey;
}
private static class LockAndCounter {
private final Lock lock = new ReentrantLock();
private final AtomicInteger counter = new AtomicInteger(0);
}
private LockAndCounter getLock()
{
while (true) // Try to init lock
{
LockAndCounter lockAndCounter = locksMap.get(key);
if (lockAndCounter == null)
{
LockAndCounter newLock = new LockAndCounter();
lockAndCounter = locksMap.putIfAbsent(key, newLock);
if (lockAndCounter == null)
{
lockAndCounter = newLock;
}
}
lockAndCounter.counter.incrementAndGet();
synchronized (lockAndCounter)
{
LockAndCounter lastLockAndCounter = locksMap.get(key);
if (lockAndCounter == lastLockAndCounter)
{
return lockAndCounter;
}
// else some other thread beat us to it, thus try again.
}
}
}
private void cleanupLock(LockAndCounter lockAndCounter)
{
if (lockAndCounter.counter.decrementAndGet() == 0)
{
synchronized (lockAndCounter)
{
if (lockAndCounter.counter.get() == 0)
{
locksMap.remove(key);
}
}
}
}
@Override
public void lock()
{
LockAndCounter lockAndCounter = getLock();
lockAndCounter.lock.lock();
}
@Override
public void unlock()
{
LockAndCounter lockAndCounter = locksMap.get(key);
lockAndCounter.lock.unlock();
cleanupLock(lockAndCounter);
}
@Override
public void lockInterruptibly() throws InterruptedException
{
LockAndCounter lockAndCounter = getLock();
try
{
lockAndCounter.lock.lockInterruptibly();
}
catch (InterruptedException e)
{
cleanupLock(lockAndCounter);
throw e;
}
}
@Override
public boolean tryLock()
{
LockAndCounter lockAndCounter = getLock();
boolean acquired = lockAndCounter.lock.tryLock();
if (!acquired)
{
cleanupLock(lockAndCounter);
}
return acquired;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException
{
LockAndCounter lockAndCounter = getLock();
boolean acquired;
try
{
acquired = lockAndCounter.lock.tryLock(time, unit);
}
catch (InterruptedException e)
{
cleanupLock(lockAndCounter);
throw e;
}
if (!acquired)
{
cleanupLock(lockAndCounter);
}
return acquired;
}
@Override
public Condition newCondition()
{
LockAndCounter lockAndCounter = locksMap.get(key);
return lockAndCounter.lock.newCondition();
}
}
答案 16 :(得分:0)
在您的情况下,您可以使用类似的方式(这样不会泄漏任何内存):
private Synchronizer<String> synchronizer = new Synchronizer();
private SomeData[] getSomeDataByEmail(WebServiceInterface service, String email) {
String key = "Data-" + email;
return synchronizer.synchronizeOn(key, () -> {
SomeData[] data = (SomeData[]) StaticCache.get(key);
if (data == null) {
data = service.getSomeDataForEmail(email);
StaticCache.set(key, data, CACHE_TIME);
} else {
logger.debug("getSomeDataForEmail: using cached object");
}
return data;
});
}
要使用它,您只需添加一个依赖项:
compile 'com.github.matejtymes:javafixes:1.3.0'
答案 17 :(得分:0)
最新更新2019
如果您正在寻找在JAVA中实现同步的新方法,那么此答案很适合您。
我发现Anatoliy Korovin撰写的这个很棒的博客将帮助您深刻理解同步化的内容。
How to Synchronize Blocks by the Value of the Object in Java。
这帮助我希望新的开发人员也能找到有用的东西。
答案 18 :(得分:0)
从Java 8开始,您可以使用computeIfAbsent
中的ConcurrentHashMap
来按名称创建锁。它只需要几行代码:
final ConcurrentHashMap<String, ReentrantLock> locks = new ConcurrentHashMap<>();
final ReentrantLock lock = locks.computeIfAbsent(lockName, (name) -> new ReentrantLock());
lock.lock();
try {
// Do something.
} finally {
lock.unlock();
}
如果指定的锁不存在,它将由computeIfAbsent
自动创建。
答案 19 :(得分:0)
使用带有同步功能的短期对象时应格外小心。每个Java对象都有一个附加的监视器,并且默认情况下此监视器是放气的。但是,如果有2个线程争夺显示器,则显示器会膨胀。如果该对象可以长期使用,那么这不是问题。但是,如果对象的寿命很短,则清理此膨胀的监控器可能会严重影响GC时间(因此,等待时间较长且吞吐量降低)。而且由于不总是列出GC时间,因此甚至很难发现。
如果确实要同步,则可以使用java.util.concurrent.Lock。或使用手工制作的带状锁,然后将字符串的哈希值用作该带状锁的索引。您可以一直使用这种条纹锁,这样就不会出现GC问题。
是这样的:
static final Object[] locks = newLockArray();
Object lock = locks[hashToIndex(key.hashcode(),locks.length];
synchronized(lock){
....
}
int hashToIndex(int hash, int length) {
if (hash == Integer.MIN_VALUE return 0;
return abs(hash) % length;
}
答案 20 :(得分:-1)
如果可以合理保证字符串值在整个系统中是唯一的,则可以安全地使用String.intern进行同步。 UUIDS是解决此问题的好方法。您可以通过缓存,映射将UUID与实际的字符串键相关联,甚至可以将uuid作为字段存储在实体对象上。
@Service
public class MySyncService{
public Map<String, String> lockMap=new HashMap<String, String>();
public void syncMethod(String email) {
String lock = lockMap.get(email);
if(lock==null) {
lock = UUID.randomUUID().toString();
lockMap.put(email, lock);
}
synchronized(lock.intern()) {
//do your sync code here
}
}