Objects and Patterns Workshop
Designing with Exceptions and Threads
Agenda
-
Use exceptions to indicate a broken semantic contract.
-
Throw exceptions on abnormal conditions.
-
Use a checked exception for conditions client code can reasonably be expected to handle.
-
Throw a different exception type for each abnormal condition.
-
Idiom: The Thread-Safe Object
Broken Semantic Contract
-
Guideline: Use exceptions to indicate a broken semantic contract.
-
Pre-conditions, Post-conditions, Invariants
-
charAt(int index)
pre-condition: 0 <= index <
length()
-
If you pass
-1
to charAt()
, you'll get an
(unchecked) StringIndexOutOfBoundsException
-
If you meet the pre-condition, but
String
is somehow unable
to meet the post-conditions and invariants, it will also throw an
exception.
Discussion
-
Guideline: Use exceptions to indicate a broken semantic contract.
Abnormal Conditions
-
Guideline: Throw exceptions on abnormal conditions.
-
But what's an abnormal condition?
-
Avoid using exceptions to indicate conditions that can reasonably be expected
as part of the typical functioning of the method.
Example: FileInputStream
-
FileInputStream
returns -1 on end of file.
-
Even if you call
read()
again after hitting EOF, you'll
still get -1.
-
Says: hitting EOF is a normal condition to encounter when using the
read()
method
1 // In file ex9/Example9a.java
2 import java.io.*;
3 class Example9a {
4
5 public static void main(String[] args)
6 throws IOException {
7
8 if (args.length == 0) {
9 System.out.println("Must give filename as first arg.");
10 return;
11 }
12
13 FileInputStream in;
14 try {
15 in = new FileInputStream(args[0]);
16 }
17 catch (FileNotFoundException e) {
18 System.out.println("Can't find file: " + args[0]);
19 return;
20 }
21
22 int ch;
23 while ((ch = in.read()) != -1) {
24 System.out.print((char) ch);
25 }
26 System.out.println();
27
28 in.close();
29 }
30
Example: StringTokenizer
and Stack
-
StringTokenizer
makes you ask hasMoreTokens()
before
calling nextToken()
-
If you call
nextToken()
after hasMoreTokens()
returns (or would return) false
, you get an (unchecked)
NoSuchElementException
-
Says: hitting "end of tokens" is an abnormal condition to encounter when
using the
nextToken()
method, because you are supposed to
check with hasTokens()
first
1 // In file ex9/Example9c.java
2 // This program prints the white-space separated tokens of an
3 // ASCII file in reverse order of their appearance in the file.
4 import java.io.*;
5 import java.util.*;
6 class Example9c {
7
8 public static void main(String[] args)
9 throws IOException {
10
11 if (args.length == 0) {
12 System.out.println("Must give filename as first arg.");
13 return;
14 }
15
16 FileInputStream in = null;
17 try {
18 in = new FileInputStream(args[0]);
19 }
20 catch (FileNotFoundException e) {
21 System.out.println("Can't find file: " + args[0]);
22 return;
23 }
24
25 // Read file into a StringBuffer
26 StringBuffer buf = new StringBuffer();
27 try {
28 int ch;
29 while ((ch = in.read()) != -1) {
30 buf.append((char) ch);
31 }
32 }
33 finally {
34 in.close();
35 }
36
37 // Separate StringBuffer into tokens and
38 // push each token into a Stack
39 StringTokenizer tok = new StringTokenizer(buf.toString());
40 Stack stack = new Stack();
41 while (tok.hasMoreTokens()) {
42 stack.push(tok.nextToken());
43 }
44
45 // Print out tokens in reverse order.
46 while (!stack.empty()) {
47 System.out.println((String) stack.pop());
48 }
49 }
50
-
Stack
makes you ask empty()
before
calling pop()
-
If you call
pop()
after empty()
returns (or would return) true
, you get an (unchecked)
EmptyStackException
-
Says: hitting "empty stack" is an abnormal condition to encounter when
using the
pop()
method, because you are supposed to
check with empty()
first
Why Just Abnormal Conditions?
-
Exceptions are free unless thrown...
-
But if thrown, they are very expensive.
-
Separating error handling code that almost never happens from code that
represents the normal path of execution can make code more readable
-
But if the normal path of execution has to go through lots of catch
clauses, the code is harder to read
-
Abnormal condition is what an exception means, because broken contracts should be relatively rare
Discussion
-
Guideline: Throw exceptions on abnormal conditions.
Exceptions and throws
-
Guideline: Use a checked exception for conditions client code can reasonably be expected to handle.
-
Java requires that a method declare in a
throws
clause the
exceptions that it may throw
-
A method's
throws
clause indicates to client programmers what
exceptions they may have to deal with when they invoke the method
-
Only exceptions that will cause a method to complete abruptly should
appear in its
throws
clause:
// In file VirtualPerson.java
class VirtualPerson {
public void drinkCoffee(CoffeeCup cup)
throws TooColdException,
TemperatureException,
UnusualTasteException {
try {
int i = (int) (Math.random() * 4.0);
switch (i) {
case 0:
throw new TooHotException();
case 1:
throw new TooColdException();
case 2:
throw new UnusualTasteException();
default:
throw new TemperatureException();
}
}
catch (TooHotException e) {
System.out.println(
"This coffee is too hot.");
// Customer will wait until it cools
// to an acceptable temperature.
}
}
//...
}
-
The calling method must either catch the exception or declare it in
its own
throws
clause:
// In file VirtualCafe.java
class VirtualCafe {
public static void serveCustomer(
VirtualPerson cust, CoffeeCup cup)
throws TemperatureException,
UnusualTasteException {
try {
cust.drinkCoffee(cup);
}
catch (TooColdException e) {
System.out.println(
"This coffee is too cold.");
// Add more hot coffee...
}
}
}
Checked versus Unchecked
-
Only checked exceptions need appear in
throws
clauses
-
Whether an exception is "checked" or "unchecked" is determined by its
position in the hierarchy of throwable classes:
Exceptions In Your Face
-
Placing an exception in a
throws
clause forces client
programmers who invoke your method to deal with the exception, either by:
- Catching it, or
- Declaring it in their own
throws
clause
-
Error
s: For the JVM and Java API
-
To make a checked exception, subclass
Exception
but not
RuntimeException
-
To make an unchecked exception, subclass
RuntimeException
Why Checked Exceptions?
-
From the JLS: "This compile-time checking for the presence of exception
handlers is designed to reduce the number of exceptions which are not
properly handled."
-
If you are throwing an exception for an abnormal condition that you feel
client programmers should consciously decide how to handle, throw a checked
exception.
-
Unchecked exceptions indicate software bugs.
-
Precisely because unchecked exceptions usually represent software bugs, they
often can't be handled somewhere with more context.
Discussion
-
Guideline: Use a checked exception for conditions client code can reasonably be expected to handle.
Exception Classes
Why Exception Classes?
-
Catch clauses catch exceptions based on type
-
You want each different kind of abnormal condition to be catchable in
its own catch clause, to separate the handlers
-
Use inheritance relationships to enable a single catch clause to catch
all flavors (subclasses) of a certain kind of abnormal condition
-
A catch clause for
TemperatureException
, for example, can
catch TooHotException
and TooColdException
-
catch (TooHotException e) { ...
has meaning
Discussion
-
Guideline: Throw a different exception type for each abnormal
condition.
The Thread-Safe Object
- A state machine
RGBColor
object (not thread-safe)
1 // In file objectidioms/ex6/RGBColor.java
2 // Instances of this class are NOT thread-safe.
3
4 public class RGBColor {
5
6 private int r;
7 private int g;
8 private int b;
9
10 public RGBColor(int r, int g, int b) {
11
12 checkRGBVals(r, g, b);
13
14 this.r = r;
15 this.g = g;
16 this.b = b;
17 }
18
19 public void setColor(int r, int g, int b) {
20
21 checkRGBVals(r, g, b);
22
23 this.r = r;
24 this.g = g;
25 this.b = b;
26 }
27
28 /**
29 * returns color in an array of three ints: R, G, and B
30 */
31 public int[] getColor() {
32
33 int[] retVal = new int[3];
34 retVal[0] = r;
35 retVal[1] = g;
36 retVal[2] = b;
37
38 return retVal;
39 }
40
41 public void invert() {
42
43 r = 255 - r;
44 g = 255 - g;
45 b = 255 - b;
46 }
47
48 private static void checkRGBVals(int r, int g, int b) {
49
50 if (r < 0 || r > 255 || g < 0 || g > 255 ||
51 b < 0 || b > 255) {
52
53 throw new IllegalArgumentException();
54 }
55 }
56 }
Write/Write Conflicts
Thread |
Statement |
r |
g |
b |
Color |
none |
object represents green |
0 |
255 |
0 |
GREEN |
blue |
blue thread invokes setColor(0, 0, 255) |
0 |
255 |
0 |
GREEN |
blue |
checkRGBVals(0, 0, 255); |
0 |
255 |
0 |
GREEN |
blue |
this.r = 0; |
0 |
255 |
0 |
GREEN |
blue |
this.g = 0; |
0 |
255 |
0 |
GREEN |
blue |
blue gets preempted |
0 |
0 |
0 |
BLACK |
red |
red thread invokes setColor(255, 0, 0) |
0 |
0 |
0 |
BLACK |
red |
checkRGBVals(255, 0, 0); |
0 |
0 |
0 |
BLACK |
red |
this.r = 255; |
0 |
0 |
0 |
BLACK |
red |
this.g = 0; |
255 |
0 |
0 |
RED |
red |
this.b = 0; |
255 |
0 |
0 |
RED |
red |
red thread returns |
255 |
0 |
0 |
RED |
blue |
later, blue thread continues |
255 |
0 |
0 |
RED |
blue |
this.b = 255 |
255 |
0 |
0 |
RED |
blue |
blue thread returns |
255 |
0 |
255 |
MAGENTA |
none |
object represents magenta |
255 |
0 |
255 |
MAGENTA |
Read/Write Conflicts
Thread |
Statement |
r |
g |
b |
Color |
none |
object represents green |
0 |
255 |
0 |
GREEN |
blue |
blue thread invokes setColor(0, 0, 255) |
0 |
255 |
0 |
GREEN |
blue |
checkRGBVals(0, 0, 255); |
0 |
255 |
0 |
GREEN |
blue |
this.r = 0; |
0 |
255 |
0 |
GREEN |
blue |
this.g = 0; |
0 |
255 |
0 |
GREEN |
blue |
blue gets preempted |
0 |
0 |
0 |
BLACK |
red |
red thread invokes getColor() |
0 |
0 |
0 |
BLACK |
red |
int[] retVal = new int[3]; |
0 |
0 |
0 |
BLACK |
red |
retVal[0] = 0; |
0 |
0 |
0 |
BLACK |
red |
retVal[1] = 0; |
0 |
0 |
0 |
BLACK |
red |
retVal[2] = 0; |
0 |
0 |
0 |
BLACK |
red |
return retVal; |
0 |
0 |
0 |
BLACK |
red |
red thread returns black |
0 |
0 |
0 |
BLACK |
blue |
later, blue thread continues |
0 |
0 |
0 |
BLACK |
blue |
this.b = 255 |
0 |
0 |
0 |
BLACK |
blue |
blue thread returns |
0 |
0 |
255 |
BLUE |
none |
object represents blue |
0 |
0 |
255 |
BLUE |
Thread-Safe RGBColor
Object
1 // In file objectidioms/ex7/RGBColor.java
2 // Instances of this class are thread-safe.
3
4 public class RGBColor {
5
6 private int r;
7 private int g;
8 private int b;
9
10 public RGBColor(int r, int g, int b) {
11
12 checkRGBVals(r, g, b);
13
14 this.r = r;
15 this.g = g;
16 this.b = b;
17 }
18
19 public void setColor(int r, int g, int b) {
20
21 checkRGBVals(r, g, b);
22
23 synchronized (this) {
24
25 this.r = r;
26 this.g = g;
27 this.b = b;
28 }
29 }
30
31 /**
32 * returns color in an array of three ints: R, G, and B
33 */
34 public int[] getColor() {
35
36 int[] retVal = new int[3];
37
38 synchronized (this) {
39
40 retVal[0] = r;
41 retVal[1] = g;
42 retVal[2] = b;
43 }
44
45 return retVal;
46 }
47
48 public synchronized void invert() {
49
50 r = 255 - r;
51 g = 255 - g;
52 b = 255 - b;
53 }
54
55 private static void checkRGBVals(int r, int g, int b) {
56
57 if (r < 0 || r > 255 || g < 0 || g > 255 ||
58 b < 0 || b > 255) {
59
60 throw new IllegalArgumentException();
61 }
62 }
63 }
Ready for Threads
Thread |
Statement |
r |
g |
b |
Color |
none |
object represents green |
0 |
255 |
0 |
GREEN |
blue |
blue thread invokes setColor(0, 0, 255) |
0 |
255 |
0 |
GREEN |
blue |
checkRGBVals(0, 0, 255); |
0 |
255 |
0 |
GREEN |
blue |
blue thread acquires lock |
0 |
255 |
0 |
GREEN |
blue |
this.r = 0; |
0 |
255 |
0 |
GREEN |
blue |
this.g = 0; |
0 |
255 |
0 |
GREEN |
blue |
blue gets preempted |
0 |
0 |
0 |
BLACK |
red |
red thread invokes setColor(255, 0, 0) |
0 |
0 |
0 |
BLACK |
red |
checkRGBVals(255, 0, 0); |
0 |
0 |
0 |
BLACK |
red |
red thread blocks because object locked |
0 |
0 |
0 |
BLACK |
blue |
later, blue thread continues |
0 |
0 |
0 |
BLACK |
blue |
this.b = 255 |
0 |
0 |
0 |
BLACK |
blue |
blue thread returns and releases lock |
0 |
0 |
255 |
BLUE |
red |
later, red thread acquires lock and continues |
0 |
0 |
255 |
BLUE |
red |
this.r = 255; |
0 |
0 |
255 |
BLUE |
red |
this.g = 0; |
255 |
0 |
255 |
MAGENTA |
red |
this.b = 0; |
255 |
0 |
255 |
MAGENTA |
red |
red thread returns and releases lock |
255 |
0 |
0 |
RED |
none |
object represents red |
255 |
0 |
0 |
RED |
The Thread-Safe Object
-
Make instance variables private
-
Figure out what the monitor regions should be and mark them synchronized
-
Make objects thread-safe only if they'll actually be used in a multi-threaded
environment
-
Why? Performance hit from acquiring the lock and the possibility of deadlock
Discussion