initial commit
This commit is contained in:
commit
62e2025a20
93 changed files with 2477 additions and 0 deletions
4
.idea/encodings.xml
generated
Normal file
4
.idea/encodings.xml
generated
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding" addBOMForNewFiles="with NO BOM" />
|
||||||
|
</project>
|
||||||
6
.idea/misc.xml
generated
Normal file
6
.idea/misc.xml
generated
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/ConcurrencyPuzzlers.iml" filepath="$PROJECT_DIR$/ConcurrencyPuzzlers.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
124
.idea/uiDesigner.xml
generated
Normal file
124
.idea/uiDesigner.xml
generated
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Palette2">
|
||||||
|
<group name="Swing">
|
||||||
|
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
|
||||||
|
</item>
|
||||||
|
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
|
||||||
|
<initial-values>
|
||||||
|
<property name="text" value="Button" />
|
||||||
|
</initial-values>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
|
||||||
|
<initial-values>
|
||||||
|
<property name="text" value="RadioButton" />
|
||||||
|
</initial-values>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
|
||||||
|
<initial-values>
|
||||||
|
<property name="text" value="CheckBox" />
|
||||||
|
</initial-values>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
|
||||||
|
<initial-values>
|
||||||
|
<property name="text" value="Label" />
|
||||||
|
</initial-values>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||||
|
<preferred-size width="150" height="-1" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||||
|
<preferred-size width="150" height="-1" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||||
|
<preferred-size width="150" height="-1" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||||
|
<preferred-size width="150" height="50" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||||
|
<preferred-size width="150" height="50" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||||
|
<preferred-size width="150" height="50" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||||
|
<preferred-size width="150" height="50" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
|
||||||
|
<preferred-size width="150" height="50" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||||
|
<preferred-size width="150" height="50" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
|
||||||
|
<preferred-size width="200" height="200" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
|
||||||
|
<preferred-size width="200" height="200" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
|
||||||
|
<preferred-size width="-1" height="20" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
|
||||||
|
</item>
|
||||||
|
</group>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
1057
.idea/workspace.xml
generated
Normal file
1057
.idea/workspace.xml
generated
Normal file
File diff suppressed because it is too large
Load diff
21
ConcurrencyPuzzlers.iml
Normal file
21
ConcurrencyPuzzlers.iml
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="JAVA_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
<orderEntry type="module-library">
|
||||||
|
<library name="JUnit4">
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/junit/junit/4.12/junit-4.12.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</orderEntry>
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
Binary file not shown.
BIN
out/production/ConcurrencyPuzzlers/chapter1/SafeSequence.class
Normal file
BIN
out/production/ConcurrencyPuzzlers/chapter1/SafeSequence.class
Normal file
Binary file not shown.
BIN
out/production/ConcurrencyPuzzlers/chapter1/UnsafeSequence.class
Normal file
BIN
out/production/ConcurrencyPuzzlers/chapter1/UnsafeSequence.class
Normal file
Binary file not shown.
BIN
out/production/ConcurrencyPuzzlers/chapter1/cretans/Cretan.class
Normal file
BIN
out/production/ConcurrencyPuzzlers/chapter1/cretans/Cretan.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
out/production/ConcurrencyPuzzlers/chapter2/LazyInit.class
Normal file
BIN
out/production/ConcurrencyPuzzlers/chapter2/LazyInit.class
Normal file
Binary file not shown.
BIN
out/production/ConcurrencyPuzzlers/chapter2/SafeLazyInit.class
Normal file
BIN
out/production/ConcurrencyPuzzlers/chapter2/SafeLazyInit.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
out/production/ConcurrencyPuzzlers/chapter3/Publication.class
Normal file
BIN
out/production/ConcurrencyPuzzlers/chapter3/Publication.class
Normal file
Binary file not shown.
BIN
out/production/ConcurrencyPuzzlers/chapter3/Visibility.class
Normal file
BIN
out/production/ConcurrencyPuzzlers/chapter3/Visibility.class
Normal file
Binary file not shown.
BIN
out/production/ConcurrencyPuzzlers/chapter3/VolatileCretan.class
Normal file
BIN
out/production/ConcurrencyPuzzlers/chapter3/VolatileCretan.class
Normal file
Binary file not shown.
BIN
out/production/ConcurrencyPuzzlers/chapter3/VolatileStatus.class
Normal file
BIN
out/production/ConcurrencyPuzzlers/chapter3/VolatileStatus.class
Normal file
Binary file not shown.
BIN
out/production/ConcurrencyPuzzlers/chapter4/Atomicity.class
Normal file
BIN
out/production/ConcurrencyPuzzlers/chapter4/Atomicity.class
Normal file
Binary file not shown.
BIN
out/production/ConcurrencyPuzzlers/chapter4/Car.class
Normal file
BIN
out/production/ConcurrencyPuzzlers/chapter4/Car.class
Normal file
Binary file not shown.
BIN
out/production/ConcurrencyPuzzlers/chapter4/Ownership.class
Normal file
BIN
out/production/ConcurrencyPuzzlers/chapter4/Ownership.class
Normal file
Binary file not shown.
BIN
out/production/ConcurrencyPuzzlers/chapter4/Ownership2.class
Normal file
BIN
out/production/ConcurrencyPuzzlers/chapter4/Ownership2.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
out/production/ConcurrencyPuzzlers/chapter6/Shutdown.class
Normal file
BIN
out/production/ConcurrencyPuzzlers/chapter6/Shutdown.class
Normal file
Binary file not shown.
BIN
out/production/ConcurrencyPuzzlers/chapter6/ShutdownNow.class
Normal file
BIN
out/production/ConcurrencyPuzzlers/chapter6/ShutdownNow.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
out/production/ConcurrencyPuzzlers/common/NotThreadSafe.class
Normal file
BIN
out/production/ConcurrencyPuzzlers/common/NotThreadSafe.class
Normal file
Binary file not shown.
BIN
out/production/ConcurrencyPuzzlers/common/TestHarness.class
Normal file
BIN
out/production/ConcurrencyPuzzlers/common/TestHarness.class
Normal file
Binary file not shown.
BIN
out/production/ConcurrencyPuzzlers/common/ThreadSafe.class
Normal file
BIN
out/production/ConcurrencyPuzzlers/common/ThreadSafe.class
Normal file
Binary file not shown.
BIN
out/production/ConcurrencyPuzzlers/sleepers/Sleeper1.class
Normal file
BIN
out/production/ConcurrencyPuzzlers/sleepers/Sleeper1.class
Normal file
Binary file not shown.
BIN
out/production/ConcurrencyPuzzlers/sleepers/Sleeper2.class
Normal file
BIN
out/production/ConcurrencyPuzzlers/sleepers/Sleeper2.class
Normal file
Binary file not shown.
BIN
out/production/ConcurrencyPuzzlers/sleepers/Sleeper3a.class
Normal file
BIN
out/production/ConcurrencyPuzzlers/sleepers/Sleeper3a.class
Normal file
Binary file not shown.
BIN
out/production/ConcurrencyPuzzlers/sleepers/Sleeper3b.class
Normal file
BIN
out/production/ConcurrencyPuzzlers/sleepers/Sleeper3b.class
Normal file
Binary file not shown.
BIN
out/production/ConcurrencyPuzzlers/sleepers/Sleeper4.class
Normal file
BIN
out/production/ConcurrencyPuzzlers/sleepers/Sleeper4.class
Normal file
Binary file not shown.
52
readme.md
Normal file
52
readme.md
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
# Java Concurrency in Practice
|
||||||
|
I created this code to complement the book by showing the pitfalls of using threads in java and by showing the correct use of some concurrency primitives since java 1.5. It is no replacement for the book and does not necessarily cover all aspects covered. Some code goes beyond the book for instance when trying to actually prove what is said, for instance the thread-unsafety of java.util.HashMap.
|
||||||
|
The code is presented in 'puzzler' style. So it can contain unsafe code and it's up to the reader to discover that. This readme is added as an explanation.
|
||||||
|
|
||||||
|
## Chapter 1 Fundamentals
|
||||||
|
* *UnsafeSequence* 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* Uses *synchronized* to guarantee ordered reads/updates.
|
||||||
|
|
||||||
|
### cretans
|
||||||
|
The motivation for this code is that it's easy to shoot yourself in the foot with threads in java and how to prevent that. I would like to add here that java has evolved over the years while keeping less useful concepts (ie. object.wait(), or volatile), while more modern languages have fewer outdated threading primitives and are therefore safer to use.
|
||||||
|
|
||||||
|
* *Cretan* A simple class that has mutable state and that has no protection from being shared in a dysfunctional way.
|
||||||
|
* *CretanAttack* Shows how the invariant for Cretan can be violated
|
||||||
|
* *SynchronizedCretan* Shows that all access to shared mutable state needs to be guarded by locks, not just the writes
|
||||||
|
* *SynchronizedCretan2* All access is now guarded by _synchronized_ so 'WTF' no longer occurs. While this style is safe, it's not always the most scalable approach.
|
||||||
|
* *SynchronizedCretan3* Shows that you can synchronize on anything, but this code is tricky because it only works because the compiler *interns* the String literal, so it becomes a reference to the same object.
|
||||||
|
* *SynchronizedCretan4* Is wrong, because *new String()* always creates a new object, so it no longer serves as a valid lock.
|
||||||
|
* *SynchronizedCretan5* Is tricky because _synchronized_ on a method uses _this_ as the lock object. So update() and compare() might not use the same lock object.
|
||||||
|
|
||||||
|
## Chapter 2 Thread Safety
|
||||||
|
* *LazyInit* 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* Is safe while not being very scalable. Readonly singletons are better initialized while starting up, for instance using dependency injection.
|
||||||
|
|
||||||
|
## Chapter 3 Sharing Objects
|
||||||
|
* *AttemptToShowReordering* The java memory model facilitates modern processors that are free to reorder instructions, if (and only if) reordering would not harm sequential execution. While this is accepted theory, it is surprisingly difficult (if not impossible) to show reordering on Intel processors. The code tries to find a situation where variables _b_ or _c_ are set but _a_ is not.
|
||||||
|
* *VolatileStatus* The only valid use case for _volatile_ is when you have 1 writing thread and n>0 reading threads. This is useful for state changes.
|
||||||
|
* *Visibility* The idea here is that the write to _value_ might occur **after** the write to _ready_ resulting in printing 0. This is because there is no explicit happens-before relation between lines 27 and 28 and the processor is free to reorder the instructions. Using a memory barrier would establish such a happens before. In java that would mean using volatile, an AtomicReference or synchronized. So far I have not seen it print 0. See https://stackoverflow.com/questions/52648800/how-to-demonstrate-java-instruction-reordering-problems
|
||||||
|
* *VolatileCretan* The _volatile_ clause only guarantees visibility. It is not sufficient for thread safety.
|
||||||
|
* *NonAtomicDoubleUpdates* This is again a failed attempt to show something that you shouldn't do. Doubles and Longs are 8 bytes in size. Writes to memory are atomic for 4 bytes only. Two writes of 4 bytes should result in corrupt values if executed from multiple threads. I guess this could lead to negative values whereas the code should only write positives. #fail
|
||||||
|
* *Publication* Constants are guaranteed to be published safely.
|
||||||
|
|
||||||
|
## Chapter 4 Composing Objects
|
||||||
|
* *Ownership* and *Ownership2* These classes are added to show the concept of ownership which is made 'invisible' in java. The only difference in runtime behavior is the _car_ being garbage collected or not. In **rust** this code would not pass the borrow checker because a reference in that language must only exist in one place (no borrowing of mutable references), so the compiler knows when to deallocate the object safely.
|
||||||
|
* *Atomicity* The CopyOnWriteArrayList is threadsafe, but non-atomic reads/writes are not. Use compound methods like addIfAbsent
|
||||||
|
|
||||||
|
## Chapter 5 Building Blocks
|
||||||
|
* *ShowMeTheValues fails with a ConcurrentModificationException because toString() on a list will iterate over it and in this case it is also being updated.
|
||||||
|
### juc
|
||||||
|
* *HashMapTest* Written by Artem Novikov for 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.
|
||||||
|
|
||||||
|
## Chapter 6 Task Execution
|
||||||
|
|
||||||
|
### futures
|
||||||
|
* *TheFuture* 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.
|
||||||
|
* *CancelledFuture* Shows what cancellation does to the future. Calls to Future.get() fail after cancellation.
|
||||||
|
* *ExceptionalFuture* Shows what happens when the calculation raises an error. The Exception 'hides' until Future.get() is called.
|
||||||
|
|
||||||
|
* *CachedThreadPool* Shows the use of a cached threadpool. It has a potentially unlimited number of threads and reuses them when they are discarded by the user.
|
||||||
|
* *FixedThreadPoolWebserver* Shows the use of a fixed threadpool. It has a limited number of threads and reuses them when they are discarded by the user.
|
||||||
|
* *Shutdown* and *ShutdownNow* Show the difference between both methods. There are still a lot of tasks in queue when shutdown runs, and they will still be executed. After shutdownNow() there are less dots indicating earlier stop. The currently running task is being interrupted while sleeping.
|
||||||
|
* *SingleThreadExecutor* Serves to prove that the queue used is guaranteed FIFO so the numbers are being printed in ascending order.
|
||||||
|
* *CompletionServiceExample* 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.
|
||||||
35
src/chapter1/SafeSequence.java
Normal file
35
src/chapter1/SafeSequence.java
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
package chapter1;
|
||||||
|
|
||||||
|
import common.TestHarness;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Basic example taken from JCIP listing 1.2 (also slightly altered)
|
||||||
|
*/
|
||||||
|
public class SafeSequence {
|
||||||
|
private final List<Integer> collectedValues = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
|
private int value = 0;
|
||||||
|
|
||||||
|
//access to shared mutable state is now made sequential
|
||||||
|
public synchronized void addNextValue() {
|
||||||
|
collectedValues.add(++value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() throws InterruptedException {
|
||||||
|
TestHarness testHarness = new TestHarness(8);
|
||||||
|
|
||||||
|
testHarness.testMultiThreaded(() -> {
|
||||||
|
testHarness.waitForSignal();
|
||||||
|
addNextValue();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
System.out.printf("result %s%n", collectedValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
37
src/chapter1/UnsafeSequence.java
Normal file
37
src/chapter1/UnsafeSequence.java
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
package chapter1;
|
||||||
|
|
||||||
|
import common.NotThreadSafe;
|
||||||
|
import common.TestHarness;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Basic example taken from JCIP listing 1.1 (altered slightly)
|
||||||
|
*/
|
||||||
|
@NotThreadSafe
|
||||||
|
public class UnsafeSequence {
|
||||||
|
//shared mutable state
|
||||||
|
private int value=0;
|
||||||
|
|
||||||
|
private final List<Integer> collectedValues = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
|
public void addNextValue() {
|
||||||
|
collectedValues.add(++value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() throws InterruptedException {
|
||||||
|
TestHarness testHarness = new TestHarness(8);
|
||||||
|
|
||||||
|
testHarness.testMultiThreaded(() -> {
|
||||||
|
testHarness.waitForSignal();
|
||||||
|
addNextValue();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
System.out.printf("result %s%n",collectedValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
26
src/chapter1/cretans/Cretan.java
Normal file
26
src/chapter1/cretans/Cretan.java
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
package chapter1.cretans;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Epimenides the Cretan says, 'All the Cretans are liars'
|
||||||
|
*
|
||||||
|
* https://en.wikipedia.org/wiki/Epimenides_paradox
|
||||||
|
*
|
||||||
|
* or This sentence is false.
|
||||||
|
*/
|
||||||
|
public class Cretan {
|
||||||
|
|
||||||
|
public double value;
|
||||||
|
|
||||||
|
public Cretan(double value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void test() {
|
||||||
|
if (value != value) { // paradoxical?
|
||||||
|
/* can we reach this line ?*/
|
||||||
|
System.out.println("WTF?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
41
src/chapter1/cretans/CretanAttack.java
Normal file
41
src/chapter1/cretans/CretanAttack.java
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
package chapter1.cretans;
|
||||||
|
|
||||||
|
public class CretanAttack {
|
||||||
|
|
||||||
|
public double value;
|
||||||
|
private final Cretan cretan=new Cretan(0);
|
||||||
|
|
||||||
|
public CretanAttack(double value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
CretanAttack instance = new CretanAttack(0);
|
||||||
|
instance.test();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void test() {
|
||||||
|
new Thread(() -> {
|
||||||
|
for (; ; ) {
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
|
||||||
|
new Thread(() -> {
|
||||||
|
for (; ; ) {
|
||||||
|
compare();
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update() {
|
||||||
|
value++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void compare() {
|
||||||
|
if (value != value) { // this program is a liar
|
||||||
|
System.out.println("WTF?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/chapter1/cretans/SynchronizedCretan.java
Normal file
31
src/chapter1/cretans/SynchronizedCretan.java
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
package chapter1.cretans;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* update is now synchronized. Will it still WTF ?
|
||||||
|
*/
|
||||||
|
public class SynchronizedCretan extends CretanAttack{
|
||||||
|
|
||||||
|
public double value;
|
||||||
|
|
||||||
|
public SynchronizedCretan(double value) {
|
||||||
|
super(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
SynchronizedCretan instance = new SynchronizedCretan(0);
|
||||||
|
instance.test();
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void update() {
|
||||||
|
value++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void compare() {
|
||||||
|
if (value != value) {
|
||||||
|
System.out.println("WTF?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/chapter1/cretans/SynchronizedCretan2.java
Normal file
28
src/chapter1/cretans/SynchronizedCretan2.java
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
package chapter1.cretans;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* now both read and update are synchronized
|
||||||
|
*/
|
||||||
|
public class SynchronizedCretan2 extends CretanAttack {
|
||||||
|
|
||||||
|
public double value;
|
||||||
|
|
||||||
|
public SynchronizedCretan2(double value) {
|
||||||
|
super(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SynchronizedCretan2 instance = new SynchronizedCretan2(0);
|
||||||
|
instance.test();
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void update() {
|
||||||
|
value++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void compare() {
|
||||||
|
if (value != value) {
|
||||||
|
System.out.println("WTF?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/chapter1/cretans/SynchronizedCretan3.java
Normal file
30
src/chapter1/cretans/SynchronizedCretan3.java
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
package chapter1.cretans;
|
||||||
|
|
||||||
|
public class SynchronizedCretan3 extends CretanAttack {
|
||||||
|
|
||||||
|
public double value;
|
||||||
|
|
||||||
|
|
||||||
|
public SynchronizedCretan3(double value) {
|
||||||
|
super(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SynchronizedCretan3 instance = new SynchronizedCretan3(0);
|
||||||
|
instance.test();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update() {
|
||||||
|
synchronized ("") {
|
||||||
|
value++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void compare() {
|
||||||
|
synchronized ("") {
|
||||||
|
if (value != value) {
|
||||||
|
System.out.println("WTF?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/chapter1/cretans/SynchronizedCretan4.java
Normal file
31
src/chapter1/cretans/SynchronizedCretan4.java
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
package chapter1.cretans;
|
||||||
|
|
||||||
|
/* threadsafe ?*/
|
||||||
|
public class SynchronizedCretan4 extends CretanAttack {
|
||||||
|
|
||||||
|
public double value;
|
||||||
|
|
||||||
|
|
||||||
|
public SynchronizedCretan4(double value) {
|
||||||
|
super(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SynchronizedCretan4 instance = new SynchronizedCretan4(0);
|
||||||
|
instance.test();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update() {
|
||||||
|
synchronized (new String()) {
|
||||||
|
value++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void compare() {
|
||||||
|
synchronized (new String()) {
|
||||||
|
if (value != value) {
|
||||||
|
System.out.println("WTF?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
src/chapter1/cretans/SynchronizedCretan5.java
Normal file
36
src/chapter1/cretans/SynchronizedCretan5.java
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
package chapter1.cretans;
|
||||||
|
|
||||||
|
/* threadsafe? */
|
||||||
|
public class SynchronizedCretan5 {
|
||||||
|
|
||||||
|
private static SynchronizedCretan5 instance = new SynchronizedCretan5(0);
|
||||||
|
public double value;
|
||||||
|
|
||||||
|
public SynchronizedCretan5(double value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
new Thread(() -> {
|
||||||
|
for (; ; ) {
|
||||||
|
instance.update();
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
|
||||||
|
new Thread(() -> {
|
||||||
|
for (; ; ) {
|
||||||
|
instance.compare();
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void update() {
|
||||||
|
instance=new SynchronizedCretan5(value++);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void compare() {
|
||||||
|
if (value != value) {
|
||||||
|
System.out.println("WTF?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/chapter2/ExpensiveObject.java
Normal file
16
src/chapter2/ExpensiveObject.java
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
package chapter2;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class ExpensiveObject {
|
||||||
|
|
||||||
|
public ExpensiveObject() {
|
||||||
|
/* fake long execution time*/
|
||||||
|
try {
|
||||||
|
TimeUnit.SECONDS.sleep(2);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
System.out.printf("Thread %s: new instance created (%s) \n", Thread.currentThread().getName(), this);
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/chapter2/LazyInit.java
Normal file
26
src/chapter2/LazyInit.java
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
package chapter2;
|
||||||
|
|
||||||
|
import common.TestHarness;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class LazyInit {
|
||||||
|
private ExpensiveObject expensiveObject = null;
|
||||||
|
|
||||||
|
public ExpensiveObject getInstance() {
|
||||||
|
if (expensiveObject == null) {
|
||||||
|
expensiveObject = new ExpensiveObject();
|
||||||
|
}
|
||||||
|
return expensiveObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
TestHarness testHarness = new TestHarness(8);
|
||||||
|
LazyInit lazyInit = new LazyInit();
|
||||||
|
|
||||||
|
testHarness.testMultiThreaded(() -> {
|
||||||
|
testHarness.waitForSignal();
|
||||||
|
lazyInit.getInstance();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
33
src/chapter2/SafeLazyInit.java
Normal file
33
src/chapter2/SafeLazyInit.java
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
package chapter2;
|
||||||
|
|
||||||
|
import common.TestHarness;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.LongAdder;
|
||||||
|
|
||||||
|
public class SafeLazyInit {
|
||||||
|
private ExpensiveObject expensiveObject = null;
|
||||||
|
|
||||||
|
public synchronized ExpensiveObject getInstance() {
|
||||||
|
if (expensiveObject == null) {
|
||||||
|
expensiveObject = new ExpensiveObject();
|
||||||
|
}
|
||||||
|
return expensiveObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
TestHarness testHarness = new TestHarness(100);
|
||||||
|
LongAdder waitTime=new LongAdder();
|
||||||
|
SafeLazyInit safeLazyInit = new SafeLazyInit();
|
||||||
|
|
||||||
|
testHarness.testMultiThreaded(() -> {
|
||||||
|
testHarness.waitForSignal();
|
||||||
|
long t0=System.nanoTime();
|
||||||
|
safeLazyInit.getInstance();
|
||||||
|
waitTime.add(System.nanoTime()-t0);
|
||||||
|
});
|
||||||
|
|
||||||
|
System.out.printf("Waiting for instance for %f milliseconds per thread\n", (waitTime.doubleValue()/1000000)/100);
|
||||||
|
}
|
||||||
|
}
|
||||||
51
src/chapter3/AttemptToShowReordering.java
Normal file
51
src/chapter3/AttemptToShowReordering.java
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
package chapter3;
|
||||||
|
|
||||||
|
import common.NotThreadSafe;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* fails on my machine
|
||||||
|
*/
|
||||||
|
@NotThreadSafe
|
||||||
|
public class AttemptToShowReordering {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
|
||||||
|
for (int i = 0; i < 1000_000; i++) {
|
||||||
|
|
||||||
|
State state = new State();
|
||||||
|
|
||||||
|
new Thread(() -> {
|
||||||
|
|
||||||
|
/////
|
||||||
|
state.a = 1;
|
||||||
|
state.b = 1;
|
||||||
|
state.c = state.a + 1;
|
||||||
|
/////
|
||||||
|
|
||||||
|
}).start();
|
||||||
|
|
||||||
|
new Thread(() -> {
|
||||||
|
int tmpC = state.c;
|
||||||
|
int tmpB = state.b;
|
||||||
|
int tmpA = state.a;
|
||||||
|
|
||||||
|
if (tmpB == 1 && tmpA == 0) {
|
||||||
|
System.out.println("WTF! b == 1 && a == 0");
|
||||||
|
}
|
||||||
|
if (tmpC == 2 && tmpB == 0) {
|
||||||
|
System.out.println("WTF! c == 2 && b == 0");
|
||||||
|
}
|
||||||
|
if (tmpC == 2 && tmpA == 0) {
|
||||||
|
System.out.println("WTF! c == 2 && a == 0");
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class State {
|
||||||
|
int a = 0;
|
||||||
|
int b = 0;
|
||||||
|
int c = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
60
src/chapter3/NonAtomicDoubleUpdates.java
Normal file
60
src/chapter3/NonAtomicDoubleUpdates.java
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
package chapter3;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
|
public class NonAtomicDoubleUpdates {
|
||||||
|
|
||||||
|
static Thread t1, t2, t3;
|
||||||
|
private static NonAtomicDoubleUpdates instance = new NonAtomicDoubleUpdates(0);
|
||||||
|
private static boolean running = true;
|
||||||
|
private final List<Double> allValues = new CopyOnWriteArrayList<>();
|
||||||
|
public double value;
|
||||||
|
private static Random random=new Random();
|
||||||
|
|
||||||
|
public NonAtomicDoubleUpdates(double value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
|
||||||
|
t1 = new Thread(() -> {
|
||||||
|
for (; running; ) {
|
||||||
|
instance.update();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
t2 = new Thread(() -> {
|
||||||
|
for (; running; ) {
|
||||||
|
instance.update();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
t1.start();
|
||||||
|
t2.start();
|
||||||
|
for (; running; ) {
|
||||||
|
instance.test();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update() {
|
||||||
|
value=Math.abs(random.nextInt(Integer.MAX_VALUE));
|
||||||
|
allValues.add(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void test() {
|
||||||
|
try {
|
||||||
|
if (value != value) {
|
||||||
|
System.out.println("WTF?");
|
||||||
|
running = false;
|
||||||
|
t1.join();
|
||||||
|
t2.join();
|
||||||
|
System.out.println(allValues);
|
||||||
|
System.exit(-1);
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
src/chapter3/Publication.java
Normal file
6
src/chapter3/Publication.java
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
package chapter3;
|
||||||
|
|
||||||
|
public class Publication {
|
||||||
|
|
||||||
|
public static String THE_ANSWER = "42";
|
||||||
|
}
|
||||||
20
src/chapter3/Visibility.java
Normal file
20
src/chapter3/Visibility.java
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
package chapter3;
|
||||||
|
|
||||||
|
public class Visibility {
|
||||||
|
|
||||||
|
private static boolean ready;
|
||||||
|
private static int value;
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
new Thread(() -> {
|
||||||
|
while (!ready) {
|
||||||
|
Thread.yield();
|
||||||
|
}
|
||||||
|
System.out.println(value);
|
||||||
|
}).start();
|
||||||
|
|
||||||
|
value = 42;
|
||||||
|
ready = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
33
src/chapter3/VolatileCretan.java
Normal file
33
src/chapter3/VolatileCretan.java
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
package chapter3;
|
||||||
|
|
||||||
|
public class VolatileCretan {
|
||||||
|
|
||||||
|
private static VolatileCretan instance = new VolatileCretan(0);
|
||||||
|
public volatile double value;
|
||||||
|
|
||||||
|
|
||||||
|
public VolatileCretan(double value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
new Thread(() -> {
|
||||||
|
for (; ; ) {
|
||||||
|
instance.value++;
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
|
||||||
|
new Thread(() -> {
|
||||||
|
for (; ; ) {
|
||||||
|
instance.test();
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void test() {
|
||||||
|
if (value != value) {
|
||||||
|
System.out.println("WTF?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
47
src/chapter3/VolatileStatus.java
Normal file
47
src/chapter3/VolatileStatus.java
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
package chapter3;
|
||||||
|
|
||||||
|
import common.ThreadSafe;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class VolatileStatus {
|
||||||
|
//shared mutable variable
|
||||||
|
private static boolean finished = false;
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(2);
|
||||||
|
|
||||||
|
// simulate some task in other thread
|
||||||
|
int waitTime = new Random().nextInt(10);
|
||||||
|
System.out.printf("task takes %d seconds \n", waitTime);
|
||||||
|
threadPool.schedule(() -> {
|
||||||
|
finished = true;
|
||||||
|
}, waitTime, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
// main thread waits for finished to be true
|
||||||
|
threadPool.submit(() -> {
|
||||||
|
int count = 0;
|
||||||
|
long t0 = System.currentTimeMillis();
|
||||||
|
|
||||||
|
while (!finished) {
|
||||||
|
System.out.printf("waiting for task to finish, %d\n", ++count);
|
||||||
|
try {
|
||||||
|
TimeUnit.SECONDS.sleep(1);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// ignore for now
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long t1=System.currentTimeMillis();
|
||||||
|
long actualTime=t1-t0;
|
||||||
|
|
||||||
|
System.out.printf("waited %d milliseconds", actualTime);
|
||||||
|
});
|
||||||
|
|
||||||
|
//orderly shutdown
|
||||||
|
threadPool.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/chapter4/Atomicity.java
Normal file
29
src/chapter4/Atomicity.java
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
package chapter4;
|
||||||
|
|
||||||
|
import common.TestHarness;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class Atomicity {
|
||||||
|
private final CopyOnWriteArrayList<String> values = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
new TestHarness(8).testMultiThreaded(() -> this.addOneEntry());
|
||||||
|
Assert.assertEquals(1, values.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addOneEntry() {
|
||||||
|
if (!values.contains("value")) {
|
||||||
|
try {
|
||||||
|
TimeUnit.MILLISECONDS.sleep(1);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
values.add("value");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
src/chapter4/Car.java
Normal file
8
src/chapter4/Car.java
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
package chapter4;
|
||||||
|
|
||||||
|
public class Car {
|
||||||
|
|
||||||
|
public void drive(){
|
||||||
|
System.out.printf("Thread %s driving this car", Thread.currentThread());
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/chapter4/Ownership.java
Normal file
10
src/chapter4/Ownership.java
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
package chapter4;
|
||||||
|
|
||||||
|
public class Ownership {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Car car = new Car();
|
||||||
|
car.drive();
|
||||||
|
//car is garbage collected
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
16
src/chapter4/Ownership2.java
Normal file
16
src/chapter4/Ownership2.java
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
package chapter4;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class Ownership2 {
|
||||||
|
private final static Map<String, Car> cars = new HashMap<>();
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Car car = new Car();
|
||||||
|
cars.put("toyota", car);
|
||||||
|
car.drive();
|
||||||
|
//car is not garbage collected
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
28
src/chapter5/ShowMeTheValues.java
Normal file
28
src/chapter5/ShowMeTheValues.java
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
package chapter5;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/* Will this give me the values? */
|
||||||
|
public class ShowMeTheValues {
|
||||||
|
|
||||||
|
private final List<Integer> values = new ArrayList<>();
|
||||||
|
private volatile int i = 0;
|
||||||
|
|
||||||
|
public static void main(String[] args) throws InterruptedException {
|
||||||
|
new ShowMeTheValues().run();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void run() throws InterruptedException {
|
||||||
|
new Thread(() -> {
|
||||||
|
for (; ; ) {
|
||||||
|
values.add(i++);
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
|
||||||
|
TimeUnit.SECONDS.sleep(1);
|
||||||
|
|
||||||
|
System.out.println(values);
|
||||||
|
}
|
||||||
|
}
|
||||||
34
src/chapter5/juc/HashMapTest.java
Normal file
34
src/chapter5/juc/HashMapTest.java
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
package chapter5.juc;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* https://stackoverflow.com/questions/18542037/how-to-prove-that-hashmap-in-java-is-not-thread-safe
|
||||||
|
*/
|
||||||
|
public class HashMapTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
Map<Integer, String> map = new HashMap<>();
|
||||||
|
|
||||||
|
Integer targetKey = Integer.MAX_VALUE;
|
||||||
|
String targetValue = "v";
|
||||||
|
map.put(targetKey, targetValue);
|
||||||
|
|
||||||
|
new Thread(() -> {
|
||||||
|
IntStream.range(0, targetKey).forEach(key -> map.put(key, "someValue"));
|
||||||
|
}).start();
|
||||||
|
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (!targetValue.equals(map.get(targetKey))) {
|
||||||
|
System.out.printf("Current size of map: %d",map.size());
|
||||||
|
throw new RuntimeException("HashMap is not thread safe.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/chapter6/CachedThreadPool.java
Normal file
23
src/chapter6/CachedThreadPool.java
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
package chapter6;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
public class CachedThreadPool {
|
||||||
|
private static final Executor threadpool = Executors.newCachedThreadPool();
|
||||||
|
|
||||||
|
public static void main(String[] args) throws IOException {
|
||||||
|
ServerSocket serverSocket = new ServerSocket(80);
|
||||||
|
for (; ; ) {
|
||||||
|
Socket clientSocket = serverSocket.accept();
|
||||||
|
threadpool.execute(() -> handleRequest(clientSocket));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void handleRequest(Socket clientSocket) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/chapter6/CompletionServiceExample.java
Normal file
25
src/chapter6/CompletionServiceExample.java
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
package chapter6;
|
||||||
|
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
public class CompletionServiceExample {
|
||||||
|
|
||||||
|
public static void main(String[] args) throws InterruptedException, ExecutionException {
|
||||||
|
ExecutorService executor = Executors.newCachedThreadPool();
|
||||||
|
ExecutorCompletionService<Integer> completionService = new ExecutorCompletionService<>(executor);
|
||||||
|
|
||||||
|
for (int i = 1; i < 11; i++) {
|
||||||
|
final int increment = i;
|
||||||
|
completionService.submit(() -> {
|
||||||
|
TimeUnit.SECONDS.sleep(increment);
|
||||||
|
return increment;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
Future<Integer> future = completionService.take();
|
||||||
|
int value = future.get();
|
||||||
|
System.out.println(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
41
src/chapter6/FixedThreadPoolWebserver.java
Normal file
41
src/chapter6/FixedThreadPoolWebserver.java
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
package chapter6;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* taken from listing 6.4. Added an actual response that the server will always give, proving that the thread is being reused.
|
||||||
|
*/
|
||||||
|
public class FixedThreadPoolWebserver {
|
||||||
|
|
||||||
|
private static final Executor threadpool = Executors.newFixedThreadPool(1);
|
||||||
|
|
||||||
|
public static void main(String[] args) throws IOException {
|
||||||
|
threadpool.execute(() -> {
|
||||||
|
Thread.currentThread().setName("Hello Visitor");
|
||||||
|
});
|
||||||
|
|
||||||
|
ServerSocket serverSocket = new ServerSocket(8080);
|
||||||
|
for (; ; ) {
|
||||||
|
Socket clientSocket = serverSocket.accept();
|
||||||
|
threadpool.execute(() -> handleRequest(clientSocket));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void handleRequest(Socket clientSocket) {
|
||||||
|
try {
|
||||||
|
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
|
||||||
|
out.println(createHttpReponse());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String createHttpReponse() {
|
||||||
|
return String.format("HTTP/1.0 200%nContent-type: text/plain%nContent-length: 14%n%n%s%n",Thread.currentThread().getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/chapter6/Shutdown.java
Normal file
29
src/chapter6/Shutdown.java
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
package chapter6;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class Shutdown {
|
||||||
|
public static void main(String[] args) throws InterruptedException {
|
||||||
|
ExecutorService threadPool = Executors.newCachedThreadPool();
|
||||||
|
threadPool.submit(() -> {
|
||||||
|
for (int i = 0; ; i++) {
|
||||||
|
threadPool.submit(() -> {
|
||||||
|
try {
|
||||||
|
TimeUnit.MICROSECONDS.sleep(100);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
System.out.print(".");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
TimeUnit.SECONDS.sleep(1);
|
||||||
|
|
||||||
|
System.out.println();
|
||||||
|
System.out.println("initiating shutdown");
|
||||||
|
|
||||||
|
threadPool.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/chapter6/ShutdownNow.java
Normal file
28
src/chapter6/ShutdownNow.java
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
package chapter6;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class ShutdownNow {
|
||||||
|
public static void main(String[] args) throws InterruptedException {
|
||||||
|
ExecutorService threadPool = Executors.newCachedThreadPool();
|
||||||
|
threadPool.submit(() -> {
|
||||||
|
for (int i = 0; ; i++) {
|
||||||
|
final int v = i;
|
||||||
|
threadPool.submit(() -> {
|
||||||
|
try {
|
||||||
|
TimeUnit.MICROSECONDS.sleep(10);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
System.out.print(".");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
TimeUnit.SECONDS.sleep(1);
|
||||||
|
System.out.printf("%ninitiating shutdown Now%n");
|
||||||
|
|
||||||
|
threadPool.shutdownNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/chapter6/SingleThreadExecutor.java
Normal file
19
src/chapter6/SingleThreadExecutor.java
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
package chapter6;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
|
||||||
|
public class SingleThreadExecutor {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||||
|
for (int i=0; i<10; i++){
|
||||||
|
final int count = i;
|
||||||
|
executor.submit(()-> print(count));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void print(int value){
|
||||||
|
System.out.println(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
36
src/chapter6/futures/CancelledFuture.java
Normal file
36
src/chapter6/futures/CancelledFuture.java
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
package chapter6.futures;
|
||||||
|
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
public class CancelledFuture {
|
||||||
|
|
||||||
|
private ExecutorService executor = Executors.newCachedThreadPool();
|
||||||
|
|
||||||
|
public static void main(String[] args) throws ExecutionException, InterruptedException {
|
||||||
|
new CancelledFuture().run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
private void run() throws InterruptedException, ExecutionException {
|
||||||
|
Future<String> futureData = getData();
|
||||||
|
System.out.printf("done: %b%n", futureData.isDone());
|
||||||
|
System.out.printf("cancelled: %b%n", futureData.isCancelled());
|
||||||
|
|
||||||
|
try {
|
||||||
|
futureData.cancel(true);
|
||||||
|
} finally {
|
||||||
|
System.out.printf("done: %b%n", futureData.isDone());
|
||||||
|
System.out.printf("cancelled: %b%n", futureData.isCancelled());
|
||||||
|
System.out.printf("result: %s%n", futureData.get());
|
||||||
|
}
|
||||||
|
executor.shutdownNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Future<String> getData() {
|
||||||
|
return executor.submit(() -> {
|
||||||
|
TimeUnit.SECONDS.sleep(5);
|
||||||
|
return "data";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
30
src/chapter6/futures/ExceptionalFuture.java
Normal file
30
src/chapter6/futures/ExceptionalFuture.java
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
package chapter6.futures;
|
||||||
|
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
public class ExceptionalFuture {
|
||||||
|
|
||||||
|
private ExecutorService executor = Executors.newCachedThreadPool();
|
||||||
|
|
||||||
|
public static void main(String[] args) throws ExecutionException, InterruptedException {
|
||||||
|
new ExceptionalFuture().run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
private void run() throws InterruptedException, ExecutionException {
|
||||||
|
Future<String> futureData = getData();
|
||||||
|
System.out.printf("done: %b%n", futureData.isDone());
|
||||||
|
System.out.printf("cancelled: %b%n", futureData.isCancelled());
|
||||||
|
|
||||||
|
System.out.printf("result: %s%n", futureData.get());
|
||||||
|
|
||||||
|
executor.shutdownNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Future<String> getData() {
|
||||||
|
return executor.submit(() -> {
|
||||||
|
TimeUnit.SECONDS.sleep(5);
|
||||||
|
throw new RuntimeException("exception is rule");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
32
src/chapter6/futures/TheFuture.java
Normal file
32
src/chapter6/futures/TheFuture.java
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
package chapter6.futures;
|
||||||
|
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
public class TheFuture {
|
||||||
|
|
||||||
|
private ExecutorService executor = Executors.newCachedThreadPool();
|
||||||
|
|
||||||
|
public static void main(String[] args) throws ExecutionException, InterruptedException {
|
||||||
|
new TheFuture().run();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void run() throws InterruptedException, ExecutionException {
|
||||||
|
Future<String> futureData = getData();
|
||||||
|
System.out.printf("done: %b%n", futureData.isDone());
|
||||||
|
System.out.printf("cancelled: %b%n", futureData.isCancelled());
|
||||||
|
|
||||||
|
futureData.cancel(true);
|
||||||
|
|
||||||
|
System.out.printf("result: %s%n", futureData.get());
|
||||||
|
System.out.printf("done: %b%n", futureData.isDone());
|
||||||
|
System.out.printf("cancelled: %b%n", futureData.isCancelled());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Future<String> getData() {
|
||||||
|
return executor.submit(() -> {
|
||||||
|
TimeUnit.SECONDS.sleep(5);
|
||||||
|
return "data";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
39
src/chapter7/TrickyTaskCancellation.java
Normal file
39
src/chapter7/TrickyTaskCancellation.java
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
package chapter7;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
|
||||||
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
|
|
||||||
|
public class TrickyTaskCancellation {
|
||||||
|
|
||||||
|
private static int count;
|
||||||
|
private static int countDown = 5;
|
||||||
|
|
||||||
|
public static void main(String[] args) throws InterruptedException {
|
||||||
|
Thread mainThread = Thread.currentThread();
|
||||||
|
|
||||||
|
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
||||||
|
|
||||||
|
//show countdown every second
|
||||||
|
scheduler.scheduleAtFixedRate(() -> System.out.println(countDown--), 0, 1, SECONDS);
|
||||||
|
|
||||||
|
//schedule interrupt in 5 seconds
|
||||||
|
scheduler.schedule(mainThread::interrupt, 5, SECONDS);
|
||||||
|
|
||||||
|
//do long running activity on main-thread while checking the interrupted status
|
||||||
|
for (; !mainThread.isInterrupted(); ) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// What happens if you use this code instead of the above?
|
||||||
|
// for (; !Thread.interrupted(); ) {
|
||||||
|
// count++;
|
||||||
|
// }
|
||||||
|
|
||||||
|
System.out.println("Done.");
|
||||||
|
System.out.printf("Thread status = %s\n", mainThread.isInterrupted() ? "interrupted" : "not interrupted");
|
||||||
|
|
||||||
|
scheduler.shutdownNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
4
src/common/NotThreadSafe.java
Normal file
4
src/common/NotThreadSafe.java
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
package common;
|
||||||
|
|
||||||
|
public @interface NotThreadSafe {
|
||||||
|
}
|
||||||
67
src/common/TestHarness.java
Normal file
67
src/common/TestHarness.java
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
package common;
|
||||||
|
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test Harness for multithreaded code
|
||||||
|
*/
|
||||||
|
public class TestHarness {
|
||||||
|
|
||||||
|
private final int threadCount;
|
||||||
|
private final CountDownLatch countDownLatch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a TestHarness
|
||||||
|
* @param threadCount the number of threads
|
||||||
|
*/
|
||||||
|
public TestHarness(int threadCount) {
|
||||||
|
this.threadCount = threadCount;
|
||||||
|
this.countDownLatch = new CountDownLatch(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* In the tested code, this method must call this method,
|
||||||
|
* to make the thread waits for the signal (the moment countDown is called on the latch).
|
||||||
|
* This technique (see 5.1.1 in JCiP) makes sure all threads start at exactly the same time,
|
||||||
|
* instead of creation time of the runnable. This increases concurrency to the max. */
|
||||||
|
public void waitForSignal() {
|
||||||
|
try {
|
||||||
|
countDownLatch.await();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes a runnable using a pool of threads.
|
||||||
|
*
|
||||||
|
* @param action The Runnable with the code that is te be tested.
|
||||||
|
*/
|
||||||
|
public void testMultiThreaded(Runnable action) {
|
||||||
|
ExecutorService threadPool = Executors.newFixedThreadPool(threadCount);
|
||||||
|
|
||||||
|
long t0 = System.currentTimeMillis();
|
||||||
|
|
||||||
|
for (int i = 0; i < threadCount; i++) {
|
||||||
|
threadPool.submit(() -> {
|
||||||
|
action.run();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* start !*/
|
||||||
|
countDownLatch.countDown();
|
||||||
|
|
||||||
|
try {
|
||||||
|
threadPool.shutdown();
|
||||||
|
threadPool.awaitTermination(10, TimeUnit.SECONDS);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
long t1 = System.currentTimeMillis();
|
||||||
|
System.out.printf("Total execution time in TestHarness: %d milliseconds\n", t1 - t0);
|
||||||
|
}
|
||||||
|
}
|
||||||
4
src/common/ThreadSafe.java
Normal file
4
src/common/ThreadSafe.java
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
package common;
|
||||||
|
|
||||||
|
public @interface ThreadSafe {
|
||||||
|
}
|
||||||
13
src/sleepers/Sleeper1.java
Normal file
13
src/sleepers/Sleeper1.java
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
package sleepers;
|
||||||
|
|
||||||
|
public class Sleeper1 {
|
||||||
|
|
||||||
|
// NB they are milliseconds
|
||||||
|
public static void main(String[] args) throws InterruptedException {
|
||||||
|
System.out.println("sleep");
|
||||||
|
|
||||||
|
Thread.sleep(1000);
|
||||||
|
|
||||||
|
System.out.println("wake up");
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/sleepers/Sleeper2.java
Normal file
15
src/sleepers/Sleeper2.java
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
package sleepers;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class Sleeper2 {
|
||||||
|
|
||||||
|
/* now the unit is clear*/
|
||||||
|
public static void main(String[] args) throws InterruptedException {
|
||||||
|
System.out.println("sleep");
|
||||||
|
|
||||||
|
TimeUnit.SECONDS.sleep(1);
|
||||||
|
|
||||||
|
System.out.println("wake up");
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/sleepers/Sleeper3a.java
Normal file
17
src/sleepers/Sleeper3a.java
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
package sleepers;
|
||||||
|
|
||||||
|
public class Sleeper3a {
|
||||||
|
|
||||||
|
/* or use Object.wait */
|
||||||
|
public static void main(String[] args) throws InterruptedException {
|
||||||
|
System.out.println("sleep");
|
||||||
|
|
||||||
|
Sleeper3a sleeper = new Sleeper3a();
|
||||||
|
|
||||||
|
synchronized (sleeper) { // synchronized is mandatory
|
||||||
|
sleeper.wait(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("wake up");
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/sleepers/Sleeper3b.java
Normal file
38
src/sleepers/Sleeper3b.java
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
package sleepers;
|
||||||
|
|
||||||
|
import common.TestHarness;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public class Sleeper3b {
|
||||||
|
|
||||||
|
/* what does this print? */
|
||||||
|
public void sleep() {
|
||||||
|
synchronized (this) {
|
||||||
|
try {
|
||||||
|
System.out.printf("[%s] thread %s sleep\n", new Date().toString(), Thread.currentThread().getName());
|
||||||
|
|
||||||
|
this.wait(1000);
|
||||||
|
|
||||||
|
System.out.printf("[%s] thread %s wake up\n", new Date().toString(), Thread.currentThread().getName());
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
TestHarness testHarness = new TestHarness(2);
|
||||||
|
Sleeper3b sleeper = new Sleeper3b();
|
||||||
|
|
||||||
|
testHarness.testMultiThreaded(() -> {
|
||||||
|
testHarness.waitForSignal();
|
||||||
|
sleeper.sleep();
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
33
src/sleepers/Sleeper4.java
Normal file
33
src/sleepers/Sleeper4.java
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
package sleepers;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* javadoc for Thread.yield:
|
||||||
|
*
|
||||||
|
* It is rarely appropriate to use this method. It may be useful
|
||||||
|
* for debugging or testing purposes, where it may help to reproduce
|
||||||
|
* bugs due to race conditions.
|
||||||
|
*/
|
||||||
|
public class Sleeper4 {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.out.println("sleep with yield");
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
long t0 = System.currentTimeMillis();
|
||||||
|
while (System.currentTimeMillis() - t0 < 1000) {
|
||||||
|
Thread.yield();
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
System.out.println("wake up");
|
||||||
|
System.out.printf("System.currentTimeMillis was called %d times%n", count);
|
||||||
|
|
||||||
|
System.out.printf("%nsleep without yield%n");
|
||||||
|
count = 0;
|
||||||
|
t0 = System.currentTimeMillis();
|
||||||
|
while (System.currentTimeMillis() - t0 < 1000) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("wake up");
|
||||||
|
System.out.printf("System.currentTimeMillis was called %d times%n", count);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue