Strange AWT related threding problem

This is also posted here on the SDN, but seeing as I’m not sure how active those forums are, and that I can not do anything until this is fixed, I’m also posting here.

I am having some problems with the event handeling in the game engine I am writing:
I need to listen to key press events and release events and put them in to some Sets, which are read from the step()
method every frame and transalated into platform-independent GFKeyEvents for inturnal use.
The reason for doing so is to get rid of key repeating so the game objects just see “press, hold, hold, hold, release” no mater what the OS is doing.

As you will see the key event methods are synchronized as is step() yet some how I still get

Exception in thread "Event thread 1" java.util.ConcurrentModificationException
	at java.util.HashMap$HashIterator.nextEntry(HashMap.java:810)
	at java.util.HashMap$KeyIterator.next(HashMap.java:845)
	at com.glassFlame.event.EventSystem.step(EventSystem.java:77)
	at com.glassFlame.event.EventThread.run(EventThread.java:36)

I have also wrapped the Sets using

Collections.synchronizedSet()

here is the class with some of the irelivent methods removed:


package com.glassFlame.event;
 
import java.awt.Component;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.HashSet;
import java.util.Vector;
import java.util.Collections;
import java.util.Set;
 
import com.glassFlame.GFObject;
import com.glassFlame.renderer.RenderManager;
import com.glassFlame.renderer.Renderer;
import com.glassFlame.scene.Scene;
import com.glassFlame.scene.bounding.BoundingStructure;
 
public class EventSystem implements KeyListener, MouseListener, MouseMotionListener{
 
	private BoundingStructure  graph;
	private EventThread        pump;
	private Component          source;
	private Scene              scene;
	private RenderManager      ren;
	private boolean            clear;
	private Vector<GFObject>   objs;
	private Vector<GFKeyListener> klobjs;
	private Set<GFKeyEvent>  keysHeld, keysPressedThisFrame, keysReleasedThisFrame, keysReleasedLastFrame, keysPressedLastFrame;
	private Vector<GFKeyEvent>  keyPresses, keyHolds, keyReleases;
	private Object keyLock = new Object();
 
	public EventSystem(BoundingStructure graph,Component source,RenderManager ren,int millsPerFrame)
	{
		this.clear  = true;
		this.graph  = graph;
		this.source = source;
		this.ren    = ren;
 
		pump = new EventThread(this,millsPerFrame);
 
		objs   = new Vector<GFObject>(100);
		klobjs = new Vector<GFKeyListener>(100);
 
		keysHeld              = Collections.synchronizedSet( new HashSet<GFKeyEvent>());
		keysPressedThisFrame  = Collections.synchronizedSet( new HashSet<GFKeyEvent>());
		keysReleasedThisFrame = Collections.synchronizedSet( new HashSet<GFKeyEvent>());
		keysReleasedLastFrame = Collections.synchronizedSet( new HashSet<GFKeyEvent>());
		keysPressedLastFrame  = Collections.synchronizedSet( new HashSet<GFKeyEvent>());
 
		keyPresses  = new Vector<GFKeyEvent>();
		keyHolds    = new Vector<GFKeyEvent>();
		keyReleases = new Vector<GFKeyEvent>();
 
		this.source.addKeyListener(this);
		this.source.addMouseListener(this);
		this.source.addMouseMotionListener(this);
	}
 
	public synchronized void addObject(GFObject obj)
	{
		graph.addObject(obj);
		objs.add(obj);
		if(obj instanceof GFKeyListener)
			klobjs.add((GFKeyListener) obj);
	}
 
