问题
I'm writing a game in which a thread - GameThread - loops forever, updating all my sprites, rendering them, and then sleeping for some time before doing it all again. I also have a custom-made Event handler which deals with key presses etc.
This all works fine in most cases. However I have a problem if an event is thrown while GameThread is rendering. On rare occasions, the handler that deals with the event may make a concurrent change to what needs to be rendered affecting the results of the GameThread rendering.
To avoid this, I want the event handler to pause the GameThread immediately, handle the event, then resume GameThread.
The
suspend()
/resume()
methods suit my needs, but they were deprecated. In my case, however, as there is little chance of a deadlock, is it safe to use them regardless?If no, what other alternatives do I have that don't have a huge amount of overhead?
I have seen a suggestion of requesting a thread to pause by setting a flag in the Thread to be paused. In my case, however, I don't see that as a viable option since the GameThread loop may take a while during an iteration through the loop. I won't be able to check the flag until I'm done with the loop and by then it is too late.
I need immediate pausing, or else the user will notice a delay in the event handling.
回答1:
The suspend() / resume() methods suit my needs, but they were deprecated. In my case, however, as there is little chance of a deadlock, is it safe to use them regardless?
Obviously, if there is ZERO chance of a deadlock then it is safe. But there are all sorts of unexpected ways to get a deadlock. For instance, you could happen to pause a thread while it is initializing a class ... and that would deadlock any other thread trying to refer to a static field of that class. (This is a consequence of a specified behaviour of the JVM. There are other places where the locking / synchronization that goes on under the hood is not specified. Fair enough. It doesn't need to be ... unless you are contemplating using these deprecated methods.)
So, the reality is that it is really difficult to determine (prove) if it is actually safe. And if you can't determine this, then it is a potentially risky thing to do. That's WHY the methods are deprecated.
(Strictly speaking, this is not a deadlock. A deadlock is when the threads can never proceed. In this case, the other threads can proceed if you can resume the paused thread.)
回答2:
If you want to synchronize access to resources, use a ReentrantLock:
ReentrantLock sync = new ReentrantLock();
You'd have to pass that lock to each runnable where you want to access the shared data.
Then in each place you're accessing the resource in question, you would use that shared lock object, and obtain and release the lock (ie, your critical sections):
sync.lock();
try {
// critical section code here
}
finally {
sync.unlock();
}
This is pretty standard concurrent programming in java. Keep in mind "lock" is a blocking method, so you might want to use "tryLock" instead, which allows you to try and acquire the lock, but returns a boolean as to whether or not you actually got the lock:
if (sync.tryLock()) {
try {
//critical section
}
finally {
sync.unlock();
}
}
There's a version of "tryLock" which will wait a given amount of time, before it will give up trying to acquire the lock and return a false value.
回答3:
Usually, you would do some thread synchronization:
- http://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html
This would let you do one of the two things you are doing: either render in the game rendering thread or do the changes based on your events.
The problem you are facing seems to be that your rendering code is taking too long for you to actually have a smooth experience (i.e. a lot of events can pile up for processing while you are rendering something). In that case, you should make your rendering out of independent pieces that can finish quickly and synchronize on them.
Without any code I cannot give you a specific advice, however in general it would look something like this:
List<Shape> shapesToRender;
Object lockObject = new Object(); // Note this must be somehow shared between two threads
// Your rendering thread method
public void renderForever() {
while(true) {
for(Shape shape: shapesToRender) {
synchronized(lockObject) {
render(shape);
}
}
}
}
// One of your event handlers
public void handleEvent(Event event) {
synchronized(lockObject) {
// Process event somehow, e.g. change the color of some of the shapes
event.getShape().setColor(Color.RED);
}
}
With the above, either:
- You will be rendering one shape (and all your event handlers will be waiting for that to finish), or
- Some of your event handlers will be doing something (and your rendering thread will be waiting for that to finish)
You should look at this Java trail in more depth:
- http://docs.oracle.com/javase/tutorial/essential/concurrency/index.html
as there are other solutions, e.g. using lock objects:
- http://docs.oracle.com/javase/tutorial/essential/concurrency/newlocks.html
or concurrent collections:
- http://docs.oracle.com/javase/tutorial/essential/concurrency/collections.html
that, depending on your problem, might be easier and, most importantly, very well tested solutions that would allow you to do something in a standard way, thus avoiding all the pitfalls that you can get into when rolling out custom threading code.
Hope this helps.
来源:https://stackoverflow.com/questions/9952636/how-to-synchronize-shared-data-between-threads-by-using-pause-then-resume-or-alt