Effective Java Review (90 Part Series)
1 Effective Java Tuesday! Let’s Consider Static Factory Methods
2 Effective Java Tuesday! The Builder Pattern!
… 86 more parts…
3 Effective Java Tuesday! Singletons!
4 Effective Java Tuesday! Utility Classes!
5 Effective Java Tuesday! Prefer Dependency Injection!
6 Effective Java Tuesday! Avoid Creating Unnecessary Objects!
7 Effective Java Tuesday! Don’t Leak Object References!
8 Effective Java Tuesday! Avoid Finalizers and Cleaners!
9 Effective Java Tuesday! Prefer try-with-resources
10 Effective Java Tuesday! Obey the `equals` contract
11 Effective Java Tuesday! Obey the `hashCode` contract
12 Effective Java Tuesday! Override `toString`
13 Effective Java Tuesday! Override `clone` judiciously
14 Effective Java Tuesday! Consider Implementing `Comparable`
15 Effective Java Tuesday! Minimize the Accessibility of Classes and Member
16 Effective Java Tuesday! In Public Classes, Use Accessors, Not Public Fields
17 Effective Java Tuesday! Minimize Mutability
18 Effective Java Tuesday! Favor Composition Over Inheritance
19 Effective Java Tuesday! Design and Document Classes for Inheritance or Else Prohibit It.
20 Effective Java Tuesday! Prefer Interfaces to Abstract Classes
21 Effective Java! Design Interfaces for Posterity
22 Effective Java! Use Interfaces Only to Define Types
23 Effective Java! Prefer Class Hierarchies to Tagged Classes
24 Effective Java! Favor Static Members Classes over Non-Static
25 Effective Java! Limit Source Files to a Single Top-Level Class
26 Effective Java! Don’t Use Raw Types
27 Effective Java! Eliminate Unchecked Warnings
28 Effective Java! Prefer Lists to Array
29 Effective Java! Favor Generic Types
30 Effective Java! Favor Generic Methods
31 Effective Java! Use Bounded Wildcards to Increase API Flexibility
32 Effective Java! Combine Generics and Varargs Judiciously
33 Effective Java! Consider Typesafe Heterogenous Containers
34 Effective Java! Use Enums Instead of int Constants
35 Effective Java! Use Instance Fields Instead of Ordinals
36 Effective Java! Use EnumSet Instead of Bit Fields
37 Effective Java! Use EnumMap instead of Ordinal Indexing
38 Effective Java! Emulate Extensible Enums With Interfaces.
39 Effective Java! Prefer Annotations to Naming Patterns
40 Effective Java! Consistently Use the Override Annotation
41 Effective Java! Use Marker Interfaces to Define Types
42 Effective Java! Prefer Lambdas to Anonymous Classes
43 Effective Java! Prefer Method References to Lambdas
44 Effective Java! Favor the Use of Standard Functional Interfaces
45 Effective Java! Use Stream Judiciously
46 Effective Java! Prefer Side-Effect-Free Functions in Streams
47 Effective Java! Prefer Collection To Stream as a Return Type
48 Effective Java! Use Caution When Making Streams Parallel
49 Effective Java! Check Parameters for Validity
50 Effective Java! Make Defensive Copies When Necessary
51 Effective Java! Design Method Signatures Carefully
52 Effective Java! Use Overloading Judiciously
53 Effective Java! Use Varargs Judiciously
54 Effective Java! Return Empty Collections or Arrays, Not Nulls
55 Effective Java! Return Optionals Judiciously
56 Effective Java: Write Doc Comments For All Exposed APIs
57 Effective Java: Minimize The Scope of Local Variables
58 Effective Java: Prefer for-each loops to traditional for loops
59 Effective Java: Know and Use the Libraries
60 Effective Java: Avoid Float and Double If Exact Answers Are Required
61 Effective Java: Prefer Primitive Types to Boxed Types
62 Effective Java: Avoid Strings When Other Types Are More Appropriate
63 Effective Java: Beware the Performance of String Concatenation
64 Effective Java: Refer to Objects By Their Interfaces
65 Effective Java: Prefer Interfaces To Reflection
66 Effective Java: Use Native Methods Judiciously
67 Effective Java: Optimize Judiciously
68 Effective Java: Adhere to Generally Accepted Naming Conventions
69 Effective Java: Use Exceptions for Only Exceptional Circumstances
70 Effective Java: Use Checked Exceptions for Recoverable Conditions
71 Effective Java: Avoid Unnecessary Use of Checked Exceptions
72 Effective Java: Favor The Use of Standard Exceptions
73 Effective Java: Throw Exceptions Appropriate To The Abstraction
74 Effective Java: Document All Exceptions Thrown By Each Method
75 Effective Java: Include Failure-Capture Information in Detail Messages
76 Effective Java: Strive for Failure Atomicity
77 Effective Java: Don’t Ignore Exceptions
78 Effective Java: Synchronize Access to Shared Mutable Data
79 Effective Java: Avoid Excessive Synchronization
80 Effective Java: Prefer Executors, Tasks, and Streams to Threads
81 Effective Java: Prefer Concurrency Utilities Over wait and notify
82 Effective Java: Document Thread Safety
83 Effective Java: Use Lazy Initialization Judiciously
84 Effective Java: Don’t Depend on the Thread Scheduler
85 Effective Java: Prefer Alternatives To Java Serialization
86 Effective Java: Implement Serializable With Great Caution
87 Effective Java: Consider Using a Custom Serialized Form
88 Effective Java: Write readObject Methods Defensively
89 Effective Java: For Instance Control, Prefer Enum types to readResolve
90 Effective Java: Consider Serialization Proxies Instead of Serialized Instances
At the core of each Object
in the Java language there are three methods, wait
, notify
, and notifyAll
. These methods allow you low-level concurrency control options. Up until Java 5, this was the go-to option for facilitating concurrency control. However, since the release of Java 5 (in 2004) there are now higher-level tools that can be used that are much easier and less error-prone. This being the case, in new code, we should be using exclusively these provided concurrency utilities.
The java.util.concurrent
provides three different kinds of utilities. The first is the Executor framework discussed in a previous item, concurrent collections implementations, and synchronizers.
The concurrent collections provided by the Java language are high-performance concurrent implementation of the common collection interfaces (List
, Queue
, and `Map). These collections bring their own concurrency controls and thus don’t need (and shouldn’t be) externally synchronized. At times these collections provide lock-free implementations of functions and the fact that the user of the collection doesn’t need to know how the implementation works is great.
Because these concurrent collections sometimes need to do multiple actions (because they are state-dependent) atomically they provide functions to facilitate this. These methods can be extremely useful so in Java 8 many of these functions were provided on the main Collection interfaces. One example of this is putIfAbsent(key, value)
. We could use this functionality to develop a Strin.intern
implementation.
`
private static final ConcurrentMap map = new ConcurrentHashMap<>();
public static String intern(String s) {
String previousValue = map.putIfAbsent(s, s);
return previousValue == null ? s : previousValue;
}
`
This is pretty efficient and concise but we can make it even more performant but using the knowledge that get
operations are optimized in ConcurrentHashMap
and write the following:
public static String intern(String s) {
String result = map.get(s);
if (result == null) {
result = map.putIfAbsent(s, s);
if (result == null) {
result = s;
}
}
return result;
}
This new implementation is actually faster than the built-in intern
function (although it doesn’t take into account some of the memory management jobs that the built-in intern
function must perform).
These built-in concurrent capable collections effectively obsolete the built-in synchronizing collection operations (Collections.synchronizedMap
etc). Simply replacing one of these usages with its concurrent counterpart is likely a great performance benefit. These concurrent collections are even used internally by the concurrent package to facilitate its work like the BlockingQueue
‘s usage in ThreadPoolExecutor
as discussed in the previous item.
Another type of concurrent utility provided by the java.util.concurrent
package is synchronizers. These are objects that allow one thread to wait on another thread. Although this is a fairly basic concept it can be used powerfully. The most common synchronizers are the CountdownLatch
and the Semaphore
but there are also more advanced synchronizers like the CyclicBarrier
, Exchanger
, and Phaser
.
Let’s look a little more into CountdownLatch
. This class serves as a single-use barrier that allows threads to wait on one another before proceeding. The class takes an int
in its constructor of the number of times its countdown
method must be invoked before it unblocks the threads waiting on it. Using this class let’s build a simple timer function that has all threads initialize, wait until all are ready, start processing, and then stop and determine how long the process took.
`
public static long time(Executor executor, int concurrency, Runnable action) {
CountDownLatch ready = new CountDownLatch(concurrency);
CountDownLatch start = new CountDownLatch(1);
CountDownLatch done = new CountDownLatch(concurrency);
for(int i=0; i executor.execute(() -> {
ready.countDown();
try {
start.await();
action.run()
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
done.countdown();
}
});
}
ready.await();
long startNanos = System.nanoTime();
start.countDown();
done.await();
return System.nanoTime() – startNanos();
}
`
In this example we use three different CountdownLatch
s which can get a little muddy but it does keep things pretty separated. We have the ready
latch that each thread checks in with and the main thread is waiting on. Once all threads check in the main thread starts the timer and triggers the start
latch which all the worker threads have been waiting on. All the worker threads do their work and then check-in when finished via the done
latch. The main thread is waiting on the done
latch to open and then marks the finished processing time.
Let’s consider a few other things about this example. What would happen if we passed in a concurrency
count that didn’t match the number of threads? If it was too few we would end up prematurely starting our time and finishing our timer. If it was higher than our thread count then we would be deadlocked in what is known as a thread starvation deadlock
. You will also notice that we catch the InterruptedException
. By convention whenever this exception type is caught we should call Thread.currentThread().interrupt()
to signal to the owner of the thread that the thread has been interrupted and allow it to handle that in whatever way seems fit. Finally, you will notice the usage of System.nanoTime()
vs something like System.currentTimeMillis()
. This is because it is more accurate and because it is unaffected by the system’s real-time clock. It is also of note that, unless the Runnable
represents a significant amount of work, this function won’t return very interesting work. This is because even System.nanonTime()
is not accurate enough for microbenchmarking. It is for this reason that tools like JMH exist for this specific purpose.
This only begins to cover the utilities provided by the concurrent utilities built into the core language. Feel free to dig deeper into these utilities.
Even though there are better methods out there than using wait
and notify
directly we may need to maintain code that does use these functions. The wait
method is, as the name suggests, used to make a thread wait for some condition. It must be invoked in a synchronized region that locks the on the method it is invoked. The colloquial usage looks like this:
synchronized(obj) {
while(<condition does not hold) {
obj.wait();
}
// perform action now that condition holds.
}
Things of note, we need to always call wait
within a loop checking that the condition we are waiting for is true. If the condition we are waiting for is true and the notify
or notifyAll
method is called before the wait
method is called there is no guarantee the thread will ever wake up. By putting the check in a loop we ensure safety. If the thread moves past the wait
before the condition holds we lose the protection of our invariant. There are several ways that a thread can be woken up when the condition it is waiting for is not true.
- Another thread could have also been notified and taken the lock.
- Another thread could have invoked
notify
incorrectly. - The invoking thread could have triggered
notify
too early before it was actually ready. - In rare circumstances, a waiting thread can be woken up even without a
notify
call.
One topic that comes up when discussing wait
and notify
is whether to use notify
or notifyAll
. As a reminder notify
wakes up one waiting thread and notifyAll
wakes up all waiting threads. Waking up all waiting threads is a safe, conservative choice. It will always guarantee you will wake up all threads that need to be awakened. You actually may be waking up more threads than you need to, but if you are properly checking on condition before proceeding after waiting, these additional threads that were woken up will simply go back to waiting. Using only notify
could lead to a bit of an optimization but in the long run, it’s not likely worth it.
Simply put, wait
and notify
rarely need to be used in new code. By using modern concurrent utilities we can have much simpler code, safer code, and likely more performant. If we do find ourselves maintaining code that does use wait
and notify
we should be careful we are using the functionality correctly. Always check before proceeding, loop if the condition isn’t met, and prefer notifyAll
over notify
.
Effective Java Review (90 Part Series)
1 Effective Java Tuesday! Let’s Consider Static Factory Methods
2 Effective Java Tuesday! The Builder Pattern!
… 86 more parts…
3 Effective Java Tuesday! Singletons!
4 Effective Java Tuesday! Utility Classes!
5 Effective Java Tuesday! Prefer Dependency Injection!
6 Effective Java Tuesday! Avoid Creating Unnecessary Objects!
7 Effective Java Tuesday! Don’t Leak Object References!
8 Effective Java Tuesday! Avoid Finalizers and Cleaners!
9 Effective Java Tuesday! Prefer try-with-resources
10 Effective Java Tuesday! Obey the `equals` contract
11 Effective Java Tuesday! Obey the `hashCode` contract
12 Effective Java Tuesday! Override `toString`
13 Effective Java Tuesday! Override `clone` judiciously
14 Effective Java Tuesday! Consider Implementing `Comparable`
15 Effective Java Tuesday! Minimize the Accessibility of Classes and Member
16 Effective Java Tuesday! In Public Classes, Use Accessors, Not Public Fields
17 Effective Java Tuesday! Minimize Mutability
18 Effective Java Tuesday! Favor Composition Over Inheritance
19 Effective Java Tuesday! Design and Document Classes for Inheritance or Else Prohibit It.
20 Effective Java Tuesday! Prefer Interfaces to Abstract Classes
21 Effective Java! Design Interfaces for Posterity
22 Effective Java! Use Interfaces Only to Define Types
23 Effective Java! Prefer Class Hierarchies to Tagged Classes
24 Effective Java! Favor Static Members Classes over Non-Static
25 Effective Java! Limit Source Files to a Single Top-Level Class
26 Effective Java! Don’t Use Raw Types
27 Effective Java! Eliminate Unchecked Warnings
28 Effective Java! Prefer Lists to Array
29 Effective Java! Favor Generic Types
30 Effective Java! Favor Generic Methods
31 Effective Java! Use Bounded Wildcards to Increase API Flexibility
32 Effective Java! Combine Generics and Varargs Judiciously
33 Effective Java! Consider Typesafe Heterogenous Containers
34 Effective Java! Use Enums Instead of int Constants
35 Effective Java! Use Instance Fields Instead of Ordinals
36 Effective Java! Use EnumSet Instead of Bit Fields
37 Effective Java! Use EnumMap instead of Ordinal Indexing
38 Effective Java! Emulate Extensible Enums With Interfaces.
39 Effective Java! Prefer Annotations to Naming Patterns
40 Effective Java! Consistently Use the Override Annotation
41 Effective Java! Use Marker Interfaces to Define Types
42 Effective Java! Prefer Lambdas to Anonymous Classes
43 Effective Java! Prefer Method References to Lambdas
44 Effective Java! Favor the Use of Standard Functional Interfaces
45 Effective Java! Use Stream Judiciously
46 Effective Java! Prefer Side-Effect-Free Functions in Streams
47 Effective Java! Prefer Collection To Stream as a Return Type
48 Effective Java! Use Caution When Making Streams Parallel
49 Effective Java! Check Parameters for Validity
50 Effective Java! Make Defensive Copies When Necessary
51 Effective Java! Design Method Signatures Carefully
52 Effective Java! Use Overloading Judiciously
53 Effective Java! Use Varargs Judiciously
54 Effective Java! Return Empty Collections or Arrays, Not Nulls
55 Effective Java! Return Optionals Judiciously
56 Effective Java: Write Doc Comments For All Exposed APIs
57 Effective Java: Minimize The Scope of Local Variables
58 Effective Java: Prefer for-each loops to traditional for loops
59 Effective Java: Know and Use the Libraries
60 Effective Java: Avoid Float and Double If Exact Answers Are Required
61 Effective Java: Prefer Primitive Types to Boxed Types
62 Effective Java: Avoid Strings When Other Types Are More Appropriate
63 Effective Java: Beware the Performance of String Concatenation
64 Effective Java: Refer to Objects By Their Interfaces
65 Effective Java: Prefer Interfaces To Reflection
66 Effective Java: Use Native Methods Judiciously
67 Effective Java: Optimize Judiciously
68 Effective Java: Adhere to Generally Accepted Naming Conventions
69 Effective Java: Use Exceptions for Only Exceptional Circumstances
70 Effective Java: Use Checked Exceptions for Recoverable Conditions
71 Effective Java: Avoid Unnecessary Use of Checked Exceptions
72 Effective Java: Favor The Use of Standard Exceptions
73 Effective Java: Throw Exceptions Appropriate To The Abstraction
74 Effective Java: Document All Exceptions Thrown By Each Method
75 Effective Java: Include Failure-Capture Information in Detail Messages
76 Effective Java: Strive for Failure Atomicity
77 Effective Java: Don’t Ignore Exceptions
78 Effective Java: Synchronize Access to Shared Mutable Data
79 Effective Java: Avoid Excessive Synchronization
80 Effective Java: Prefer Executors, Tasks, and Streams to Threads
81 Effective Java: Prefer Concurrency Utilities Over wait and notify
82 Effective Java: Document Thread Safety
83 Effective Java: Use Lazy Initialization Judiciously
84 Effective Java: Don’t Depend on the Thread Scheduler
85 Effective Java: Prefer Alternatives To Java Serialization
86 Effective Java: Implement Serializable With Great Caution
87 Effective Java: Consider Using a Custom Serialized Form
88 Effective Java: Write readObject Methods Defensively
89 Effective Java: For Instance Control, Prefer Enum types to readResolve
90 Effective Java: Consider Serialization Proxies Instead of Serialized Instances
原文链接:Effective Java: Prefer Concurrency Utilities Over wait and notify
暂无评论内容