1 /* 2 * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 import java.util.concurrent.atomic.AtomicInteger; 25 import java.util.concurrent.BrokenBarrierException; 26 import java.util.concurrent.CyclicBarrier; 27 28 29 /** 30 * RacingThreadsTest is a support class for creating a test 31 * where multiple threads are needed to exercise a code path. 32 * The RacingThreadsTest class is typically used as follows: 33 * <ul> 34 * <li> 35 * Extend RacingThreadsTest class in order to provide the test 36 * specific variables and/or code, e.g., <br> 37 * public class MyRacingThreadsTest extends RacingThreadsTest 38 * <li> 39 * Use 40 * "new MyRacingThreadsTest(name, n_threads, n_loops, n_secs)" 41 * to create your test with the specified name and the specified 42 * number of WorkerThreads that execute the test code in parallel 43 * up to n_loops iterations or n_secs seconds. 44 * <li> 45 * Use 46 * "new DriverThread(test)" 47 * to create the test DriverThread that manages all the 48 * WorkerThreads. The DriverThread class can be extended to 49 * provide test specific code and/or variables. However, that 50 * is typically done in your test's subclass. 51 * <li> 52 * Use 53 * "new WorkerThread(workerNum, test)" 54 * to create WorkerThread-workerNum that executes the test code. 55 * The WorkerThread class can be extended to provide test thread 56 * specific code and/or variables. 57 * <li> 58 * Use 59 * "RacingThreadsTest.runTest(driver, workers)" 60 * to run the test. If the test fails, then a RuntimeException 61 * is thrown. 62 * </ul> 63 * 64 * The RacingThreadsTest class provides many methods that can be 65 * overridden in order to provide test specific semantics at each 66 * identified test execution point. At a minimum, your test's 67 * subclass needs to override the 68 * "void executeRace(WorkerThread)" 69 * method in order to exercise your race condition and it needs to 70 * override the 71 * "void checkRaceResults(DriverThread)" 72 * method in order to check the results of the race. Your 73 * checkRaceResults() method should call the 74 * "int incAndGetFailCnt()" 75 * method when it detects a failure. It can also call the 76 * "void unexpectedException(Thread, Exception)" 77 * method if it detects an unexpected exception; this will cause 78 * an error message to be output and the failure count to be 79 * incremented. When the RacingThreadsTest.runTest() method is 80 * done running the races, if there is a non-zero failure count, 81 * then a RuntimeException will be thrown. 82 * <p> 83 * The RacingThreadsTest class uses three internal barriers to 84 * coordinate actions between the DriverThread and the WorkerThreads. 85 * These barriers should not be managed or used by your test's 86 * subclass and are only mentioned here to provide clarity about 87 * interactions between the DriverThread and the WorkerThreads. 88 * The following transaction diagram shows when the different 89 * RacingThreadsTest methods are called relative to the different 90 * barriers: 91 * 92 * <pre> 93 * DriverThread WorkerThread-0 WorkerThread-N-1 94 * --------------------- --------------------- --------------------- 95 * run(workers) 96 * oneTimeDriverInit() 97 * <start WorkerThreads> run() run() 98 * <top of race loop> : : 99 * perRaceDriverInit() oneTimeWorkerInit() oneTimeWorkerInit() 100 * : <top of race loop> <top of race loop> 101 * : perRaceWorkerInit() perRaceWorkerInit() 102 * startBarrier startBarrier startBarrier 103 * : executeRace() executeRace() 104 * finishBarrier finishBarrier finishBarrier 105 * checkRaceResults() : : 106 * resetBarrier resetBarrier resetBarrier 107 * perRaceDriverEpilog() perRaceWorkerEpilog() perRaceWorkerEpilog() 108 * <repeat race or done> <repeat race or done> <repeat race or done> 109 * : oneTimeWorkerEpilog() oneTimeWorkerEpilog() 110 * <join WorkerThreads> <WorkerThread ends> <WorkerThread ends> 111 * oneTimeDriverEpilog() 112 * <DriverThread ends> 113 * </pre> 114 * 115 * Just to be clear about the parallel parts of this infrastructure: 116 * <ul> 117 * <li> 118 * After the DriverThread starts the WorkerThreads, the DriverThread 119 * and the WorkerThreads are running in parallel until the startBarrier 120 * is reached. 121 * <li> 122 * After the WorkerThreads leave the startBarrier, they are running 123 * the code in executeRace() in parallel which is the whole point 124 * of this class. 125 * <li> 126 * The DriverThread heads straight to the finishBarrier and waits for 127 * the WorkerThreads to get there. 128 * <li> 129 * After the DriverThread leaves the finishBarrier, it checks the 130 * results of the race. 131 * <li> 132 * The WorkerThreads head straight to the resetBarrier and wait for 133 * the DriverThread to get there. 134 * <li> 135 * If this is not the last race, then after the DriverThread and 136 * WorkerThreads leave the resetBarrier, the DriverThread and the 137 * WorkerThreads are running in parallel until the startBarrier 138 * is reached. 139 * <li> 140 * If this is the last race, then after the DriverThread and 141 * WorkerThreads leave the resetBarrier, the DriverThread and the 142 * WorkerThreads are running in parallel as each WorkerThread ends. 143 * <li> 144 * The DriverThread waits for the WorkerThreads to end and 145 * then it ends 146 * </ul> 147 * 148 * Once the DriverThread has ended, the RacingThreadsTest.runTest() 149 * method checks the failure count. If there were no failures, then 150 * a "Test PASSed" message is printed. Otherwise, the failure count 151 * is printed, a "Test FAILed" message is printed and a RuntimeException 152 * is thrown. 153 */ 154 public class RacingThreadsTest { 155 /** 156 * name of the test 157 */ 158 public final String TEST_NAME; 159 /** 160 * maximum number of test iterations (race loops) 161 */ 162 public final int N_LOOPS; 163 /** 164 * the maximum number of seconds to execute the test loop 165 */ 166 public final int N_SECS; 167 /** 168 * number of WorkerThreads 169 */ 170 public final int N_THREADS; 171 172 /** 173 * Creates a test with the specified name and the specified number 174 * of WorkerThreads that execute the test code in parallel up to 175 * n_loops iterations or n_secs seconds. The RacingThreadsTest 176 * class is extended in order to provide the test specific variables 177 * and/or code. 178 * @param name the name of the test 179 * @param n_threads the number of WorkerThreads 180 * @param n_loops the maximum number of test iterations 181 * @param n_secs the maximum number of seconds to execute the test loop 182 */ RacingThreadsTest(String name, int n_threads, int n_loops, int n_secs)183 RacingThreadsTest(String name, int n_threads, int n_loops, int n_secs) { 184 TEST_NAME = name; 185 N_THREADS = n_threads; 186 N_LOOPS = n_loops; 187 N_SECS = n_secs; 188 189 finishBarrier = new CyclicBarrier(N_THREADS + 1); 190 resetBarrier = new CyclicBarrier(N_THREADS + 1); 191 startBarrier = new CyclicBarrier(N_THREADS + 1); 192 } 193 194 195 /** 196 * Entry point for exercising the RacingThreadsTest class. 197 */ main(String[] args)198 public static void main(String[] args) { 199 // a dummy test: 200 // - 2 threads 201 // - 3 loops 202 // - 2 seconds 203 // - standard DriverThread 204 // - standard WorkerThread 205 RacingThreadsTest test = new RacingThreadsTest("dummy", 2, 3, 2); 206 DriverThread driver = new DriverThread(test); 207 WorkerThread[] workers = new WorkerThread[2]; 208 for (int i = 0; i < workers.length; i++) { 209 workers[i] = new WorkerThread(i, test); 210 } 211 test.runTest(driver, workers); 212 } 213 214 private static volatile boolean done = false; // test done flag 215 216 // # of fails; AtomicInteger since any WorkerThread can increment 217 private static final AtomicInteger failCnt = new AtomicInteger(); 218 // # of loops; volatile is OK since only DriverThread increments 219 // but using AtomicInteger for consistency 220 private static final AtomicInteger loopCnt = new AtomicInteger(); 221 private static boolean verbose 222 = Boolean.getBoolean("RacingThreadsTest.verbose"); 223 224 // barriers for starting, finishing and resetting the race 225 private final CyclicBarrier finishBarrier; 226 private final CyclicBarrier resetBarrier; 227 private final CyclicBarrier startBarrier; 228 229 230 /** 231 * Get current done flag value. 232 * @return the current done flag value 233 */ getDone()234 public boolean getDone() { 235 return done; 236 } 237 238 /** 239 * Set done flag to specified value. 240 * @param v the new done flag value 241 */ setDone(boolean v)242 public void setDone(boolean v) { 243 done = v; 244 } 245 246 /** 247 * Get current failure counter value. 248 * @return the current failure count 249 */ getFailCnt()250 public int getFailCnt() { 251 return failCnt.get(); 252 } 253 254 /** 255 * Increment and get current failure counter value. 256 * @return the current failure count after incrementing 257 */ incAndGetFailCnt()258 public int incAndGetFailCnt() { 259 return failCnt.incrementAndGet(); 260 } 261 262 /** 263 * Get current loop counter value. 264 * @return the current loop count 265 */ getLoopCnt()266 public int getLoopCnt() { 267 return loopCnt.get(); 268 } 269 270 /** 271 * Increment and get current loop counter value. 272 * @return the current loop count after incrementing 273 */ incAndGetLoopCnt()274 public int incAndGetLoopCnt() { 275 return loopCnt.incrementAndGet(); 276 } 277 278 /** 279 * Get current verbose flag value. 280 * @return the current verbose flag value 281 */ getVerbose()282 public boolean getVerbose() { 283 return verbose; 284 } 285 286 /** 287 * Set verbose flag to specified value. 288 * @param v the new verbose flag value 289 */ setVerbose(boolean v)290 public void setVerbose(boolean v) { 291 verbose = v; 292 } 293 294 /** 295 * Run the test with the specified DriverThread and the 296 * specified WorkerThreads. 297 * @param driver the DriverThread for running the test 298 * @param workers the WorkerThreads for executing the race 299 * @exception RuntimeException the test has failed 300 */ runTest(DriverThread driver, WorkerThread[] workers)301 public void runTest(DriverThread driver, WorkerThread[] workers) { 302 driver.run(workers); 303 304 try { 305 driver.join(); 306 } catch (InterruptedException ie) { 307 unexpectedException(Thread.currentThread(), ie); 308 // fall through to test failed below 309 } 310 311 if (failCnt.get() == 0) { 312 System.out.println(TEST_NAME + ": Test PASSed."); 313 } else { 314 System.out.println(TEST_NAME + ": failCnt=" + failCnt.get()); 315 System.out.println(TEST_NAME + ": Test FAILed."); 316 throw new RuntimeException("Test Failed"); 317 } 318 } 319 320 /** 321 * Helper method for reporting an unexpected Exception and 322 * calling incAndGetFailCnt(); 323 * @param t the Thread that caught the exception 324 * @param e the Exception that was caught 325 */ unexpectedException(Thread t, Exception e)326 public void unexpectedException(Thread t, Exception e) { 327 System.err.println(t.getName() + ": ERROR: unexpected exception: " + e); 328 incAndGetFailCnt(); // ignore return 329 } 330 331 332 // The following methods are typically overridden by the subclass 333 // of RacingThreadsTest to provide test specific semantics at each 334 // identified test execution point: 335 336 /** 337 * Initialize 1-time items for the DriverThread. 338 * Called by the DriverThread before WorkerThreads are started. 339 * @param dt the DriverThread 340 */ oneTimeDriverInit(DriverThread dt)341 public void oneTimeDriverInit(DriverThread dt) { 342 if (verbose) 343 System.out.println(dt.getName() + ": oneTimeDriverInit() called"); 344 } 345 346 /** 347 * Initialize 1-time items for a WorkerThread. Called by a 348 * WorkerThread after oneTimeDriverInit() and before the 349 * WorkerThread checks in with startBarrier. May execute in 350 * parallel with perRaceDriverInit() or with another 351 * WorkerThread's oneTimeWorkerInit() call or another 352 * WorkerThread's perRaceWorkerInit() call. 353 * @param wt the WorkerThread 354 */ oneTimeWorkerInit(WorkerThread wt)355 public void oneTimeWorkerInit(WorkerThread wt) { 356 if (verbose) 357 System.out.println(wt.getName() + ": oneTimeWorkerInit() called"); 358 } 359 360 /** 361 * Initialize per-race items for the DriverThread. Called by the 362 * DriverThread before it checks in with startBarrier. May execute 363 * in parallel with oneTimeWorkerInit() and perRaceWorkerInit() 364 * calls. After any race except for the last race, this method may 365 * execute in parallel with perRaceWorkerEpilog(). 366 * @param dt the DriverThread 367 */ perRaceDriverInit(DriverThread dt)368 public void perRaceDriverInit(DriverThread dt) { 369 if (verbose) 370 System.out.println(dt.getName() + ": perRaceDriverInit() called"); 371 } 372 373 /** 374 * Initialize per-race items for a WorkerThread. Called by each 375 * WorkerThread before it checks in with startBarrier. On the first 376 * call, this method may execute in parallel with another 377 * WorkerThread's oneTimeWorkerInit() call. On any call, this method 378 * may execute in parallel with perRaceDriverInit() or another 379 * WorkerThread's perRaceWorkerInit() call. After any race except 380 * for the last race, this method may execute in parallel with 381 * perRaceDriverEpilog() or another WorkerThread's 382 * perRaceWorkerEpilog() call. 383 * @param wt the WorkerThread 384 */ perRaceWorkerInit(WorkerThread wt)385 public void perRaceWorkerInit(WorkerThread wt) { 386 if (verbose) 387 System.out.println(wt.getName() + ": perRaceWorkerInit() called"); 388 } 389 390 /** 391 * Execute the race in a WorkerThread. Called by each WorkerThread 392 * after it has been released from startBarrier. 393 * @param wt the WorkerThread 394 */ executeRace(WorkerThread wt)395 public void executeRace(WorkerThread wt) { 396 if (verbose) 397 System.out.println(wt.getName() + ": executeRace() called"); 398 } 399 400 /** 401 * Check race results in the DriverThread. Called by the DriverThread 402 * after it has been released from finishBarrier and before the 403 * DriverThread checks in with resetBarrier. 404 * @param dt the DriverThread 405 */ checkRaceResults(DriverThread dt)406 public void checkRaceResults(DriverThread dt) { 407 if (verbose) 408 System.out.println(dt.getName() + ": checkRaceResults() called"); 409 } 410 411 /** 412 * Handle end-of-race items for the DriverThread. Called by the 413 * DriverThread after it has been released from resetBarrier and 414 * before the DriverThread checks in again with startBarrier. Can 415 * execute in parallel with perRaceWorkerEpilog(). If this is not 416 * the last race, can execute in parallel with perRaceWorkerInit(). 417 * If this is the last race, can execute in parallel with 418 * oneTimeWorkerEpilog(). 419 * @param dt the DriverThread 420 */ perRaceDriverEpilog(DriverThread dt)421 public void perRaceDriverEpilog(DriverThread dt) { 422 if (verbose) 423 System.out.println(dt.getName() + ": perRaceDriverEpilog() called"); 424 } 425 426 /** 427 * Handle end-of-race items for a WorkerThread. Called by each 428 * WorkerThread after it has been released from resetBarrier and 429 * before the WorkerThread checks in again with startBarrier. 430 * Can execute in parallel with perRaceDriverEpilog() or another 431 * WorkerThread's perRaceWorkerEpilog() call. If this is not the 432 * last race, can execute in parallel with perRaceDriverInit(), 433 * or another WorkerThread's perRaceWorkerInit() call. If this 434 * is the last race, can execute in parallel with another 435 * WorkerThread's oneTimeWorkerEpilog() call. 436 * @param wt the WorkerThread 437 */ perRaceWorkerEpilog(WorkerThread wt)438 public void perRaceWorkerEpilog(WorkerThread wt) { 439 if (verbose) 440 System.out.println(wt.getName() + ": perRaceWorkerEpilog() called"); 441 } 442 443 /** 444 * Handle end-of-test items for a WorkerThread. Called by each 445 * WorkerThread after it has detected that all races are done and 446 * before oneTimeDriverEpilog() is called. Can execute in parallel 447 * with perRaceDriverEpilog(), with another WorkerThread's 448 * perRaceWorkerEpilog() call or with another WorkerThread's 449 * oneTimeWorkerEpilog() call. 450 * @param wt the WorkerThread 451 */ oneTimeWorkerEpilog(WorkerThread wt)452 public void oneTimeWorkerEpilog(WorkerThread wt) { 453 if (verbose) 454 System.out.println(wt.getName() + ": oneTimeWorkerEpilog() called"); 455 } 456 457 /** 458 * Handle end-of-test items for the DriverThread. Called by the 459 * DriverThread after all the WorkerThreads have called 460 * oneTimeWorkerEpilog(). 461 * @param dt the DriverThread 462 */ oneTimeDriverEpilog(DriverThread dt)463 public void oneTimeDriverEpilog(DriverThread dt) { 464 if (verbose) 465 System.out.println(dt.getName() + ": oneTimeDriverEpilog() called"); 466 } 467 468 469 /** 470 * DriverThread for executing the test. 471 */ 472 public static class DriverThread extends Thread { 473 private final RacingThreadsTest test; 474 475 /** 476 * Create the test DriverThread that manages all the WorkerThreads. 477 * The DriverThread class can be extended to provide test specific 478 * variables and/or code. However, that is typically done in the 479 * subclass of RacingThreadsTest. 480 * @parameter test the RacingThreadsTest being run 481 */ DriverThread(RacingThreadsTest test)482 DriverThread(RacingThreadsTest test) { 483 super("DriverThread"); 484 this.test = test; 485 } 486 run(WorkerThread[] workers)487 private void run(WorkerThread[] workers) { 488 System.out.println(getName() + ": is starting."); 489 System.out.println(getName() + ": # WorkerThreads: " + test.N_THREADS); 490 System.out.println(getName() + ": max # loops: " + test.N_LOOPS); 491 System.out.println(getName() + ": max # secs: " + test.N_SECS); 492 493 // initialize 1-time items for the DriverThread 494 test.oneTimeDriverInit(this); 495 496 // start all the threads 497 for (int i = 0; i < workers.length; i++) { 498 workers[i].start(); 499 } 500 501 // All WorkerThreads call oneTimeWorkerInit() and 502 // perRaceWorkerInit() on the way to startBarrier. 503 504 long endTime = System.currentTimeMillis() + test.N_SECS * 1000; 505 506 for (; !test.getDone() && test.getLoopCnt() < test.N_LOOPS; 507 test.incAndGetLoopCnt()) { 508 509 if (test.getVerbose() && (test.N_LOOPS < 10 || 510 (test.getLoopCnt() % (test.N_LOOPS / 10)) == 0)) { 511 System.out.println(getName() + ": race loop #" 512 + test.getLoopCnt()); 513 } 514 515 // initialize per-race items for the DriverThread 516 test.perRaceDriverInit(this); 517 518 try { 519 // we've setup the race so start it when all 520 // WorkerThreads get to the startBarrier 521 test.startBarrier.await(); 522 } catch (BrokenBarrierException bbe) { 523 test.unexpectedException(this, bbe); 524 return; 525 } catch (InterruptedException ie) { 526 test.unexpectedException(this, ie); 527 return; 528 } 529 530 // All WorkerThreads are racing via executeRace() 531 // at this point 532 533 // wait for all threads to finish the race 534 try { 535 test.finishBarrier.await(); 536 } catch (BrokenBarrierException bbe) { 537 test.unexpectedException(this, bbe); 538 return; 539 } catch (InterruptedException ie) { 540 test.unexpectedException(this, ie); 541 return; 542 } 543 // All WorkerThreads are heading to resetBarrier at this 544 // point so we can check the race results before we reset 545 // for another race (or bail because we are done). 546 547 test.checkRaceResults(this); 548 549 if (test.getLoopCnt() + 1 >= test.N_LOOPS || 550 System.currentTimeMillis() >= endTime) { 551 // This is the last loop or we're out of time. 552 // Let test threads know we are done before we release 553 // them from resetBarrier 554 test.setDone(true); 555 } 556 557 // release the WorkerThreads from resetBarrier 558 try { 559 test.resetBarrier.await(); 560 } catch (BrokenBarrierException bbe) { 561 test.unexpectedException(this, bbe); 562 return; 563 } catch (InterruptedException ie) { 564 test.unexpectedException(this, ie); 565 return; 566 } 567 568 // All WorkerThreads call perRaceWorkerEpilog(). If 569 // this is not the last loop, then all WorkerThreads 570 // will also call perRaceWorkerInit() on the way to 571 // startBarrier. If this is the last loop, then all 572 // WorkerThreads will call oneTimeWorkerEpilog() on 573 // their way to ending. 574 575 // handle end-of-race items for the DriverThread 576 test.perRaceDriverEpilog(this); 577 } 578 579 System.out.println(getName() + ": completed " + test.getLoopCnt() 580 + " race loops."); 581 if (test.getLoopCnt() < test.N_LOOPS) { 582 System.out.println(getName() + ": race stopped @ " + test.N_SECS 583 + " seconds."); 584 } 585 586 for (int i = 0; i < workers.length; i++) { 587 try { 588 workers[i].join(); 589 } catch (InterruptedException ie) { 590 test.unexpectedException(this, ie); 591 return; 592 } 593 } 594 595 // handle end-of-test items for the DriverThread 596 test.oneTimeDriverEpilog(this); 597 598 System.out.println(getName() + ": is done."); 599 } 600 } 601 602 603 /** 604 * WorkerThread for executing the race. 605 */ 606 public static class WorkerThread extends Thread { 607 private final RacingThreadsTest test; 608 private final int workerNum; 609 610 /** 611 * Creates WorkerThread-N that executes the test code. The 612 * WorkerThread class can be extended to provide test thread 613 * specific variables and/or code. 614 * @param workerNum the number for the new WorkerThread 615 * @parameter test the RacingThreadsTest being run 616 */ WorkerThread(int workerNum, RacingThreadsTest test)617 WorkerThread(int workerNum, RacingThreadsTest test) { 618 super("WorkerThread-" + workerNum); 619 this.test = test; 620 this.workerNum = workerNum; 621 } 622 623 /** 624 * get the WorkerThread's number 625 * @return the WorkerThread's number 626 */ getWorkerNum()627 public int getWorkerNum() { 628 return workerNum; 629 } 630 631 /** 632 * Run the race in a WorkerThread. 633 */ run()634 public void run() { 635 System.out.println(getName() + ": is running."); 636 637 // initialize 1-time items for the WorkerThread 638 test.oneTimeWorkerInit(this); 639 640 while (!test.getDone()) { 641 // initialize per-race items for the WorkerThread 642 test.perRaceWorkerInit(this); 643 644 try { 645 test.startBarrier.await(); // wait for race to start 646 } catch (BrokenBarrierException bbe) { 647 test.unexpectedException(this, bbe); 648 return; 649 } catch (InterruptedException ie) { 650 test.unexpectedException(this, ie); 651 return; 652 } 653 654 // execute the race for the WorkerThread 655 test.executeRace(this); 656 657 try { 658 test.finishBarrier.await(); // this thread is done 659 } catch (BrokenBarrierException bbe) { 660 test.unexpectedException(this, bbe); 661 return; 662 } catch (InterruptedException ie) { 663 test.unexpectedException(this, ie); 664 return; 665 } 666 667 try { 668 test.resetBarrier.await(); // wait for race to reset 669 } catch (BrokenBarrierException bbe) { 670 test.unexpectedException(this, bbe); 671 return; 672 } catch (InterruptedException ie) { 673 test.unexpectedException(this, ie); 674 return; 675 } 676 677 // handle end-of-race items for the WorkerThread 678 test.perRaceWorkerEpilog(this); 679 } 680 681 // handle end-of-test items for the WorkerThread 682 test.oneTimeWorkerEpilog(this); 683 684 System.out.println(getName() + ": is ending."); 685 } 686 } 687 } 688