Syncing String Objects in Java

I have a webapp that I am in the middle of testing load and performance, especially on a function where we expect several hundred users to access the same page and be updated every 10 seconds on that page. One area of ​​improvement we discovered with this feature was caching responses from the web service for a period of time, as the data did not change.

After implementing this basic caching in some subsequent tests, it turned out that I did not consider the possibility of simultaneously accessing the cache at the same time. I found that for ~ 100 ms, about 50 threads tried to retrieve the object from the cache, finding that it expired, hitting the web service to retrieve the data, and then returning the object to the cache.

The original code looked something like this:

private SomeData[] getSomeDataByEmail(WebServiceInterface service, String email) { final String key = "Data-" + email; 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; } 

So, to make sure that only one thread called the web service when the object in key expired, it seemed to me that I needed to synchronize the get / set Cache operation, and it seemed that using the cache key would be a good candidate for the object to synchronize (thus , a call to this method for e-mail b@b.com will not be blocked by calls to methods at a@a.com).

I updated the method so that it looks like this:

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

I also added log lines for things like “in front of the synchronization block”, “inside the synchronization block”, “about exiting the synchronization block” and “after the synchronization block”, so I could determine if I got / get synchronized efficiently operation.

However, it does not seem to have worked. My test logs have an output, for example:

(log output is 'thread_name' 'log name' 'message')
http-80-Processor253 jsp.view-page - getSomeDataForEmail: about to enter a synchronization block
http-80-Processor253 jsp.view-page - getSomeDataForEmail: internal synchronization block
http-80-Processor253 cache.StaticCache - get: object in key [SomeData-test@test.com] has expired

http-80-Processor253 cache.StaticCache - get: key [SomeData-test@test.com] return value [null]
http-80-Processor263 jsp.view-page - getSomeDataForEmail: about to enter a synchronization block
http-80-Processor263 jsp.view-page - getSomeDataForEmail: internal synchronization block
http-80-Processor263 cache.StaticCache - get: object with key [SomeData-test@test.com] has expired

http-80-Processor263 cache.StaticCache - get: key [SomeData-test@test.com] return value [null]
http-80-Processor131 jsp.view-page - getSomeDataForEmail: about to enter a synchronization block
http-80-Processor131 jsp.view-page - getSomeDataForEmail: internal synchronization block
http-80-Processor131 cache.StaticCache - get: object in key [SomeData-test@test.com] has expired

http-80-Processor131 cache.StaticCache - get: key [SomeData-test@test.com] returning value [null]
http-80-Processor104 jsp.view-page - getSomeDataForEmail: internal synchronization block
http-80-Processor104 cache.StaticCache - get: object with key [SomeData-test@test.com] has expired

http-80-Processor104 cache.StaticCache - get: key [SomeData-test@test.com] return value [null]
http-80-Processor252 jsp.view-page - getSomeDataForEmail: about to enter a synchronization block
http-80-Processor283 jsp.view-page - getSomeDataForEmail: about to enter a synchronization block
http-80-Processor2 jsp.view-page - getSomeDataForEmail: about to enter a synchronization block
http-80-Processor2 jsp.view-page - getSomeDataForEmail: internal synchronization block

I wanted to see only one thread at a time entering / leaving the synchronization block around get / set operations.

Is there a problem in synchronizing String objects? I thought the cache key would be a good choice, since it is unique to the operation, and although the final String key declared in this method, I thought that each thread would receive a link to the same object and therefore there would be synchronization on this separate object.

What am I doing wrong here?

