7/31/2019 Test-First Java Concurrency for the Classroom
1/53
Test-First Java Concurrency
for the Classroom
SIGCSE 2010
Mathias Ricken and Robert Cartwright
Rice University
March 12, 2009
7/31/2019 Test-First Java Concurrency for the Classroom
2/53
2
Two Trends
Test-driven development Concurrent programming
Brian Goetz, Java Concurrency inPractice, Addison-Wesley, 2006
7/31/2019 Test-First Java Concurrency for the Classroom
3/53
3
Unit Testing Benefits
Occurs early Automates testing
Keeps the shared repository clean
Prevents bugs from reoccurring
Allows safe refactoring
Serves as documentation
7/31/2019 Test-First Java Concurrency for the Classroom
4/53
4
Unit Testing in Assignments
Hand out test cases to students Improves confidence and understanding
Instill good practices Require students to extend test suites
Automated grading Part graded automatically, part by hand
7/31/2019 Test-First Java Concurrency for the Classroom
5/53
5
Moores Law Requires Concurrency
Adopted from
Sutter 2009
7/31/2019 Test-First Java Concurrency for the Classroom
6/53
6
Concurrency Is Difficult
Unit testing not effective in
multi-threaded programs
7/31/2019 Test-First Java Concurrency for the Classroom
7/53
7
Existing Unit Testing Frameworks
JUnit, TestNG
Dont detect test failures in threads other
than main thread Failures in event thread not detected either
Dont ensure that other threads terminate
Tests that should fail may succeed
7/31/2019 Test-First Java Concurrency for the Classroom
8/53
8
Sample JUnit Tests
publicclass SimpleTest extends TestCase {
public void testException() {
thrownew RuntimeException("booh!");
}
public void testAssertion() {assertEquals(0, 1);
}
}if (0!=1)
throw newAssertionFailedError();
} Both testsfail.
Both testsfail.
7/31/2019 Test-First Java Concurrency for the Classroom
9/53
9
JUnit Test with Child Thread
publicclass SimpleTest extends TestCase {
public void testException() {
new Thread() {
public void run() {
thrownew RuntimeException("booh!");}
}.start();
}
}
new Thread() {
public void run() {
thrownew RuntimeException("booh!");}
}.start();
thrownew RuntimeException("booh!");
Mainthread
Child
thread
Main
thread
Child
thread
spawns
uncaught!
end of
testsuccess!
Uncaught exception,
test should fail but
does not!
7/31/2019 Test-First Java Concurrency for the Classroom
10/53
10
ConcJUnit
Backward compatible replacement for JUnit Detects exceptions in all threads
Exception handler for all child threads and the
event thread Ensures that child threads have terminated
and event thread is done
Enumerate live threads after test Inspect event queue
Requires all child threads to be joined
Analyze join graph
7/31/2019 Test-First Java Concurrency for the Classroom
11/53
11
Thread Creation Coordinates
In Thread.start() record stack trace ofThread.currentThread()
Easy to find where a thread that caused afailure was started
Also shows where threads that outlived thetest were started
7/31/2019 Test-First Java Concurrency for the Classroom
12/53
12
Creation Coordinates Example
class Main {
void foo() {
// which one?
new Helper(true).start();new Helper(false).start();
// ...
}
}
AssertionError:
at Helper.m(Helper.java:2)
at Helper.run(Helper.java:3)
Started at:
at Main.foo(Main.java:4)
at Main.bar(Main.java:15)
at Main.main(Main.java:25)
class Helper extends Thread {
void m() { if (b) Assert.fail(); }
public void run() { m(); }
private boolean b;
// }
7/31/2019 Test-First Java Concurrency for the Classroom
13/53
13
ConcJUnit Demo
7/31/2019 Test-First Java Concurrency for the Classroom
14/53
14
Concurrency Examples
In-class discussion Multi-threaded counter: data races Multi-threaded bank: deadlock
Homework
Bounded buffer
Readers-writer lock
Test suite handed out to help students
Multi-threaded Breakout
7/31/2019 Test-First Java Concurrency for the Classroom
15/53
15
Example: Counter
Class that can increment an integer variableN times
Write test first
public class CounterTest extends TestCase {final long PER_THREAD = 1000000;
public void testSingle() {
Counter c = new Counter();
c.incrementNTimes(PER_THREAD);
assertEquals(PER_THREAD, c.getCount());
}
}
7/31/2019 Test-First Java Concurrency for the Classroom
16/53
16
Counter: Implementation
Write implementation
public class Counter {
private long count = 0;
public long getCount() { return count; }
public void incrementNTimes(long n) {
for(long i=0; i
7/31/2019 Test-First Java Concurrency for the Classroom
17/53
17
Counter: Multi-threaded Test
Write multi-threaded testpublic void testMulti() {
final Counter c = new Counter();
for(int i=0; i
7/31/2019 Test-First Java Concurrency for the Classroom
18/53
18
Shared Data
Why does the multi-threaded counter test fail?
Thecount field is shared among threads
The ++count operation is not atomic
Thread may be interrupted after reading count, butbefore writing back to count
count=0 regA=? regB=?
A1 regA = count; 0 0 ?
B1 regB = count; 0 0 0A2 regA = regA + 1; 0 1 0
A3 count = regA; 1 1 0
B2 regB = regB + 1; 1 1 1
B3 count = regB; 1 1 1
7/31/2019 Test-First Java Concurrency for the Classroom
19/53
19
Data Races
Definition Two threads access the same data
At least one access is a write
Nothing prevents the order from changing
Would like code to execute atomically(without interruption)
Java does not support atomicity(for general code)
7/31/2019 Test-First Java Concurrency for the Classroom
20/53
20
Java Locks & Synchronized
Java provides lock objects andsynchronized blocks
synchronized(lock) { ++count; }
Thread must compete for ownership of lock
object before entering synchronized block
Synchronized block is not atomic
But once a thread has a lock object, no other
thread can execute code protected by the
same lock object
7/31/2019 Test-First Java Concurrency for the Classroom
21/53
21
Counter: Re-Write
Rewrite implementation
// ...
private Object lock = new Object();
public void incrementNTimes(long n) {
for(long i=0; i
7/31/2019 Test-First Java Concurrency for the Classroom
22/53
22
Concurrency Still Difficult
Even race-free, deadlock-free programsare not deterministic
Thread scheduling is essentially non-
deterministic
Different schedules may compute
different results
May or may not be acceptable, depending
on the task
7/31/2019 Test-First Java Concurrency for the Classroom
23/53
23
Multi-threaded Breakout
Uses ACM Java Task Force material Based on Breakout - Nifty Assignment by Eric
Roberts, SIGCSE 2006
Multiple balls, each in its own thread Atomicity assumption when removing bricks
Ends game before all bricks are removed
Other problems X,Y coordinate changes not atomic
X,Y coordinates not volatile or synchronized,event thread may never see the updates
Correctly synchronized version still notdeterministic
7/31/2019 Test-First Java Concurrency for the Classroom
24/53
24
Future Work
Testing all schedules is intractable
Insert random delays/yields beforesynchronization operations
Must considervolatile variable accesses tocomply with Java Memory Model
Re-run program several times
Can detect a number of sample problems
Record schedule, replay if test fails Makes failures reproducible if found
*3
7/31/2019 Test-First Java Concurrency for the Classroom
25/53
25
Conclusion
Unit testing has important benefits inindustry and in the classroom
Concurrent programming is becoming more
important, and its difficult
ConcJUnit helps
www.concutest.org
www.drjava.org
http://www.concutest.org/http://www.drjava.org/http://www.drjava.org/http://www.concutest.org/7/31/2019 Test-First Java Concurrency for the Classroom
26/53
Notes
7/31/2019 Test-First Java Concurrency for the Classroom
27/53
7/31/2019 Test-First Java Concurrency for the Classroom
28/53
28
publicclass Test extends TestCase {
public voidtestException() {
Thread t = new Thread(new Runnable() {
public void run() {
thrownew RuntimeException("booh!");
}
});
t.start();
while(t.isAlive()) {
try { t.join(); }
catch(InterruptedException ie) { }
}
}
}
Thread t = new Thread(new Runnable() {
public voidrun() {
thrownew RuntimeException("booh!");
}
});
t.start();
while(t.isAlive()) {
try { t.join(); }
catch(InterruptedException ie) { }
}
thrownew RuntimeException("booh!");
Loop sincejoin() may end
spuriously
4.
Spurious Wakeup
7/31/2019 Test-First Java Concurrency for the Classroom
29/53
Image Attribution
7/31/2019 Test-First Java Concurrency for the Classroom
30/53
30
Image Attribution
1. Left image on Two Trends: Test Driven
Development, Damian Cugley.
2. Right image on Two Trends: adapted from Brian
Goetz et al. 2006, Addison Wesley.
3. Graph on Moores Law:
Adapted from Herb Sutter 2009
4. Image on Concurrency Is Difficult:
Caption Fridays
http://www.oxfordcc.co.uk/newsletters/003/softwaredevelopmentpractices.htmlhttp://www.oxfordcc.co.uk/newsletters/003/softwaredevelopmentpractices.htmlhttp://www.javaconcurrencyinpractice.com/http://www.javaconcurrencyinpractice.com/http://www.gotw.ca/publications/concurrency-ddj.htmhttp://www.captionfridays.com/2008/40-i-want-to-go-first/http://www.captionfridays.com/2008/40-i-want-to-go-first/http://www.gotw.ca/publications/concurrency-ddj.htmhttp://www.javaconcurrencyinpractice.com/http://www.javaconcurrencyinpractice.com/http://www.oxfordcc.co.uk/newsletters/003/softwaredevelopmentpractices.htmlhttp://www.oxfordcc.co.uk/newsletters/003/softwaredevelopmentpractices.html7/31/2019 Test-First Java Concurrency for the Classroom
31/53
Extra Slides
7/31/2019 Test-First Java Concurrency for the Classroom
32/53
32
Changes to JUnit (1 of 3)
Thread group with exception handler JUnit test runs in a separate thread, not main thread
Child threads are created in same thread group
When test ends, check if handler was invoked
Reasoning:
Uncaught exceptions in all threadsmust causefailure
7/31/2019 Test-First Java Concurrency for the Classroom
33/53
33
JUnit Test with Child Thread
publicclass Test extends TestCase {
public voidtestException() {
new Thread(new Runnable() {
public void run() {
thrownew RuntimeException("booh!");
}
}).start();
}
}
new Thread(new Runnable() {
public voidrun() {
thrownew RuntimeException("booh!");
}
}).start();
thrownew RuntimeException("booh!");
invokes
TestGroups Uncaught
Exception Handler
7/31/2019 Test-First Java Concurrency for the Classroom
34/53
34
JUnit Test with Child Thread
publicclass Test extends TestCase {
public voidtestException() {
new Thread() {
public void run() {
thrownew RuntimeException("booh!");
}
}.start();
}
}
new Thread() {
public voidrun() {
thrownew RuntimeException("booh!");
}
}.start();
thrownew RuntimeException("booh!");
Test
thread
Child
thread
uncaught!
end of test
Mainthread
spawns and joins resumescheck
exception
handlerinvokes
exception
handler
failure!
7/31/2019 Test-First Java Concurrency for the Classroom
35/53
35
Child Thread Outlives Parent
publicclass Test extends TestCase {
public voidtestException() {
new Thread() {
public void run() {
thrownew RuntimeException("booh!");
}
}.start();
}
}
new Thread() {
public voidrun() {
thrownew RuntimeException("booh!");
}
}.start();
thrownew RuntimeException("booh!");
Test
thread
Child
thread
uncaught!end of test
success!
invokes
exceptionhandler
Mainthread
check exception
handler
Too late!
7/31/2019 Test-First Java Concurrency for the Classroom
36/53
36
Changes to JUnit (2 of 3)
Check for living child threads after test ends
Reasoning: Uncaught exceptions in all threadsmust cause
failure
If the test is declared a success before all childthreads have ended, failures may go unnoticed
Therefore, all child threads must terminate
before test ends
7/31/2019 Test-First Java Concurrency for the Classroom
37/53
37
Check for Living Child Threads
publicclass Test extends TestCase {
public voidtestException() {
new Thread() {
public void run() {
thrownew RuntimeException("booh!");
}
}.start();
}
}
new Thread() {
public voidrun() {
thrownew RuntimeException("booh!");
}
}.start();
thrownew RuntimeException("booh!");
Test
thread
Child
thread
uncaught!end of test
failure!
invokes
groupshandler
Mainthread
check for living
child threads
check groups
handler
7/31/2019 Test-First Java Concurrency for the Classroom
38/53
7/31/2019 Test-First Java Concurrency for the Classroom
39/53
39
Changes to JUnit (3 of 3)
Check if any child threads were not joined
Reasoning: All child threads must terminate before test ends
Without join()operation, a test may get lucky
Require all child threads to be joined
7/31/2019 Test-First Java Concurrency for the Classroom
40/53
40
Fork/Join Model
Parent thread joins with each of its childthreads
May be too limited for a general-purposeprogramming language
Child
thread 1
Child
thread 2
Mainthread
7/31/2019 Test-First Java Concurrency for the Classroom
41/53
41
Example of Other Join Models
Chain of child threads guaranteed tooutlive parent
Main thread joins with last thread of
chain
Child
thread 1
Child
thread 2
Main
thread
Child
thread 3
7/31/2019 Test-First Java Concurrency for the Classroom
42/53
42
Modifying the Java Runtime
Changing Thread.start()and join()
Need to modify Java Runtime Library
Utility to process users rt.jar file
Put new jar file on boot classpath:-Xbootclasspath/p:newrt.jar
Still works without modified Thread class
Just does not emit lucky warnings
7/31/2019 Test-First Java Concurrency for the Classroom
43/53
43
Join with All Offspring Threads
Main thread joins with all offspringthreads, regardless of what thread
spawned them
Child
thread 1
Child
thread 2
Main
thread
7/31/2019 Test-First Java Concurrency for the Classroom
44/53
44
Generalize to Join Graph
Threads as nodes; edges to joinedthread
Test is well-formed as long as all
threads are reachable from main thread
Child
thread 1
Child
thread 2
Main
thread
Child
thread 3
MT
CT1
CT2
CT3
7/31/2019 Test-First Java Concurrency for the Classroom
45/53
45
Child
thread 1
Childthread 2
Main
threadMT
CT1
CT2
Childthread 1
Child
thread 2
Main
threadMT
CT1
CT2
Join Graph Examples
7/31/2019 Test-First Java Concurrency for the Classroom
46/53
46
Childthread 1
Child
thread 2
Main
threadMT
CT1
CT2
Unreachable Nodes
An unreachable node has not beenjoined
Child thread may outlive the test
C i h G h
7/31/2019 Test-First Java Concurrency for the Classroom
47/53
47
child
Thread
main
ThreadMT
CT
Constructing the Graph
// in mainThread
childThread.start();
Add node forchildThread
C t ti th G h
7/31/2019 Test-First Java Concurrency for the Classroom
48/53
48
// in mainThreadchildThread.join();
When leaving join(), add edge frommainThread to childThread
child
Thread
main
ThreadMT
CT
Constructing the Graph
*2
E l M lti th d d B k
7/31/2019 Test-First Java Concurrency for the Classroom
49/53
49
Example: Multi-threaded Bank
Program simulating checking accounts
Account balances are shared data
To avoid data races, use synchronized
Need access to two accounts for transfers
synchronized(locks[from]) {
synchronized(locks[to]) {
accounts[from] -= amount;
accounts[to] += amount;
}
}Test hangs!
D dl k
7/31/2019 Test-First Java Concurrency for the Classroom
50/53
50
Deadlock
Thread A transfers from account 0 to 1
Thread B transfers from account 1 to 0
Thread A gets interrupted after acquiring locks[0]
// thread A // thread B
synchronized(locks[0]) {
synchronized(locks[1]) {
synchronized(locks[0])
// cant continue, locks[0]
// is owned by thread A */synchronized(locks[1])
// cant continue, locks[1]
// is owned by thread B */
L k A i iti O d
7/31/2019 Test-First Java Concurrency for the Classroom
51/53
51
Lock Acquisition Order
No deadlock if both threads had attempted toacquire lock 0 first
When acquiring more than one lock object,always acquire them in the same order e.g. acquire lower accounts lock object first
synchronized(locks[Math.min(from,to)]) {
synchronized(locks[Math.max(from,to)]) {
accounts[from] -= amount;accounts[to] += amount;
}
}
H k A i t
7/31/2019 Test-First Java Concurrency for the Classroom
52/53
52
Homework Assignment
Common structures students will seetime and again
Bounded buffer
Readers-writer lock
Grade correctness and efficiency, e.g.
Maximize concurrency
Only wake up as few threads as possible
Provide students with test suites
M Th k T
7/31/2019 Test-First Java Concurrency for the Classroom
53/53
Many Thanks To
My advisor Corky Cartwright
My committee members
Walid Taha David Scott
Bill Scherer
NSF and Texas ATP For providing partial funding