diff --git a/readme.md b/readme.md index 11b2b9f..61309b7 100644 --- a/readme.md +++ b/readme.md @@ -5,6 +5,7 @@ The code is presented in 'puzzler' style. So it can contain unsafe code and it's ## Chapter 1 Fundamentals - [`UnsafeSequence`](src/chapter1/UnsafeSequence.java) Shows that concurrent reads will see the same value, resulting in undefined behavior. The code uses the treadsafe _CopyOnWriteArrayList_ instead of printing directly to stdout to prove that the writes/updates are not ordered, not the calls to _println()_. The code also introduces the use of _TestHarness_ that allows any number of threads to operate on the class under test. Internally it uses a _CountdownLatch_ to assure that the threads al start the exact same time instead of creation time (the threads themselves are created sequentially). This technique is described in JCiP ch. 5.1.1 - [`SafeSequence`](src/chapter1/SafeSequence.java) Uses *synchronized* to guarantee ordered reads/updates. +- [`Waiter`](src/chapter1/Waiter.java) Object.wait() is tricky. For instance the user must use _synchronized_ but in this case does not lock on the monitor. Other threads can still 'synchronize' on it as shown. The more well known catch is that there can be spurious wakeups, so the user needs some boolean value to make sure a wakeup was valid (not shown here). ### cretans _'All the Cretans are liars'_ -- Epimenides the Cretan @@ -20,6 +21,15 @@ The motivation for this code is that it's easy to shoot yourself in the foot wit - [`SynchronizedCretan4`](src/chapter1/cretans/SynchronizedCretan4.java) Is wrong, because *new String()* always creates a new object, so it no longer serves as a valid lock. - [`SynchronizedCretan5`](src/chapter1/cretans/SynchronizedCretan5.java) Is tricky because _synchronized_ on a method uses _this_ as the lock object. So update() and compare() might not use the same lock object. +### Sleepers +This code shows the many ways you historically have to make the thread stop for a while. Should never be necessary. In theory. This approach is still very common even in production code. +* [`Sleeper1`](src/chapter1/sleepers/Sleeper1.java) Still the most used way, using Thread.sleep() where the argument is the number of milliseconds. It is not theoretical that a developer mistakenly thinks they are seconds. +* [`Sleeper2`](src/chapter1/sleepers/Sleeper2.java) The more modern remedy where the unit is explicit. +* [`Sleeper3a`](src/chapter1/sleepers/Sleeper3a.java) Again using Object.wait(). +* [`Sleeper3b`](src/chapter1/sleepers/Sleeper3b.java) Synchronized still doesn't synchronize on _this_ +* [`Sleeper4`](src/chapter1/sleepers/Sleeper4.java) Thread.yield(). Don't use it. + + ## Chapter 2 Thread Safety - [`LazyInit`](src/chapter2/LazyInit.java) Taken from Listing 2.3. Lazy initialization might seem a good way to postpone initialization until it is actually needed, but introduces new problems (race conditions). This was not uncommon in older applications. - [`SafeLazyInit`](src/chapter2/SafeLazyInit.java) Is safe while not being very scalable. Readonly singletons are better initialized while starting up, for instance using dependency injection. @@ -39,12 +49,12 @@ The motivation for this code is that it's easy to shoot yourself in the foot wit ## Chapter 5 Building Blocks - [`ShowMeTheValues`](src/chapter5/ShowMeTheValues.java) fails with a ConcurrentModificationException because toString() on a list will iterate over it and in this case it is also being updated. ### juc -- [`HashMapTest`](src/chapter5/juc/HashMapTest.java) Written by Artem Novikov on stackoverflow. Showing the regular _HashMap_ is not threadsafe is easy. While one thread adds keys to the map continually, another thread checks for the existence of a previously added key/value. Note that IntStream.range is exclusive for the upper bound, so targetKey will never be overwritten in the map. +- [`HashMapTest`](src/chapter5/juc/HashMapTest.java) Written by Artem Novikov on [stackoverflow](https://stackoverflow.com/questions/18542037/how-to-prove-that-hashmap-in-java-is-not-thread-safe). Showing the regular _HashMap_ is not threadsafe is easy. While one thread adds keys to the map continually, another thread checks for the existence of a previously added key/value. Note that IntStream.range is exclusive for the upper bound, so targetKey will never be overwritten in the map. ## Chapter 6 Task Execution ### Futures -- [`TheFuture`](src/chapter6/futures/TheFuture.java) Shows the correct use of Future. A call to getData() is asynchronous and returns a Future immediately. Calls to Future.get() block until a value is set in another thread. +- [`TheFuture`](src/chapter6/futures/TheFuture.java) Shows the correct use of java.util.concurrent.Future. A call to getData() is asynchronous and returns a Future immediately. Calls to Future.get() block until a value is set in another thread. - [`CancelledFuture`](src/chapter6/futures/CancelledFuture.java) Shows what cancellation does to the future. Calls to Future.get() fail after cancellation. - [`ExceptionalFuture`](src/chapter6/futures/ExceptionalFuture.java) Shows what happens when the calculation raises an error. The Exception 'hides' until Future.get() is called. - [`CompletionServiceExample`](src/chapter6/futures/CompletionServiceExample.java) The JUC CompletionService combines the use of Futures with a BlockingQueue. As tasks are completed, they are added to the queue, where they can be retrieved with take(), which blocks while no completed Future is available. diff --git a/src/chapter1/Waiter.java b/src/chapter1/Waiter.java new file mode 100644 index 0000000..ab6f243 --- /dev/null +++ b/src/chapter1/Waiter.java @@ -0,0 +1,45 @@ +package chapter1; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public class Waiter { + + + public static void main(String[] args) throws InterruptedException { + Object lock = new Object(); + + ScheduledExecutorService executor = Executors.newScheduledThreadPool(2); + + //wait after 2 seconds + executor.schedule(() -> { + synchronized (lock){ + try { + System.out.println("2 wait again"); + lock.wait(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + }, 2, TimeUnit.SECONDS); + + //release lock after 4 seconds + executor.schedule(() -> { + synchronized (lock){ + System.out.println("3 notifying"); + lock.notify(); + } + }, 4, TimeUnit.SECONDS); + + // wait now + System.out.println("1 wait"); + synchronized (lock) { + lock.wait(); + } + + System.out.println("4 wait no more"); + + executor.shutdownNow(); + } +} diff --git a/src/sleepers/Sleeper1.java b/src/chapter1/sleepers/Sleeper1.java similarity index 90% rename from src/sleepers/Sleeper1.java rename to src/chapter1/sleepers/Sleeper1.java index fd043a8..42666bb 100644 --- a/src/sleepers/Sleeper1.java +++ b/src/chapter1/sleepers/Sleeper1.java @@ -1,4 +1,4 @@ -package sleepers; +package chapter1.sleepers; public class Sleeper1 { diff --git a/src/sleepers/Sleeper2.java b/src/chapter1/sleepers/Sleeper2.java similarity index 91% rename from src/sleepers/Sleeper2.java rename to src/chapter1/sleepers/Sleeper2.java index d0c773c..33ce74f 100644 --- a/src/sleepers/Sleeper2.java +++ b/src/chapter1/sleepers/Sleeper2.java @@ -1,4 +1,4 @@ -package sleepers; +package chapter1.sleepers; import java.util.concurrent.TimeUnit; diff --git a/src/sleepers/Sleeper3a.java b/src/chapter1/sleepers/Sleeper3a.java similarity index 93% rename from src/sleepers/Sleeper3a.java rename to src/chapter1/sleepers/Sleeper3a.java index 8534bba..e5abb53 100644 --- a/src/sleepers/Sleeper3a.java +++ b/src/chapter1/sleepers/Sleeper3a.java @@ -1,4 +1,4 @@ -package sleepers; +package chapter1.sleepers; public class Sleeper3a { diff --git a/src/sleepers/Sleeper3b.java b/src/chapter1/sleepers/Sleeper3b.java similarity index 96% rename from src/sleepers/Sleeper3b.java rename to src/chapter1/sleepers/Sleeper3b.java index d5e7720..8952228 100644 --- a/src/sleepers/Sleeper3b.java +++ b/src/chapter1/sleepers/Sleeper3b.java @@ -1,4 +1,4 @@ -package sleepers; +package chapter1.sleepers; import common.TestHarness; import org.junit.Test; diff --git a/src/sleepers/Sleeper4.java b/src/chapter1/sleepers/Sleeper4.java similarity index 97% rename from src/sleepers/Sleeper4.java rename to src/chapter1/sleepers/Sleeper4.java index f4f0f4e..a6764b8 100644 --- a/src/sleepers/Sleeper4.java +++ b/src/chapter1/sleepers/Sleeper4.java @@ -1,4 +1,4 @@ -package sleepers; +package chapter1.sleepers; /* * javadoc for Thread.yield: diff --git a/src/chapter3/VolatileStatus.java b/src/chapter3/VolatileStatus.java index 74335d2..fa8a65b 100644 --- a/src/chapter3/VolatileStatus.java +++ b/src/chapter3/VolatileStatus.java @@ -1,7 +1,5 @@ package chapter3; -import common.ThreadSafe; - import java.util.Random; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; diff --git a/src/chapter4/Atomicity.java b/src/chapter4/Atomicity.java index 437fa8c..93e3dea 100644 --- a/src/chapter4/Atomicity.java +++ b/src/chapter4/Atomicity.java @@ -12,7 +12,11 @@ public class Atomicity { @Test public void test() { - new TestHarness(8).testMultiThreaded(() -> this.addOneEntry()); + TestHarness testHarness = new TestHarness(8); + testHarness.testMultiThreaded(() -> { + testHarness.waitForSignal(); + this.addOneEntry(); + }); Assert.assertEquals(1, values.size()); }