Java Concurrency In Practice - Question to Ask

Java Concurrency In Practice - Question to Ask

Thread Safety

What are the differences between Concurrency and Parallel?

What is reentrancy lock?

Simple race condition:

  • read-modify-write
  • check-then-act

Guarded by a lock

  • For every invariant that involves more than on variable, all the variables involved in that invariant must be guarded by the same lock
  • Avoid holding locks during lengthy computations or operations at risk of not completing quickly such as network or console I/O

Sharing Objects

Locking & Visibility

  • Related to Phantom read in DB.
  • Reordering (JVM allow reordering)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class NoVisibility {
private static boolean ready;
private static int number;
private static class ReaderThread extends Thread {
public void run() {
while (!ready)
Thread.yield();
System.out.println(number);
}
public static void main(String[] args) {
new ReaderThreadO().start();
number = 42;
ready = true;
}
}
}

Lesson: Always use the proper synchronization whenever data is shared across threads.

  • Nonatomic 64-bit operations (Long and Double)

  • Locking is not just about mutual exclusion; it is also about memory visibility

  • volatile variables (use with caution)

1
2
3
4
5
6
volatile boolean asleep; // will be set by other thread

// ...

while (!asleep)
countSomeSheep();
  • Locking can guarantee both visibility and atomicity; volatile variables can only guarantee visibility;

Publication and escape

  • alien method
  • Inner class instances contain a hidden reference to the enclosing instance
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class ThisEscape {
    public ThisEscape(EventSource source) {
    source.registerListener(
    new EventListener() {
    public void onEvent(Event e) {
    doSomething(e);
    }
    }
    });
    }
  • Do not allow the this reference to escape during construction (A common mistake: start a thread from a constructor)

Thread confinement

  • Design consideration
    Confine the modification to a single thread to prevent race conditions for volatile variables
  • Stack confinement V.S. ThreadLocal

Immutability

Unmodifiable state, all fields are final, and proper construction.

  • Immutable objects are always thread-safe.
  • It is a good practice to make all fields final unless they need to be mutable.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @ThreadSafe
    public class VolatileCachedFactorizer implements Servlet {
    private volatile OneValueCache cache = new OneValueCache(null, null):
    public void service(ServletRequest reg, ServletResponse resp) {
    BigInteger i = extractFromRequest(req);
    BigInteger[] factors = cache.getFactors(i);
    if (factors == null) {
    factors = factor(i);
    cache = new OneValueCache(i, factors);
    }
    encodeIntoResponse(resp, factors);
    }
    }

Safe publication idioms

Provided the object is effectively immutable

  • Initializing an object reference from a static initializer;
  • Storing a reference to it into a volatile field or AtomicReference;
  • Storing a reference to it into a final field of a properly constructed object; or Storing a reference to it into a field that is properly guarded by a lock (Java native implementation like ConcurrentLinkedQueue, static initializers);

Safely published effectively (why, research happen-before relationship in JVM memory model) immutable objects can be used safely by any thread without additional synchronization.

Lesson:

  • Immutable objects can be published through any mechanism
  • Effectively immutable objects must be safely published;
  • Mutable objects must be safely published, and must be either thread-safe or guarded by a lock for inner state.

Composing Objects

Consider the whether:

  • The reference assignment is thread-safe or not
  • The object is thread-safe or not

Pattern:

  • Identify the variables that form the object’s state;
  • Identify the invariants that constrain the state variables; (State ownership, who control the lock)
  • Establish a policy for managing concurrent access to the object’s state.

Instance confinement

The Java monitor pattern

  • Intrinsic lock is public accessible. Clients may use it correctly or incorrectly.

Delegating thread safety

  • Independent state variables

Composition

  • Favor composition over inheritance (and access to public intrinsic lock).

Building Blocks

Synchronized collections

Problems:

  • Compound actions -> Know which lock to lock (check-then-act)
  • Iterators and ConcurrentModificationException
  • Hidden iterators (e.g. Iteration hidden within string concatenation)

Concurrent collections

  • Replacing synchronized collections with concurrent colections

  • ConcurrentHashMap: weakly consistent

  • CopyOnWriteArrayList: as long as an effectively immutable object is properly published, no further synchronization is required

  • Blocking queues and the producer-consumer pattern
    SynchronousQueue: direct handling from producer to consumer

  • Serial thread confinement -> Pass ownership/&make visibiable to other thread

  • Deque and work stealing

Blocking and interruptible methods

  • Interruption is a cooperative mechanism

Synchronizers

  • Latches: A latch acts as a gate. Lates can be used to ensure that certain activities do not proceed until other one-time activities complete. Used to wait other event

  • FutureTask: Also acts like a latch

  • Semaphores: Counting semaphores are used to control the number of activities that can access a certain resource or perform a given action at the same time. e.g. Bounded resource (list, set, map)

  • Barriers: All the threads must come together at a barrier point at the same time in order to proceed. Used to wait other threads. e.g. CyclicBarrier

Reinvent Wheels: Case: Cache

Task Execution

Using an Executor is usually the easiest path to implementing a producer-consumer design in your application.

Thread pools

Executor lifecycle

  • Running
  • Shutting Down
  • Terminated

Boundary of task

Lifecycle of a task by Executor:

  • created
  • submitted
  • started
  • completed

e.g. Future

Assigning a different type of task to each worker does not scale well

ExecutorCompletionService

Executor meets BlockingQueue

invokeAll

Author

Xiao Pu

Posted on

2020-01-21

Updated on

2020-11-21

Licensed under

Comments