Fine-grained synchronization to serve a file
Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
2
down vote
favorite
I'm writing a proxy server that serves cached resources. If resource is not cached, it's retrieved and put into filesystem. Server is multi-threaded and I want to avoid any kind of global locking. If there are several requests for the same resource that is not cached at the same time, one request should retrieve resource, put it into filesystem and other requests should be blocked and then serve cached resource.
My first approach was to synchronize on interned string:
void serveFile(Path file)
if (!Files.exists(file))
synchronized (file.toString().intern())
if (!Files.exists(file))
retrieveResource(file);
serveResource(file);
But there are many discussions about why this approach is not the best, so I rewrote is as follows:
private final ConcurrentMap<Path, Object> downloadLocks = new ConcurrentHashMap<>();
void serveFile(Path file)
if (!Files.exists(file))
Object lock = downloadLocks.get(file);
if (lock == null)
Object newLock = new Object();
Object existingLock = downloadLocks.putIfAbsent(file, newLock);
lock = existingLock == null ? newLock : existingLock;
try
synchronized (lock)
if (!Files.exists(file))
retrieveResource(file);
finally
downloadLocks.remove(file, lock);
serveResource(file);
retrieveResource
method retrieves resource into a temporary file and then performs atomical move into the final destination, so I assume that it's safe to use.
My question is if it's a correct multithreaded code and if there are better suited primitives for this fine-grained synchronization. I'm using Java 7.
java multithreading thread-safety
add a comment |Â
up vote
2
down vote
favorite
I'm writing a proxy server that serves cached resources. If resource is not cached, it's retrieved and put into filesystem. Server is multi-threaded and I want to avoid any kind of global locking. If there are several requests for the same resource that is not cached at the same time, one request should retrieve resource, put it into filesystem and other requests should be blocked and then serve cached resource.
My first approach was to synchronize on interned string:
void serveFile(Path file)
if (!Files.exists(file))
synchronized (file.toString().intern())
if (!Files.exists(file))
retrieveResource(file);
serveResource(file);
But there are many discussions about why this approach is not the best, so I rewrote is as follows:
private final ConcurrentMap<Path, Object> downloadLocks = new ConcurrentHashMap<>();
void serveFile(Path file)
if (!Files.exists(file))
Object lock = downloadLocks.get(file);
if (lock == null)
Object newLock = new Object();
Object existingLock = downloadLocks.putIfAbsent(file, newLock);
lock = existingLock == null ? newLock : existingLock;
try
synchronized (lock)
if (!Files.exists(file))
retrieveResource(file);
finally
downloadLocks.remove(file, lock);
serveResource(file);
retrieveResource
method retrieves resource into a temporary file and then performs atomical move into the final destination, so I assume that it's safe to use.
My question is if it's a correct multithreaded code and if there are better suited primitives for this fine-grained synchronization. I'm using Java 7.
java multithreading thread-safety
add a comment |Â
up vote
2
down vote
favorite
up vote
2
down vote
favorite
I'm writing a proxy server that serves cached resources. If resource is not cached, it's retrieved and put into filesystem. Server is multi-threaded and I want to avoid any kind of global locking. If there are several requests for the same resource that is not cached at the same time, one request should retrieve resource, put it into filesystem and other requests should be blocked and then serve cached resource.
My first approach was to synchronize on interned string:
void serveFile(Path file)
if (!Files.exists(file))
synchronized (file.toString().intern())
if (!Files.exists(file))
retrieveResource(file);
serveResource(file);
But there are many discussions about why this approach is not the best, so I rewrote is as follows:
private final ConcurrentMap<Path, Object> downloadLocks = new ConcurrentHashMap<>();
void serveFile(Path file)
if (!Files.exists(file))
Object lock = downloadLocks.get(file);
if (lock == null)
Object newLock = new Object();
Object existingLock = downloadLocks.putIfAbsent(file, newLock);
lock = existingLock == null ? newLock : existingLock;
try
synchronized (lock)
if (!Files.exists(file))
retrieveResource(file);
finally
downloadLocks.remove(file, lock);
serveResource(file);
retrieveResource
method retrieves resource into a temporary file and then performs atomical move into the final destination, so I assume that it's safe to use.
My question is if it's a correct multithreaded code and if there are better suited primitives for this fine-grained synchronization. I'm using Java 7.
java multithreading thread-safety
I'm writing a proxy server that serves cached resources. If resource is not cached, it's retrieved and put into filesystem. Server is multi-threaded and I want to avoid any kind of global locking. If there are several requests for the same resource that is not cached at the same time, one request should retrieve resource, put it into filesystem and other requests should be blocked and then serve cached resource.
My first approach was to synchronize on interned string:
void serveFile(Path file)
if (!Files.exists(file))
synchronized (file.toString().intern())
if (!Files.exists(file))
retrieveResource(file);
serveResource(file);
But there are many discussions about why this approach is not the best, so I rewrote is as follows:
private final ConcurrentMap<Path, Object> downloadLocks = new ConcurrentHashMap<>();
void serveFile(Path file)
if (!Files.exists(file))
Object lock = downloadLocks.get(file);
if (lock == null)
Object newLock = new Object();
Object existingLock = downloadLocks.putIfAbsent(file, newLock);
lock = existingLock == null ? newLock : existingLock;
try
synchronized (lock)
if (!Files.exists(file))
retrieveResource(file);
finally
downloadLocks.remove(file, lock);
serveResource(file);
retrieveResource
method retrieves resource into a temporary file and then performs atomical move into the final destination, so I assume that it's safe to use.
My question is if it's a correct multithreaded code and if there are better suited primitives for this fine-grained synchronization. I'm using Java 7.
java multithreading thread-safety
asked Feb 4 at 20:28
vbezhenar
1133
1133
add a comment |Â
add a comment |Â
2 Answers
2
active
oldest
votes
up vote
1
down vote
accepted
The handling of Object lock
in serveFile
looks rather messy: its instantiation or retrieval could be done with just a single call of putIfAbsent
.
As @SharonBenAsher already mentioned in his answer, Java 7 has a number of interesting tools to handle concurrency in a more flexible way.
For this case, I'd suggest to use a write lock when the resource is retrieved and a read lock when it is served.
private final ConcurrentMap<Path, ReadWriteLock> downloadLocks = new ConcurrentHashMap<>();
// the original method can be shortened to this:
void serveFile(Path file)
if (!Files.exists(file))
retrieveResourceSafely(file);
serveResourceSafely(file);
private ReadWriteLock getLockFor(Path file)
if (downloadLocks.containsKey(file))
return downloadLocks.get(file);
final ReadWriteLock lock = new ReentrantReadWriteLock();
downloadLocks.put(file, lock);
return lock;
/*
Serve the file safely using a _read_ lock. According to the Javadoc on ReadWriteLock,
"only a single thread at a time (a <em>writer</em> thread)
can modify the shared data, in many cases any
number of threads can concurrently read the data
(hence <em>reader</em> threads)".
*/
private void serveResourceSafely(Path file)
final ReadWriteLock fileLock = getLockFor(file);
try
fileLock.readLock().lock();
serveResource(file);
finally
fileLock.readLock().unlock();
// Retrieves the requested resource with a _write_ lock.
private void retrieveResourceSafely(Path file)
final ReadWriteLock fileLock = getLockFor(file);
try
fileLock.writeLock().lock();
if (!Files.exists(file))
retrieveResource(file);
finally
fileLock.writeLock().unlock();
The drawback of this approach that I already see is that downloadLocks
map will be progressively filled with references to all the resources that were accessed. Emptying this map from time to time would be a solution, but I don't have an idea of the logic of resources handling in your cache.
"The handling of Object lock in serveFile looks rather messy: its instantiation or retrieval could be done with just a single call of putIfAbsent." your methodgetLockFor
seems to be wrong because returnsnull
for thread which puts the value first. Javadoc forputIfAbsent
return value: "the previous value associated with the specified key, or null if there was no mapping for the key". That's why my code is a bit messy. The best solution would be usingcomputeIfAbsent
method, but it's from Java 8.
â vbezhenar
Feb 5 at 17:02
Yes, indeed, nice remark aboutputIfAbsent
vscomputeIfAbsent
, thank you. Fixed the method.
â Antot
Feb 5 at 18:57
add a comment |Â
up vote
1
down vote
Java's low level thread synchronization mechanism (synchronized blocks, volatile variables, wait(), notify(), etc) were proven to be tricky for the human single threaded mindset. Threading hazards like deadlock, thread starvation, and race conditions, which result from incorrect use of low level thread synchronization, are also hard to detect and debug.
To that end, Java 7 introduced several new features/classes in the java.util.concurrency
package that allow developers to approach the thread synchronization domain from a high(er) level. java.util.concurrent.locks.ReentrantLock
is more flexible and gives more fine grain control over the scope of locking. it also performs better. The advice that I see around the web (including on Stack Overflow) is that ReentrantLock
should replace synchronized block in all new code.
add a comment |Â
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
1
down vote
accepted
The handling of Object lock
in serveFile
looks rather messy: its instantiation or retrieval could be done with just a single call of putIfAbsent
.
As @SharonBenAsher already mentioned in his answer, Java 7 has a number of interesting tools to handle concurrency in a more flexible way.
For this case, I'd suggest to use a write lock when the resource is retrieved and a read lock when it is served.
private final ConcurrentMap<Path, ReadWriteLock> downloadLocks = new ConcurrentHashMap<>();
// the original method can be shortened to this:
void serveFile(Path file)
if (!Files.exists(file))
retrieveResourceSafely(file);
serveResourceSafely(file);
private ReadWriteLock getLockFor(Path file)
if (downloadLocks.containsKey(file))
return downloadLocks.get(file);
final ReadWriteLock lock = new ReentrantReadWriteLock();
downloadLocks.put(file, lock);
return lock;
/*
Serve the file safely using a _read_ lock. According to the Javadoc on ReadWriteLock,
"only a single thread at a time (a <em>writer</em> thread)
can modify the shared data, in many cases any
number of threads can concurrently read the data
(hence <em>reader</em> threads)".
*/
private void serveResourceSafely(Path file)
final ReadWriteLock fileLock = getLockFor(file);
try
fileLock.readLock().lock();
serveResource(file);
finally
fileLock.readLock().unlock();
// Retrieves the requested resource with a _write_ lock.
private void retrieveResourceSafely(Path file)
final ReadWriteLock fileLock = getLockFor(file);
try
fileLock.writeLock().lock();
if (!Files.exists(file))
retrieveResource(file);
finally
fileLock.writeLock().unlock();
The drawback of this approach that I already see is that downloadLocks
map will be progressively filled with references to all the resources that were accessed. Emptying this map from time to time would be a solution, but I don't have an idea of the logic of resources handling in your cache.
"The handling of Object lock in serveFile looks rather messy: its instantiation or retrieval could be done with just a single call of putIfAbsent." your methodgetLockFor
seems to be wrong because returnsnull
for thread which puts the value first. Javadoc forputIfAbsent
return value: "the previous value associated with the specified key, or null if there was no mapping for the key". That's why my code is a bit messy. The best solution would be usingcomputeIfAbsent
method, but it's from Java 8.
â vbezhenar
Feb 5 at 17:02
Yes, indeed, nice remark aboutputIfAbsent
vscomputeIfAbsent
, thank you. Fixed the method.
â Antot
Feb 5 at 18:57
add a comment |Â
up vote
1
down vote
accepted
The handling of Object lock
in serveFile
looks rather messy: its instantiation or retrieval could be done with just a single call of putIfAbsent
.
As @SharonBenAsher already mentioned in his answer, Java 7 has a number of interesting tools to handle concurrency in a more flexible way.
For this case, I'd suggest to use a write lock when the resource is retrieved and a read lock when it is served.
private final ConcurrentMap<Path, ReadWriteLock> downloadLocks = new ConcurrentHashMap<>();
// the original method can be shortened to this:
void serveFile(Path file)
if (!Files.exists(file))
retrieveResourceSafely(file);
serveResourceSafely(file);
private ReadWriteLock getLockFor(Path file)
if (downloadLocks.containsKey(file))
return downloadLocks.get(file);
final ReadWriteLock lock = new ReentrantReadWriteLock();
downloadLocks.put(file, lock);
return lock;
/*
Serve the file safely using a _read_ lock. According to the Javadoc on ReadWriteLock,
"only a single thread at a time (a <em>writer</em> thread)
can modify the shared data, in many cases any
number of threads can concurrently read the data
(hence <em>reader</em> threads)".
*/
private void serveResourceSafely(Path file)
final ReadWriteLock fileLock = getLockFor(file);
try
fileLock.readLock().lock();
serveResource(file);
finally
fileLock.readLock().unlock();
// Retrieves the requested resource with a _write_ lock.
private void retrieveResourceSafely(Path file)
final ReadWriteLock fileLock = getLockFor(file);
try
fileLock.writeLock().lock();
if (!Files.exists(file))
retrieveResource(file);
finally
fileLock.writeLock().unlock();
The drawback of this approach that I already see is that downloadLocks
map will be progressively filled with references to all the resources that were accessed. Emptying this map from time to time would be a solution, but I don't have an idea of the logic of resources handling in your cache.
"The handling of Object lock in serveFile looks rather messy: its instantiation or retrieval could be done with just a single call of putIfAbsent." your methodgetLockFor
seems to be wrong because returnsnull
for thread which puts the value first. Javadoc forputIfAbsent
return value: "the previous value associated with the specified key, or null if there was no mapping for the key". That's why my code is a bit messy. The best solution would be usingcomputeIfAbsent
method, but it's from Java 8.
â vbezhenar
Feb 5 at 17:02
Yes, indeed, nice remark aboutputIfAbsent
vscomputeIfAbsent
, thank you. Fixed the method.
â Antot
Feb 5 at 18:57
add a comment |Â
up vote
1
down vote
accepted
up vote
1
down vote
accepted
The handling of Object lock
in serveFile
looks rather messy: its instantiation or retrieval could be done with just a single call of putIfAbsent
.
As @SharonBenAsher already mentioned in his answer, Java 7 has a number of interesting tools to handle concurrency in a more flexible way.
For this case, I'd suggest to use a write lock when the resource is retrieved and a read lock when it is served.
private final ConcurrentMap<Path, ReadWriteLock> downloadLocks = new ConcurrentHashMap<>();
// the original method can be shortened to this:
void serveFile(Path file)
if (!Files.exists(file))
retrieveResourceSafely(file);
serveResourceSafely(file);
private ReadWriteLock getLockFor(Path file)
if (downloadLocks.containsKey(file))
return downloadLocks.get(file);
final ReadWriteLock lock = new ReentrantReadWriteLock();
downloadLocks.put(file, lock);
return lock;
/*
Serve the file safely using a _read_ lock. According to the Javadoc on ReadWriteLock,
"only a single thread at a time (a <em>writer</em> thread)
can modify the shared data, in many cases any
number of threads can concurrently read the data
(hence <em>reader</em> threads)".
*/
private void serveResourceSafely(Path file)
final ReadWriteLock fileLock = getLockFor(file);
try
fileLock.readLock().lock();
serveResource(file);
finally
fileLock.readLock().unlock();
// Retrieves the requested resource with a _write_ lock.
private void retrieveResourceSafely(Path file)
final ReadWriteLock fileLock = getLockFor(file);
try
fileLock.writeLock().lock();
if (!Files.exists(file))
retrieveResource(file);
finally
fileLock.writeLock().unlock();
The drawback of this approach that I already see is that downloadLocks
map will be progressively filled with references to all the resources that were accessed. Emptying this map from time to time would be a solution, but I don't have an idea of the logic of resources handling in your cache.
The handling of Object lock
in serveFile
looks rather messy: its instantiation or retrieval could be done with just a single call of putIfAbsent
.
As @SharonBenAsher already mentioned in his answer, Java 7 has a number of interesting tools to handle concurrency in a more flexible way.
For this case, I'd suggest to use a write lock when the resource is retrieved and a read lock when it is served.
private final ConcurrentMap<Path, ReadWriteLock> downloadLocks = new ConcurrentHashMap<>();
// the original method can be shortened to this:
void serveFile(Path file)
if (!Files.exists(file))
retrieveResourceSafely(file);
serveResourceSafely(file);
private ReadWriteLock getLockFor(Path file)
if (downloadLocks.containsKey(file))
return downloadLocks.get(file);
final ReadWriteLock lock = new ReentrantReadWriteLock();
downloadLocks.put(file, lock);
return lock;
/*
Serve the file safely using a _read_ lock. According to the Javadoc on ReadWriteLock,
"only a single thread at a time (a <em>writer</em> thread)
can modify the shared data, in many cases any
number of threads can concurrently read the data
(hence <em>reader</em> threads)".
*/
private void serveResourceSafely(Path file)
final ReadWriteLock fileLock = getLockFor(file);
try
fileLock.readLock().lock();
serveResource(file);
finally
fileLock.readLock().unlock();
// Retrieves the requested resource with a _write_ lock.
private void retrieveResourceSafely(Path file)
final ReadWriteLock fileLock = getLockFor(file);
try
fileLock.writeLock().lock();
if (!Files.exists(file))
retrieveResource(file);
finally
fileLock.writeLock().unlock();
The drawback of this approach that I already see is that downloadLocks
map will be progressively filled with references to all the resources that were accessed. Emptying this map from time to time would be a solution, but I don't have an idea of the logic of resources handling in your cache.
edited Feb 5 at 18:52
answered Feb 5 at 9:52
Antot
3,5181515
3,5181515
"The handling of Object lock in serveFile looks rather messy: its instantiation or retrieval could be done with just a single call of putIfAbsent." your methodgetLockFor
seems to be wrong because returnsnull
for thread which puts the value first. Javadoc forputIfAbsent
return value: "the previous value associated with the specified key, or null if there was no mapping for the key". That's why my code is a bit messy. The best solution would be usingcomputeIfAbsent
method, but it's from Java 8.
â vbezhenar
Feb 5 at 17:02
Yes, indeed, nice remark aboutputIfAbsent
vscomputeIfAbsent
, thank you. Fixed the method.
â Antot
Feb 5 at 18:57
add a comment |Â
"The handling of Object lock in serveFile looks rather messy: its instantiation or retrieval could be done with just a single call of putIfAbsent." your methodgetLockFor
seems to be wrong because returnsnull
for thread which puts the value first. Javadoc forputIfAbsent
return value: "the previous value associated with the specified key, or null if there was no mapping for the key". That's why my code is a bit messy. The best solution would be usingcomputeIfAbsent
method, but it's from Java 8.
â vbezhenar
Feb 5 at 17:02
Yes, indeed, nice remark aboutputIfAbsent
vscomputeIfAbsent
, thank you. Fixed the method.
â Antot
Feb 5 at 18:57
"The handling of Object lock in serveFile looks rather messy: its instantiation or retrieval could be done with just a single call of putIfAbsent." your method
getLockFor
seems to be wrong because returns null
for thread which puts the value first. Javadoc for putIfAbsent
return value: "the previous value associated with the specified key, or null if there was no mapping for the key". That's why my code is a bit messy. The best solution would be using computeIfAbsent
method, but it's from Java 8.â vbezhenar
Feb 5 at 17:02
"The handling of Object lock in serveFile looks rather messy: its instantiation or retrieval could be done with just a single call of putIfAbsent." your method
getLockFor
seems to be wrong because returns null
for thread which puts the value first. Javadoc for putIfAbsent
return value: "the previous value associated with the specified key, or null if there was no mapping for the key". That's why my code is a bit messy. The best solution would be using computeIfAbsent
method, but it's from Java 8.â vbezhenar
Feb 5 at 17:02
Yes, indeed, nice remark about
putIfAbsent
vs computeIfAbsent
, thank you. Fixed the method.â Antot
Feb 5 at 18:57
Yes, indeed, nice remark about
putIfAbsent
vs computeIfAbsent
, thank you. Fixed the method.â Antot
Feb 5 at 18:57
add a comment |Â
up vote
1
down vote
Java's low level thread synchronization mechanism (synchronized blocks, volatile variables, wait(), notify(), etc) were proven to be tricky for the human single threaded mindset. Threading hazards like deadlock, thread starvation, and race conditions, which result from incorrect use of low level thread synchronization, are also hard to detect and debug.
To that end, Java 7 introduced several new features/classes in the java.util.concurrency
package that allow developers to approach the thread synchronization domain from a high(er) level. java.util.concurrent.locks.ReentrantLock
is more flexible and gives more fine grain control over the scope of locking. it also performs better. The advice that I see around the web (including on Stack Overflow) is that ReentrantLock
should replace synchronized block in all new code.
add a comment |Â
up vote
1
down vote
Java's low level thread synchronization mechanism (synchronized blocks, volatile variables, wait(), notify(), etc) were proven to be tricky for the human single threaded mindset. Threading hazards like deadlock, thread starvation, and race conditions, which result from incorrect use of low level thread synchronization, are also hard to detect and debug.
To that end, Java 7 introduced several new features/classes in the java.util.concurrency
package that allow developers to approach the thread synchronization domain from a high(er) level. java.util.concurrent.locks.ReentrantLock
is more flexible and gives more fine grain control over the scope of locking. it also performs better. The advice that I see around the web (including on Stack Overflow) is that ReentrantLock
should replace synchronized block in all new code.
add a comment |Â
up vote
1
down vote
up vote
1
down vote
Java's low level thread synchronization mechanism (synchronized blocks, volatile variables, wait(), notify(), etc) were proven to be tricky for the human single threaded mindset. Threading hazards like deadlock, thread starvation, and race conditions, which result from incorrect use of low level thread synchronization, are also hard to detect and debug.
To that end, Java 7 introduced several new features/classes in the java.util.concurrency
package that allow developers to approach the thread synchronization domain from a high(er) level. java.util.concurrent.locks.ReentrantLock
is more flexible and gives more fine grain control over the scope of locking. it also performs better. The advice that I see around the web (including on Stack Overflow) is that ReentrantLock
should replace synchronized block in all new code.
Java's low level thread synchronization mechanism (synchronized blocks, volatile variables, wait(), notify(), etc) were proven to be tricky for the human single threaded mindset. Threading hazards like deadlock, thread starvation, and race conditions, which result from incorrect use of low level thread synchronization, are also hard to detect and debug.
To that end, Java 7 introduced several new features/classes in the java.util.concurrency
package that allow developers to approach the thread synchronization domain from a high(er) level. java.util.concurrent.locks.ReentrantLock
is more flexible and gives more fine grain control over the scope of locking. it also performs better. The advice that I see around the web (including on Stack Overflow) is that ReentrantLock
should replace synchronized block in all new code.
answered Feb 5 at 8:42
Sharon Ben Asher
2,073512
2,073512
add a comment |Â
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f186754%2ffine-grained-synchronization-to-serve-a-file%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password