←Older revision |
Revision as of 15:30, 1 August 2018 |
Line 35: |
Line 35: |
| <source lang="java"> | | <source lang="java"> |
| | | |
- | public abstract class Helper {
| |
- |
| |
- | private int value1;
| |
- | private int value2;
| |
- | private int value3;
| |
- |
| |
- | protected abstract int combine(int x, int y);
| |
- |
| |
- | public final void update() {
| |
- | int v1;
| |
- | int v2;
| |
- | int v3;
| |
- | while (true) {
| |
- | synchronized (this) {
| |
- | v1 = value1;
| |
- | v2 = value2;
| |
- | v3 = value3;
| |
- | }
| |
- |
| |
- | int r1 = combine(v1, v2);
| |
- | int r2 = combine(v2, v3);
| |
- | int r3 = combine(v3, v1);
| |
- |
| |
- | synchronized (this) {
| |
- | if (v1 == value1 && v2 == value2 && v3 == value3) {
| |
- | value1 = r1;
| |
- | value2 = r2;
| |
- | value3 = r3;
| |
- | return;
| |
- | }
| |
- | }
| |
- | }
| |
- | }
| |
- | }
| |
- |
| |
- | </source>
| |
- |
| |
- | This is the correct code which removes the [[deadlock]] danger and still keeps consistency. A kind of manually written [[TransactionalMemory]]. First of all atomically read the values needed for the computation. Then release the lock and call the unknown code. Then obtain the lock again, make sure nobody else modified the internal state and if so, modify the state of the object. If the state of the object has been modified meanwhile, repeat the computation with new values.
| |
- |
| |
- | The code can be made even cleaner, if the internal data are kept in a special immutable object:
| |
| | | |
- | <source lang="java">
| + | import java.util.concurrent.atomic.AtomicReference; |
| | | |
| public abstract class Helper { | | public abstract class Helper { |
Line 93: |
Line 53: |
| } | | } |
| | | |
- | private Data data = new Data(0, 0, 0); | + | private AtomicReference<Data> data = new AtomicReference<>(new Data(0, 0, 0)); |
| | | |
| protected abstract int combine(int x, int y); | | protected abstract int combine(int x, int y); |
Line 100: |
Line 60: |
| Data current; | | Data current; |
| while (true) { | | while (true) { |
- | synchronized (this) { | + | current = data.get(); |
- | current = this.data;
| + | |
- | }
| + | |
| | | |
| int r1 = combine(current.value1, current.value2); | | int r1 = combine(current.value1, current.value2); |
Line 108: |
Line 66: |
| int r3 = combine(current.value3, current.value1); | | int r3 = combine(current.value3, current.value1); |
| | | |
- | synchronized (this) { | + | if (data.compareAndSet(current, new Data(r1, r2, r3))) { |
- | if (current == this.data) {
| + | return; |
- | this.data = new Data(r1, r2, r3);
| + | |
- | return;
| + | |
- | }
| + | |
| } | | } |
| } | | } |
Line 118: |
Line 73: |
| } | | } |
| </source> | | </source> |
| + | |
| + | Neat usage of immutability. The data that are supposed to be consistent are '''final''' in a ''Data'' class. The mutability is handled all at once with {{JDK|java/util/concurrent/atomic|AtomicReference}} ''compareAndSet'' method. Either it fails and the computation runs again or it succeeds and atomically changes the data to newly created and consistent value. |
| + | |
| + | Avoiding [[deadlock]] this way sort of trades the [[deadlock]]s for live-locks. It is not limited how many times the cycle is executed. Under some bad setup, it might even run indefinitely. It helps, if there is some invariant that gets stabilized with every change of the ''data'' field. For example {{NB|org/openide/util/lookup|AbstractLookup}} is using the same approach and with every update of the ''data'' field, the internal data structure moves towards optimum - as a result it is clear that after some number of loops, there will be no class and the update succeeds. |