	public synchronized void step(long millsPassed)
	{
		//TODO:event handling goes here
 
		//Process key events
		//this is required because they behave diferant on diferent platforms
 
		//remove keys that have been released for the held keys list
		for(GFKeyEvent ke : keysHeld)
		{
			//if the key in question was not touched this frame
			//and was released last frame
			if( (!keysPressedThisFrame.contains(ke) && !keysReleasedThisFrame.contains(ke))
					&&
					keysReleasedLastFrame.contains(ke))
				//the key has been released
			{
				System.out.println(ke.toString());
				keysHeld.remove(ke);
				keyReleases.add(ke);
			}
			else
				keyHolds.add(ke);
		}
 
		//ditect key presses by checking if a key pressed this frame
		//was not also presses the previous frame.
		//This makes it work on linux and turns extreamly fast key taps into
		//a key hold
		for(GFKeyEvent ke : keysPressedThisFrame)
		{
			if(keysPressedLastFrame.contains(ke) == false)
				keyPresses.add(ke);
		}
		//fire the key releases
		for(GFKeyListener kl : klobjs)
		{
			for(GFKeyEvent ke : keyReleases)
			{
				kl.keyReleased(ke);
			}
		}
		//fire the key holds
		for(GFKeyListener kl : klobjs)
		{
			for(GFKeyEvent ke : keyHolds)
			{
				kl.keyHeld(ke);
			}
		}
		//fire the key presses and transfer the events into the keysHeld array
		for(GFKeyListener kl : klobjs)
		{
			for(GFKeyEvent ke : keyPresses)
			{
				if(!keysHeld.contains(ke))
				{
					kl.keyPressed(ke);
					ke.setID(GFKeyEvent.KEY_TYPED);
					keysHeld.add(ke);
				}
			}
		}
 
		//System.out.println("keys presses  " + keyPresses.size());
		//System.out.println("keys holds    " + keyHolds.size());
		//System.out.println("keys releases " + keyReleases.size());
		//System.out.println(keysHeld.toString());
 
		//swap the last-frame and this-frame sets;
		Set<GFKeyEvent> tem;
 
		tem                  = keysPressedLastFrame;
		keysPressedLastFrame = keysPressedThisFrame;
		keysPressedThisFrame = tem;
 
		tem                   = keysReleasedLastFrame;
		keysReleasedLastFrame = keysReleasedThisFrame;
		keysReleasedThisFrame = tem;
		//clear all the events from last time
		keysPressedThisFrame.clear();
		keysReleasedThisFrame.clear();
 
		keyPresses.clear();
		keyHolds.clear();
		keyReleases.clear();
		//clear the buffer before drawing
		if(clear)
			ren.setBuffer(Renderer.BUFFER_DEFAULT);
		ren.clear();
		//call the draw events of all the objects
		ren.start();
		for(int i = 0; i < objs.size();i++)
		{
			objs.get(i).draw(ren);
		}
		ren.stop();
		ren.done();
	}
	public void start()
	{
		pump.start();
	}
	public void stop()
	{
		pump.pause();
	}
	public boolean running()
	{
		return pump.running();
	}
 
	@Override
	public synchronized void keyPressed(KeyEvent e) {
		keysPressedThisFrame.add(new GFKeyEvent(e,KeyEvent.KEY_PRESSED));
		System.out.println("press " + KeyEvent.getKeyText(e.getKeyCode()) );
	}
	@Override
	public synchronized void keyReleased(KeyEvent e) {
		keysReleasedThisFrame.add(new GFKeyEvent(e,KeyEvent.KEY_RELEASED));
		System.out.println("release " + KeyEvent.getKeyText(e.getKeyCode()) );
	}
	@Override
	public synchronized void keyTyped(KeyEvent e) {
//not used
	}
}

The class is construted and then the method start() is called which calls start() on the EventThread which in turn calls step about 30 times a second.

Your problem is not related to synchronization or threading.

You have fallen foul of the ‘improved’ for loop.


for(GFKeyEvent ke : keysHeld)

This construct uses an Iterator to step over the contents of the iterable keysHeld.
While an Iterator is in use, you must not modify the underlying Collection - otherwise, as you have discovered, the Iterator will throw a ConcurrentModificationException the next time any of its methods are invoked. (lucky for you the HashMap implementation has a fail-fast behaviour; otherwise this bug would have been much harder to find)

You should change your for loop construct to one that explicitly declares the Iterator, and then use the Iterators own remove(…) method to modify the Collection.

Thank you very much! :smiley: