private static ThreadGroup startMethodThreads(final MultithreadedTestCase test, ThreadedMethod[] threadMethods,
			final LinkedList<java.lang.Thread> threads, final Throwable[] error) {
		ThreadGroup threadGroup = new ThreadGroup("MTC-Threads");
		final CountDownLatch latch = new CountDownLatch(threadMethods.length);
		final Semaphore waitForRegistration = new Semaphore(0);
		final AtomicBoolean signalledError = new AtomicBoolean(false);
		for (final ThreadedMethod threadMethod : threadMethods) {
			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(threadMethod.getMethod());
						threadMethod.getMethod().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, threadMethod.getName());
			threads.add(t);

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

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