Update : after looking in the logs, it looks like methods with the same synchronization logic, where the key is always the same, for example

 final String key = "blah"; ... synchronized(key) { ... 

the same concurrency problems are not displayed - only one thread at a time enters the block.

Update 2 : Thank you all for your help! I accepted the first answer about intern() ing Strings, which solved my original problem - when multiple threads entered synchronized blocks, where I thought they shouldn't, because the key value has the same value.

As others have pointed out, using intern() for such a purpose and synchronizing on these lines really turns out to be a bad idea - when running JMeter tests against webapp to simulate the expected load, I saw the used heap size grow to almost 1 GB in less than 20 minutes.

I am currently using a simple solution to just synchronize the whole method - but I really , for example, code samples provided by martinprobst and MBCook, but since I have about 7 similar getData() methods in this class at present (since it needs about 7 different pieces of data from the web service), I did not want to add almost duplicate logic about getting and releasing locks for each method. But this is definitely very, very valuable information for future use. I think that these are, ultimately, the correct answers to how best to perform an operation such as this thread-safe, and I would give more votes for these answers if I could!

+42
java multithreading synchronization thread-safety synchronized
Sep 25 '08 at 15:27
source share
19 answers

Without putting my brain in full force, from a quick scan of what you say, it looks like you need to put () your lines:

 final String firstkey = "Data-" + email; final String key = firstkey.intern(); 

Two lines with the same value otherwise are not necessarily the same object.

Note that this may lead to a new point of contention, as deep inside the intern () virtual machine, you might need to obtain a lock. I have no idea what modern virtual machines look like in this area, but I hope that they will be expertly optimized.

I assume that you know that StaticCache still needs thread safety. But the statement should be tiny compared to what you would have if you blocked the cache, and not just the key when calling getSomeDataForEmail.

Answer to the update question :

I think that since a string literal always gives the same object. Dave Costa notes in the commentary that this is even better than that: the literal always gives a canonical representation. Thus, all String literals with the same value anywhere in the program will have the same object.

Edit

Others noted that synchronizing on the internal lines is actually a really bad idea - partly because creating an old line is allowed to lead to infinity, and partly because if more than one bit of code at any point in your program is synchronized on the internal lines, you have dependencies between these bits of code and the prevention of deadlocks or other errors. [/ p>

Strategies to avoid this by storing the lock object on each line of the line are developed in other answers as you type.

There is an alternative here - it still uses a special lock, but we know that in any case we will need one of them for the cache, and you talked about 50 threads, not 5000, so that it would not be fatal. I also suggest that the performance bottleneck here is the slow I / O lock in DoSlowThing (), which therefore greatly benefits from non-serialization. If this is not a bottleneck, then:

  • If the processor is busy, this approach may not be enough, and you need a different approach.
  • If the processor is not busy, and access to the server is not a bottleneck, then this approach is excessive, and you can also forget about this lock and key lock, put a large synchronized one (StaticCache) throughout the operation, and this is easy to do.

Obviously, this approach needs to be tested to scale before use - I can’t guarantee anything.

This code does NOT require StaticCache to be synchronized or otherwise thread safe. This needs to be reviewed if any other code (such as a scheduled cleanup of old data) ever touches the cache.

IN_PROGRESS is a dummy value - not entirely clean, but the code is simple and it stores two hash tables. It does not handle InterruptedException because I do not know what your application wants to do in this case. In addition, if DoSlowThing () fails successively for a given key, this code in its current form is not entirely elegant, since each thread through it will repeat it. Since I do not know what the criteria for failure are, and whether they can be temporary or permanent, I can’t cope with this either, I just make sure that the threads are not blocked forever. In practice, you might want to put a data value in a cache that indicates "not available", possibly for a reason, and a timeout for retrying.

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

Every time something is added to the cache, all threads wake up and check the cache (no matter what key it executes), so you can get better performance with less controversial algorithms. However, most of this work will take place during your idle CPU lock time on I / O, so this may not be a problem.

This code can be distributed for use with multiple caches if you define suitable abstractions for the cache and its associated locks, the returned data, the IN_PROGRESS dummy, and the slow operation to execute. Moving all of this into a cache method might not be a bad idea.

+39
Sep 25 '08 at 15:30
source share

Syncing on the inner line may not be a good idea - by interning it, String becomes a global object, and if you synchronize the same interned string in different parts of the application, you can get really strange and mostly unsolvable synchronization problems, such as deadlocks . This may seem unlikely, but when that happens, you are really screwed. As a rule, only synchronization on a local object, where you are absolutely sure that no code outside your module can block it.

In your case, you can use a synchronized hash table to store lock objects for your keys.

eg:.

 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 } 

This code has a race condition where two threads can put an object in the lock table one after another. This, however, should not be a problem, because then you have another thread calling the web service and updating the cache, which should not be a problem.

If you canceled caching after a while, you should check if the data is still null after retrieving from the cache if blocking! = Null.

Alternatively, and much easier, you can synchronize the entire cache search method ("getSomeDataByEmail"). This will mean that all threads must synchronize when accessing the cache, which can be a performance issue. But, as always, first try this simple solution and see if this is really a problem! In many cases, this should not be, since you probably spend much more time processing the result than synchronizing.

+26
Sep 25 '08 at 15:53
source share

Lines are not good candidates for synchronization. If you need to synchronize with the line identifier, this can be done using the line to create the mutex (see " ID synchronization "). Regardless of whether the cost of this algorithm is worth it, it depends on whether your service refers to any significant I / O.

also:

  • Hope the StaticCache.get () and set () methods are thread safe.
  • String.intern () is expensive (one that varies between VM implementations) and should be used with care.
+11
Sep 25 '08 at 16:17
source share

Others have suggested interning strings and this will work.

The problem is that Java has to store interned strings. I was told that this happens even if you do not hold the link, because the value should be the same the next time someone uses this line. This means that interning all the lines can start to absorb memory, which with the load you are describing can be a big problem.

I saw two solutions to this:

You can sync to another object

Instead of email, create an object that contains email (say, a User object) that contains the email value as a variable. If you already have another object representing a person (let's say you have already extracted something from the database based on their email), you can use it. By implementing the equals method and hashcode method, you can make sure that Java considers the objects the same when you execute static cache.contains () to find out if there is data in the cache (you have to synchronize in the cache).).

In fact, you can leave a second card so that objects can be locked. Something like that:

 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 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. } 

This will prevent 15 downloads to the same email address at the same time. You need something to prevent too many entries from getting into the emailLocks card. Using LRUMap from Apache Commons will do this.

This will require some configuration, but it may solve your problem.

Use a different key

If you are ready to put up with possible errors (I don’t know how important this is), you can use the hash code of the string as a key. Ints should not be interned.

Summary

Hope this helps. Threads are fun, aren't they? You can also use a session to set a value that means "I'm already working on finding this" and check to see if the second (third, Nth) thread needs to try to create or just wait until the result appears in the cache. I think I had three sentences.

+5
Sep 25 '08 at 16:04
source share

You can use the 1.5 concurrency utilities to provide a cache designed to provide multiple concurrent access and one addition point (that is, only one thread that ever performs the creation of an "expensive object"):

  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 } 

