private static ThreadGroup startMethodThreads(
	    final MultithreadedTestCase test,
	    SortedMap<String, Method> methods,
	    final LinkedList<java.lang.Thread> threads, final Throwable[] error) {
	ThreadGroup threadGroup = new ThreadGroup("MTC-Threads");
	final CountDownLatch latch = new CountDownLatch(methods.size());
	final Semaphore waitForRegistration = new Semaphore(0);
	final AtomicBoolean signalledError = new AtomicBoolean(false);
	for (final Map.Entry<String, Method> entry : methods.entrySet()) {
	    Runnable r = new Runnable() {
		public void run() {
		    try {
			waitForRegistration.release();
			latch.countDown();
			latch.await();

			// At this point all threads are created and released
			// (in random order?) together to run in parallel

			test.hello();
			makeAccessible(entry.getValue());
			entry.getValue().invoke(test);
		    } catch (InvocationTargetException e) {
			Throwable cause = e.getCause();
			if (cause instanceof ThreadDeath)
			    return;
			if (error[0] == null) {
			    error[0] = cause;
			}
			if (signalledError.compareAndSet(false, true))
			    signalError(threads);
		    } catch (ThreadDeath e) {
			// ignore it
		    } catch (Throwable e) {
			System.out.println(java.lang.Thread.currentThread()
				.getName()
				+ " caught " + e.getMessage());
			if (error[0] == null)
			    error[0] = e;
			if (signalledError.compareAndSet(false, true))
			    signalError(threads);
		    } finally {
			test.goodbye();
		    }
		}
	    };
	    java.lang.Thread t = new java.lang.Thread(threadGroup, r, entry
		    .getKey());
	    threads.add(t);

	    // add thread to map of method threads, mapped by name
	    test.putThread(t.getName(), t);

	    t.start();
	    waitForRegistration.acquireUninterruptibly();
	}
	return threadGroup;
    }