I would like to convert the following code to fit multithread environment.
List<Observer> list = new ArrayList<>();
public void removeObserver(Observer p) {
for (Observer observer: list) {
if (observer.equals(p)) {
list.remove(observer);
break;
}
}
}
public void addObserver(Observer p) {
list.add(p);
}
public void notifyObserver(Event obj) {
for (Observer observer: list) {
observer.notify(obj);
}
}
Definitely, one of the easiest way to do so, is to add synchronized keyword, which ensure only one thread can runs the logic, and thereby ensuring result is correct.
However, is there better way to solve the issue. I have do some sort of research, and found that I can use Collections.synchronizedList, and also notice such list.iterator is not thread-safe, so I should avoid use of forEach loop or iterator directly unless I do a synchronized (list)
I just don't want to use synchronized, and think if there is another possible approach. Here is my second attempt.
List<Observer> list = Collections.synchronizedList(new ArrayList<Observer>()); // which is thread safe
public void removeObserver(Observer p) {
// as the list may get modify, I create a copy first
List<Observer> copy = new CopyOnWriteArrayList(list);
for (Observer observer: copy) {
if (observer.equals(p)) {
// but now, no use of iterator
list.remove(observer); // remove it from the original copy
break;
}
}
}
public void addObserver(Observer p) {
list.add(p);
}
public void notifyObserver(Event obj) {
List<Observer> copy = new CopyOnWriteArrayList(list);
// not use iterator, as thread safe list's iterator can be thread unsafe
// and for-each loop use iterator concept
for (Observer observer: copy) {
observer.notify(obj);
}
}
I just want to ask if my second attempt is thread-safe? Also, is there a better approach to do this then my proposed second method?
Definitely, one of the easiest way to do so, is to add synchronized keyword, which ensure only one thread can runs the logic, and thereby ensuring result is correct.
This is correct.
However, is there better way to solve the issue?
Possibly. But lets take look at your second attempt:
List<Observer> list = Collections.synchronizedList(new ArrayList<Observer>());
// which is thread safe
Yes it is thread-safe. With certain constraints.
public void removeObserver(Observer p) {
// as the list may get modify, I create a copy first
List<Observer> copy = new CopyOnWriteArrayList(list);
...
Three problems here:
You are creating a copy of the list. That is an O(N) operation.
The CopyOnWriteArrayList constructor is going to iterate list ... and iteration of a list created by synchronizedList is not atomic / thread-safe so you have a race condition.
There is no actual benefit of using CopyOnWriteArrayList here over (say) ArrayList. The copy object is local and thread-confined so it doesn't need to be thread-safe.
In summary, this is not thread-safe AND it is more expensive simply making the original methods synchronized.
A possibly better way:
List<Observer> list = new CopyOnWriteArrayList()
public void removeObserver(Observer p) {
list.remove(p)
}
public void addObserver(Observer p) {
list.add(p);
}
public void notifyObserver(Event obj) {
for (Observer observer: list) {
observer.notify(obj);
}
}
This is thread-safe with the caveat that an Observer added while a notifyObserver call is in progress will not be notified.
The only potential problem is that mutations to a CopyOnWriteArrayList are expensive since they create a copy of the entire list. So if the ratio of mutations to notifies is too high, this may be more expensive than the solution using synchronized methods. On the other hand, if multiple threads call notifyObserver, those calls can proceed in parallel.
Related
I have the concurrent hashmap in some service class:
class MyClass implements Flushable {
private volatile ConcurrentHashMap<Integer, Object> hashMap = ...
public void add(int id, Object value) {
hashMap.put(id, value);
}
#Override
public void flush() throws IOException {
hashMap.foreach((k, v) -> ...)
hashMap.clear();
}
}
Do I need to do some additional locking to be sure that:
1. flush will process all map entries (what if add is invoked between foreach and clear?)
2. clear will not remove entries which were inserted/updated after foreach
From javadoc, there is a guarantee that update happens before read. So as far as I understand clear will block put invocations, however to reach what I want I need some additional locks.
In your case you need any extra locking here, both are operation should be locked , clear method also do locking on segment level..
I have a Set with any type of values and an AtomicBoolean that indicates if the functionality provided by that class is running.
private Set<Object> set = new HashSet<>();
private AtomicBoolean running;
Now, i have two methods, one of them is adding objects to the set and the other serves as a setup method for my class.
public void start() {
// ...
set.foreEach(someApi::addObject);
// ...
running.set(true);
}
public void addObject(Object o) {
set.add(o);
if(running.get()) {
someApi.addObject(o);
}
}
However, there is a problem with that code. If the method is invoked from another thread while the start method is iterating through the set running is still false. Thus, the object will not be added to the api.
Question: How can i guarantee that all objects in the set and objects added with addObject will be added to the api exactly one time?
My ideas:
use a lock and block the addObject method if the setup is currently adding methods to the api (or make both methods synchronized, which will slightly decrease performence tough)
Question: How can i guarantee that all objects in the set and objects added with addObject will be added to the api exactly one time?
You have to be careful here because this gets close to the ole "double check locking bug".
If I understand you question you want to:
queue the objects passed into addObject(...) in the set before the call to start().
then when start() is called, call the API method on the objects in the set.
handle the overlap if additional objects are added during the call to start()
call the method once and only once on all objects passed to addObject(...).
What is confusing is that your API call is also named addObject(). I assume this is different from the addObject(...) method in your code sample. I'm going to rename it below to be someApiMethod(...) to show that it's not going recursive.
The easiest way is going to be, unfortunately, having a synchronized block in each of the methods:
private final Set<Object> set = new HashSet<>();
public void start() {
synchronized (set) {
set.forEach(someApi::someApiMethod);
}
}
public void addObject(Object obj) {
synchronized (set) {
if (set.add(obj)) {
someApi.addObject(obj);
}
}
}
}
To make it faster is going to take a lot more complicated code. One thing you could do is use a ConcurrentHashMap and a AtomicBoolean running. Something like:
private final ConcurrentMap<Object, Object> map = new ConcurrentHashMap<>();
private final Set<Object> beforeStart = new HashSet<>();
private final AtomicBoolean running = new AtomicBoolean();
public void start() {
synchronized (beforeStart) {
for (Object obj : beforeStart) {
doIfAbsent(obj);
}
running.set(true);
}
}
public void addObject(Object obj) {
if (running.get()) {
doIfAbsent(obj);
} else {
synchronized (beforeStart) {
// we have to test running again once we get the lock
if (running.get()) {
doIfAbsent(obj);
} else {
beforeStart.add(obj);
}
}
}
}
private void doIfAbsent(Object obj) {
if (map.putIfAbsent(obj, obj)) {
someApi.someApiMethod(obj);
}
}
This is pretty complicated and it may not be any faster depending on how large your hash map is and other factors.
public static ConcurrentHashMap<Integer,Session> USER_SESSIONS...
Everything works fine. But what if the system is allowed to be authorized two sessions with the same user ID? Well, that is roughly two PCs sitting under the same account, but different session.
Tried to do so:
ConcurrentHashMap<Integer,List<Session>> USER_SESSIONS....
...............
private void addUser(Session session){
List<Session> userSessions = Server.USER_SESSIONS.get(session.mUserId);
if(userSessions==null){
userSessions = new List<Session>();
userSessions.add(session);
Server.USER_SESSIONS.put(session.getUserId(),userSessions);
}else{
userSessions.add(session);
}
}
private void removeUser(Session session){
List<Session> userSessions = Server.USER_SESSIONS.get(session.mUserId);
if(userSessions!=null){
userSessions.remove(session);
if(userSessions.size()==0)
{
Server.USER_SESSIONS.remove(session.getUserId());
}
}
}
.................
private void workWithUsers(int userId){
for(Session session : Server.USER_SESSIONS.get(userId))
{
<do it!>
}
}
Naturally, all these methods can be called from different threads, and I'm getting errors related to List . And this is natural, because while I have foreach-list session can be removed by removeUser from another thread. What to do? How to make so that-be at work with a list of all the threads List waited until it occupies the thread is finished with it? Yet done so :)
public static ConcurrentHashMap<Integer,ConcurrentHashMap<Session,Session>> USER_SESSIONS
Since ConcurrentHashMap thread safe. But I think it's crooked decision. Many thanks in advance for your help!
P.S: JRE 1.6
Please sorry for my English.
You could use List myList = Collections.synchronizedList(new ArrayList<String>()); if you don't want to use CopyOnWriteArrayList.
The only thing you need to have in mind is that it is mandatory to synchronized the code where you will be iterating over the list. You can see more info here: Collections.synchronizedList and synchronized
use List myList = Collections.synchronizedList(new ArrayList<String>()); will be better
But if there is much more read operation than write, you can also use CopyOnWriteArrayList which is safe to iterate.
Using a thread safe list is still not enough to prevent race conditions in this case.
There's a possibility that two addUser calls at the same time may overwrite each other's puts. Also an add could happen between the check for size and the remove call in remoeUser.
You need something like this (not tested). This code assumes that a session will not be removed before a call to it's add.
private void addUser(Session session) {
while (true) {
List<Session> userSessions = Collections.synchronizedList(new ArrayList<Session>());
List<Session> oldSessions = USER_SESSIONS.putIfAbsent(session.mUserId, userSessions);
if (oldSessions != null) {
userSessions = oldSessions;
}
userSessions.add(session);
// want to make sure the map still contains this list and not another
// so checking references
// this could be false if the list was removed since the call to putIfAbsent
if (userSessions == USER_SESSIONS.get(session.mUserId)) {
break;
}
}
}
private void removeUser(Session session) {
List<Session> userSessions = USER_SESSIONS.get(session.mUserId);
if (userSessions != null) {
// make whole operation synchronized to make sure a new session is not added
// after the check for empty
synchronized (userSessions) {
userSessions.remove(session);
if (userSessions.isEmpty()) {
USER_SESSIONS.remove(session.mUserId);
}
}
}
}
You could try to use CopyOnWriteArrayList or CopyOnWriteArraySet in your case.
Why would this code:
public synchronized void update() {
for(T event : eventQueue)
{
processEvent(event);
}
events = eventQueue;
eventQueue = new LinkedList<T>();
}
run differently to this code:
public synchronized void update() {
for(T event : eventQueue)
{
processEvent(event);
}
events = eventQueue;
eventQueue.clear();
}
The first version works perfectly fine, however the second does not. The eventQueue.clear(); causes the app not to receive any events and finely crashes with a Concurrent Exception.
My app has two threads. The UI thread and the GameLoop thread. The UI thread adds events to the eventQueue like so:
public synchronized void addEvent(T newEvent) {
eventQueue.add(newEvent);
}
The GameLoop thread calls the update method to get a copy (called events) of the eventQueue.
All the code can be viewed from this website: http://entropyinteractive.com/2011/02/game-engine-design-input/
This seems kinda mysterious to me, since I thought eventQueue = new LinkedList<T>(); and eventQueue.clear(); would both result in an empty LinkedList? I believe it has something todo with establishing a new reference (But why?!).
Because in this code:
public LinkedList<T> getEvents()
{
return events;
}
You're returning the original list, not a copy. If you then clear() that list, you'll cause issues because you're removing things from the list (and more important, changing the size of the list) while the other thread is using it.
Note that this function isn't synchronized, so you can't even safely return a copy from it, because the original list could change while you're copying it (changing a reference is atomic, so it's safe to do that in your update() method).
You could return a copy from a synchronized method, like this:
public synchronized LinkedList<T> getEvents()
{
return new LinkedList<T>(events);
}
But that introduces an unnecessary copy and lock. Whether that matters depends on if you care more about defensive coding or performance requirements. I assume they're doing it this way for performance reasons.
Here is your problem
events = eventQueue;
eventQueue.clear()
Once you assign events = eventQueue you are escaping the reference of eventQueue to the public via
public LinkedList<T> getEvents()
{
return events;
}
One thread can be iterating over the getEvents() queue which actually is eventQueue and another thread can be invoking addEvent and since the synchronized in addEvent no longer matches with the iterator of getEvents() you will get the comodification.
My suggestion is if you really want to publish your events do it with a new collection.
events = new LinkedList<T>(eventQueue);
eventQueue.clear()
If you do not protect all uses of eventQueue, like handing out references in getEvents(), you should try ConcurrentLinkedList.
I have a store of data objects and I wish to synchronize modifications that are related to one particular object at a time.
class DataStore {
Map<ID, DataObject> objects = // ...
// other indices and stuff...
public final void doSomethingToObject(ID id) { /* ... */ }
public final void doSomethingElseToObject(ID id) { /* ... */ }
}
That is to say, I do not wish my data store to have a single lock since modifications to different data objects are completely orthogonal. Instead, I want to be able to take a lock that pertains to a single data object only.
Each data object has a unique id. One way is to create a map of ID => Lock and synchronize upon the one lock object associated with the id. Another way is to do something like:
synchronize(dataObject.getId().toString().intern()) {
// ...
}
However, this seems like a memory leak -- the internalized strings may never be collected.
Yet another idea is to synchronize upon the data object itself; however, what if you have an operation where the data object doesn't exist yet? For example, what will a method like addDataObject(DataObject) synchronize upon?
In summary, how can I write a function f(s), where s is a String, such that f(s)==f(t) if s.equals(t) in a memory-safe manner?
Add the lock directly to this DataObject, you could define it like this:
public class DataObject {
private Lock lock = new ReentrantLock();
public void lock() { this.lock.lock(); }
public void unlock() { this.lock.unlock(); }
public void doWithAction( DataObjectAction action ) {
this.lock();
try {
action.doWithLock( this ) :
} finally {
this.unlock();
}
}
// other methods here
}
public interface DataObjectAction { void doWithLock( DataObject object ); }
And when using it, you could simply do it like this:
DataObject object = // something here
object.doWithAction( new DataObjectAction() {
public void doWithLock( DataObject object ) {
object.setProperty( "Setting the value inside a locked object" );
}
} );
And there you have a single object locked for changes.
You could even make this a read-write lock if you also have read operations happening while writting.
For such case, I normally have 2 level of lock:
First level as a reader-writer-lock, which make sure update to the map (add/delete) is properly synchronized by treating them as "write", and access to entries in map is considered as "read" on the map. Once accessed to the value, then synchronize on the value. Here is a little example:
class DataStore {
Map<ID, DataObject> objMap = // ...
ReadWritLock objMapLock = new ReentrantReadWriteLock();
// other indices and stuff...
public void addDataObject(DataObject obj) {
objMapLock.writeLock().lock();
try {
// do what u need, u may synchronize on obj too, depends on situation
objMap.put(obj.getId(), obj);
} finally {
objMapLock.writeLock().unlock();
}
}
public final void doSomethingToObject(ID id) {
objMapLock.readLock().lock();
try {
DataObject dataObj = this.objMap.get(id);
synchronized(dataObj) {
// do what u need
}
} finally {
objMapLock.readLock().unlock();
}
}
}
Everything should then be properly synchronized without sacrificing much concurrency
Yet another idea is to synchronize upon the data object itself; however, what if you have an operation where the data object doesn't exist yet? For example, what will a method like addDataObject(DataObject) synchronize upon?
Synchronizing on the object is probably viable.
If the object doesn't exist yet, then nothing else can see it. Provided that you can arrange that the object is fully initialized by its constructor, and that it is not published by the constructor before the constructor returns, then you don't need to synchronize it. Another approach is to partially initialize in the constructor, and then use synchronized methods to do the rest of the construction and the publication.