Obviously, this does not apply to the exceptions you need, and the cache does not have eviction. Perhaps you could use it as a basis for modifying your StaticCache class.

+5
Sep 27 '08 at 17:26
source share

Use a decent caching structure like ehcache .

Implementing a good cache is not as simple as some people think.

Regarding the comment that String.intern () is the source of a memory leak, this is actually not the case. Interned Strings are garbage collection, this can take longer because on some JVMs (SUNs) they are stored in the Perm space, which concerns only the GC.

+4
Oct 08 '08 at 8:48
source share

Here is a safe short Java 8 solution that uses a map of dedicated lock objects for synchronization:

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

It has a drawback that keys and lock objects will be stored in the card forever.

This can be worked around like this:

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

But then the popular keys will be constantly inserted into the card with the redistribution of blocked objects.

Update : and this leaves the possibility of a race condition, when two threads simultaneously enter the synchronized section for the same key, but with different locks.

So it might be safer and more efficient to use an outdated Guava cache :

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

Please note that here it is assumed that StaticCache is thread-safe StaticCache and will not suffer from reading and writing to different keys at the same time.

+4
Nov 11 '17 at 22:06
source share

Call:

  final String key = "Data-" + email; 

creates a new object every time the method is called. Since this object is used to block, and each call to this method creates a new object, you do not actually synchronize access to the card based on the key.

This will once again explain your editing. When you have a static string, it will work.

Using intern () solves the problem because it returns a string from the internal pool stored in the String class, which ensures that if the two strings are equal, then the pool will be used. Cm

http://java.sun.com/j2se/1.4.2/docs/api/java/lang/String.html#intern ()

+2
Sep 25 '08 at 15:39
source share

Your main problem is not only that there may be multiple String instances with the same value. The main problem is that to access the StaticCache object you only need to have one monitor for synchronization. StaticCache ( ), , , .

+2
25 . '08 15:39
source share

, . , , , .

 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() .

, ,

  ValueLock<String> lock = new ValueLock<String>(); // ... share the lock String email = "..."; try { lock.lock(email); //... } finally { lock.unlock(email); } 
+2
11 '18 18:31
source share

, .

In this example:

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

. , API- get/put, , , get getIfAbsentPut . .

, , .

SynchronizedMap , . API (get put putIfAbsent), , . , putIfAbsent: put , ( put , put , ) (, , , , . ), , .

, , , . API , , , . " ", , - , , .

Future , Future . , , , API .

+1
22 . '13 19:13
source share

html-, ​​ x ?

0
25 . '08 15:59
source share

, .

 final String key = "Data-" + email; 

/ , , , "" ?

,

 final String key = email; 

.

0
25 . '08 19:06
source share

:

 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 } } } 
0
12 . '16 2:33
source share

, , :

 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); } } } } 
0
26 . '17 0:16
source share

, / , .

Java 8, Java 6 .

Java 8:

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

Java 6:

DynamicKeyLock Lock {private final static static ConcurrentHashMap locksMap = new ConcurrentHashMap(); T;

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

:

 public class DynamicKeyLockTest { @Test public void testDifferentKeysDontLock() throws InterruptedException { DynamicKeyLock<Object> lock = new DynamicKeyLock<>(new Object()); lock.lock(); AtomicBoolean anotherThreadWasExecuted = new AtomicBoolean(false); try { new Thread(() -> { DynamicKeyLock<Object> anotherLock = new DynamicKeyLock<>(new Object()); anotherLock.lock(); try { anotherThreadWasExecuted.set(true); } finally { anotherLock.unlock(); } }).start(); Thread.sleep(100); } finally { Assert.assertTrue(anotherThreadWasExecuted.get()); lock.unlock(); } } @Test public void testSameKeysLock() throws InterruptedException { Object key = new Object(); DynamicKeyLock<Object> lock = new DynamicKeyLock<>(key); lock.lock(); AtomicBoolean anotherThreadWasExecuted = new AtomicBoolean(false); try { new Thread(() -> { DynamicKeyLock<Object> anotherLock = new DynamicKeyLock<>(key); anotherLock.lock(); try { anotherThreadWasExecuted.set(true); } finally { anotherLock.unlock(); } }).start(); Thread.sleep(100); } finally { Assert.assertFalse(anotherThreadWasExecuted.get()); lock.unlock(); } } } 
0
27 '18 7:18
source share

- ( ):

 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' 
0
20 . '19 23:08
source share

2019,

JAVA , .

enter image description here

, .

Java .

, .

0
27 . '19 10:16
source share

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 } } 
-one
06 . '19 18:18
source share



All Articles