Reputation: 147
Intern method will keep adding the strings into the pool which is a bad idea for long term process.Exact scenario is like this :
// rowKey variable defined below is the value of a row key extracted from a
// table of the db. There can be many rowKey values. (>100000).
String rowKey = "rowId_123123";
/* Once rowKey is extracted, then if another thread extracts the same
row from the table, then it should be put to wait. Object locking will
put all the threads to wait irrespective of the rowKey value which will
hinder performance. Interning the string will put it into the pool till
jvm execution.
*/
Is there a way to achieve this without interning the string and synchronizing
only on the string. Or if there is a way to convert a string into an object which can be visible to all the threads?
Upvotes: 2
Views: 1847
Reputation: 1558
Striped
ClassIn the past I have had success using the Guava Striped
class for this type of Object
to Lock
mapping operation. For in-depth details on the Striped
class please see the Guava wiki or the Striped
JavaDoc. Essentially, using a Striped<Lock>
instance to index Lock
values via String
keys is a thread-safe way to generate Lock
instances that minimizes memory footprint while maximizing concurrency. If you are avoiding 3rd party libraries you could instead implement your own Map<Object, Lock>
wrapper (perhaps mixing in WeakReference
) to roll your own less full-featured version of the Striped
class (be sure to directly compare hashcodes instead of relying on the ==
operator or the equals
methods!). In either case, the Striped
class is a good place to start learning about how to implement your lock generation/retrieval.
Note that for a specific use case, like for achieving atomic operations for database rows, you might want to use a ReadWriteLock
instead of a simple/basic Lock
(using Semaphore
is also possible). Also, please note that we don't need to intern the String
object given to Striped.get()
because the Striped
class compares Object
's for equality using hashcodes and the String
class makes special guarantees about hashcodes between character equivalent String
s always being equal. This example does use an interned String
(literal String
s are automatically interned) but the Striped
class works perfectly well with a dynamically generated String
which is not interned.
final Striped<Lock> stripedLocks = Striped.lock(10);
final String rowID = "rowLock123";
final Lock rowLock = stripedLocks.get(rowID);
try{
rowLock.lock();//Could also use tryLock or lockInterruptibly methods
//... we are successfully in the fenced/locked code block now
//... put your concurrency sensitive code here
}finally{
rowLock.unlock();
}
Don't use synchronized
on the returned Lock
object obtained from Striped.get(String)
!
I am adding this explicit warning to not use the returned Lock
object as a synchronized
block's monitor because someone edited my answer to include an example that incorrectly used the returned Lock
as a monitor object.
For reference, this is what that would look like in the above example:
//DO NOT USE THE RETURNED LOCK LIKE THIS
final Lock rowLock = stripedLocks.get(rowID);
synchronized(rowLock){
//...oh no
}
You should not use synchronized
on a Lock
because that defeats the entire purpose of using Lock
! The Lock
class is intended as a replacement for the use of synchronized
blocks. Using them together sacrifices the benefits of Lock
while still having the headaches and gotchas of synchronized
.
Upvotes: 3
Reputation: 50031
You can create your own object interning pool with weak referencing, so that it will expire unused keys.
Something like this should do it.. I think..:
final class RowLock {
public final int rowId;
private RowLock(int rowId) {
this.rowId = rowId;
}
public static synchronized RowLock getLockObject(int rowId) {
RowLock r = new RowLock(rowId);
WeakReference<RowLock> rInternedRef = pool.get(r);
if (rInternedRef != null) {
RowLock rInterned = rInternedRef.get();
if (rInterned != null) {
return rInterned;
}
}
pool.put(r, new WeakReference<>(r));
return r;
}
private static final WeakHashMap<RowLock,WeakReference<RowLock>> pool =
new WeakHashMap<>();
@Override
public int hashCode() {
return rowId;
}
@Override
public boolean equals(Object obj) {
return obj == this || (obj instanceof RowLock && ((RowLock)obj).rowId == rowId);
}
}
If your row IDs are not numeric, you can use a String rowId
instead and it will work just as well.
When you want to lock on a row, call getLockObject
and then synchronize on the returned object, which will have been "interned" via the map, so that all threads will get the same object. Once nothing is strongly referring to a RowLock
instance any more, it will be eligible for garbage collection, because its interning pool only refers to it with weak references.
Upvotes: 1