Java Concurrency: JCIP Notes
1. Risks of Threads:
1> Safety Hazards
We can add annotation: @NotThreadSafe, @ThreadSafe, @Immutable
2> Liveness Hazards
Deadlock, Starvation, Livelock
Bugs that cause liveness failures can be elusive because they depend on the relative timing of events in different threads. and therefore do not always manifest themselves in development or testing.
3> Performance Hazards
1> Context switches are more frequent in application with many threads.
2> When threads share data, they must use synchronization mechanisms.
2. Thread Intro:
1> Timer.
1) TimerTasks are executed in a thread managed by the Timer, not the application.
2) If a TimerTask access data that is also accessed by other application threads, then not only must the TimerTask do so in a thread-safe manner, but so must any other classes that access the data.
The easiest way to achieve this is to ensure that objects accessed by the TimerTask are themselves thread-safe, thus encapsulating the thread safety within the shared objects.
2> Servlets & JSPs & ServletFilters
1> One Servlet/JSP may be accessed by multiple threads at the same time.
2> Servlet often access state information shared with other servlets, such as application-scoped objects(those stored in ServletContext) or session-scoped objects(those stored in the per-client HttpSession).
When a servlet accesses objects shared across servlets or requests, it must coordinate access to these objects properly.
Servlets need to be thread-safe.
3> RMI
When the RMI code calls your remote object, in what thread does that call happen?
You don't know, but it definitely not in a thread you created.
How many threads does RMI create? Could the same remote method on the same remote object be called simultaneously in multiple RMI threads?
1> Properly coordinating access to state that may be shared with other objects.
2> Peoperly coordinating access to the state of the remote object itself(since the same object may be called in multiple threads simultaneously).
4> Swing components, such as JTable, are not thread-safe.
3. Thread Safety
Informally, an object's state is its data, stored in state variables such as instance or static fields.
An object's state may include fields from other, dependent object.
If multiple threads access the same mutable state variable without appropriate synchrnization, your program is broken. There are three ways to fix it:
1> Don's share the state variable across threads. -> Stateless objects are always thread-safe.
2> Make the state variable immutable.
3> Use synchronization whenever accessing the state variable.
It's far easier to design a class to be thread-safe than to retrofit it for thread safety later.
4. Atomicity
public class UnsafeCountingFactorizer extends HttpServlet { private long count = 0; public long getCount() { return count; } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { count++; } }
public class UnsafeCountingFactorizer extends HttpServlet { private AtomicLong count = new AtomicLong(0); public long getCount() { return count.get(); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { count.incrementAndGet(); } }
If the counter is initially 9, with some unlucky timing each thread could read the value, see that it is 9, add one to it, and each set the counter to 10.
As "long" type is not threadsafe itself, it means multiple thread can access to it at the same time. We can use AtomicLong instead to ensure threadsafe.
An increment got lost along the way, and the hit counter is now permanently off by one.
If the counter is being used to generate sequences or unique object ids, returning the same value from multiple invocations could cause serious data integrity problems.
The possibility of incorrect results in the presence of unlucky timing is so important in concurrent programming that it has a name: a race condition.
1> Race Condition: -> Check-Then-Act(Lazy init) & Read-Modify-Write(Increment)
Getting the right answer relies on lucky timing.
The common type of race condition is check-then-act, where a potentially stale observation is used to make a decision on what to do next.
Using a potentially stale observation to make a decision or perform a computation.
This type of race condition is called check-then-act; you observe something to be true(file X doesn't exist), and then take action based on that observation(create X); but in fact the observation could have become invalid between the time you observed it and the time you acted on it., causing a problem(unexpected exception, overwritten data, file corruption).
1> Lazy initialization & Race Condition
A common idiom that uses check-then-act is lazy initialization.
public class LazyInitRace { private ExpensiveObject expensiveObject = null; public ExpensiveObject getInstance() { if (null == expensiveObject) { expensiveObject = new ExpensiveObject(); } return expensiveObject; } private static class ExpensiveObject { } }
2> Data Race:
You risk a data race whenever a thread writes a variable that might next be read by another thread, or reads a variable that might have last been written by another thread if both threads do not use synchronization.
There must be a way to prevent other threads from using a variable while we are in the middle of modifying it, so we can ensure that other threads can observe or modify the state only before we start or after we finish, but not in the middle.
Back to the example of UnsafeCountingFactorizer, the state of the servlet it the state of the counter and the counter is thread-safe, our servlet is once again thread-safe.
5. Locking
Back to the example of UnsafeCountingFactorizer, what if we want to add more state to our servlet, can we just add more thread-safe state variables?
Imagine we want to cache the most recently computed result, just in case two connsecutive clients request factorization of the same number.
We need to remember two things: the last number factored, and its factors.
public class UnsafeCachingFactorizer extends HttpServlet { private final AtomicReference<BigInteger> lastNumber = new AtomicReference<BigInteger>(); private final AtomicReference<BigInteger[]> lastFactors = new AtomicReference<BigInteger[]>(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { BigInteger value = new BigInteger(req.getParameter("number")); if (value.equals(lastNumber.get())) { resp.addHeader("Factors", Arrays.asList(lastFactors.get()) .toString()); } else { BigInteger[] factors = calculateFactor(value); lastNumber.set(value); lastFactors.set(factors); resp.addHeader("Factors", Arrays.asList(factors).toString()); } } private BigInteger[] calculateFactor(BigInteger value) { try { Thread.sleep((long) (1000 * Math.random())); } catch (InterruptedException e) { e.printStackTrace(); } return new BigInteger[]{}; } }
Unfortunately, this approach does not work. Even though the atomic references are individually thread-safe, UnsafeCachingFactorizer has race conditions that could make it produce the wrong answer.
Locking is not just about mutual exclusion; it is also about memory visibility.
After we exit a synchronized block, we release the monitor, which has the effect of flushing the cache to main memory, so that writes made by this thread can be visible to other threads.
Before we can enter a synchronized block, we acquire the monitor, which has the effect of invalidating local processor cache so that variables will be reloaded from main memory.
public class NotVisibility { private static boolean ready; private static int number; private static class ReaderThread extends Thread { @Override public void run() { while (!ready) { Thread.yield(); } System.out.println(number); } } public static void main(String[] args) { new ReaderThread().start(); number = 42; ready = true; } }
This ReaderThread could loop forever because the value of ready might never become visible for reader thread.
Even more strangely, NoVisibility could print zero because the write to ready might be made visible to the reader thread before write to number, a phenomenon known as reordering.
Modern shared-memory multiple processor archectures, each processor has one or more levels of cache that are periodically reconciled with main memory.
The visibility of writes to shared data can be problematic.
To ensure that, all threads sees the most up-to-date value of shared variables, the reading and writing threads must synchronize on a common lock.
1) Volatile Variables:
When a field is declared volatile, the compiler and runtime are put on notice that this variable is shared and that operations on it should not be reordered with other memory operations.
Volatile variables are not cached in registers or in caches where they are hidden from other processors, so read a volatile variable always returns the most recent write by any thread.
We can think of volatile var as a lighter synchronized mechanism.
"Use volatile variables only when they simply implementing and verifying your synchronization policy; avoid using volatile variables when verifying correctness would require subtle reasoning about visibility. Good uses of volatile variables include ensuring the visibility of their own state, that of the object they refer to, or indicating that an important lifycycle event(such as initialization or shutdown) has occurred"
2) Publication and Escape
1> Publications: Make it available to code outside its current scope
2> Escape: An object is published when it should not have been.
1) Publishing states in this way is problematic because any caller can modify its contents. The states array has escaped its intended scope, because what we supposed to be private field has been effectively made public. Once an object escapes, you have to assume that another class or thread may, maliciously or carelessly, misuse it.
public class UnsafeState { private String[] states = new String[] { "AL", "AK" }; public String[] getStates() { return states; } }
2) When ThisEscape publishes EventListener, it implicitly publishes the enclosing ThisEscape instance as well, because inner class instances contain a hidden reference to the enclosing instance.
public class ThisEscape { public ThisEscape(EventSource source) { source.registerListener(new EventListener(){ public void onEvent(Event e){ doSomething(e); } }); } }
Use Factory method to prevent the "this" reference from Escaping During Construction
public class SafeListener { private final EventListener listener; private SafeListener() { listener = new EventListener() { public void onEvent(Event e) { doSomething(e); } }; } public static SafeListener newInstance(EventSource source) { SafeListener safe = new SafeListener(); source.registerListener(safe.listener); return safe; } }
3) Safe Construction Practices
The "this" reference shouldn't escape from the thread until after the constructor returns.
Don't allow the "this" reference to escape during construction.
A common mistake that can let the "this" reference escape during construction is to start a thread from a constructor. When an object creates a thread from its constructor, it almost always shares its "this" reference with the new thread, either explicitly(by passing it to the constructor) or implicitly(because the Thread or Runnable is an inner class of the owing object). The new thread might then be able to see the owning object before it is fully constructed. There is nothing wrong with creating a thread in a constructor, but it is best not to start the thread immediately. Instead, expose a start or initialize method that starts the owned thread.
public class ConstructorExpose { private int initialCapicity; private Thread daemonthread; public ConstructorExpose(int ainitialCapicity) throws InterruptedException { super(); this.daemonthread = new Thread(new Runnable() { @Override public void run() { System.out.println(initialCapicity); } }); daemonthread.start(); Thread.sleep(1000); this.initialCapicity = ainitialCapicity; } }
public class ConstructorExposeTest { @Test public void constructTest() throws InterruptedException { new ConstructorExpose(42); } } //0
Above is an unsafe publish/escape which we should care of.
public class ConstructorExpose2 { private int initialCapicity; private Thread daemonthread; public ConstructorExpose2(int ainitialCapicity) throws InterruptedException { super(); this.daemonthread = new Thread(new Runnable() { @Override public void run() { System.out.println(initialCapicity); } }); this.initialCapicity = ainitialCapicity; } public void initialize() { daemonthread.start(); } }
public class ConstructorExpose2Test { @Test public void constructTest() throws InterruptedException { new ConstructorExpose2(42).initialize(); } }
4) Thread Confinement
If data is only accessed from a single thread, no synchronization is needed. This technique, thread confinement, is one of the simplest ways to achieve thread safe.
When an object is confined to a thread, such usage is automatically thread-safe even if the confined object itself is not.
1) Swing uses thread confinement extensively. The Swing visual components and data model objects are not thread-safe; instead, safety is achieved by confining them to the Swing event dispatch thread. Many concurrency errors in Swing applications stem from improper use of these confined objects from another thread.
2) Pooled JDBC Connection objects. The JDBC specification doesn't require that Connection objects be thread-safe. Connection pool impelmentations provided by applications servers are thread-safe, connection pools are necessarily accessed from multiple threads, so a non-thread-safe implementation would not make sense.
It has no means of confining an object to a thread. Thread confinement is an element of your program's design that must be enforced by its implementation.
We can use ThreadLoacal provided by JDK - But even with these, it is still the programmer's responsibility to ensure that thread-confined objects do not escape from their intended thread.
5) ThreadLocal
6) Immutability: An immutable object is one whose state cannot be changed after construction.
Immutable objects are inherently thread-safe; their invariants are estabished by the constructor. and if their state cannot be changed, their invariants always hold.
An object is immutable if:
1) Its state cannot be modified after construction 2) All its fields are final 3) It is properly constructed(The this reference doesn't escape during construction)
There is a difference between an object being immutable and the reference to it being immutable.
Program state in immutable objects can still be updated by "replacing" immutable objects with a new instance holding new state.
"Some developers fear that this approach will create performance problems, but these fears are usually unwarrented. Allocation is cheaper than you might think, and immutable objects offer additional performance advantages such as reduced locking or defensive copies and reduced impact on generational garbage colleciton".
Reference Links:
1) http://*.com/questions/4818699/practical-uses-for-atomicinteger
2) https://www.securecoding.cert.org/confluence/display/java/Concurrency,+Visibility,+and+Memory
3) http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html
推荐阅读
-
Java 并发编程 - Programming Concurrency on the JVM
-
Java Concurrency: Thread&Locks
-
Java Concurrency in Practice 学习笔记(序)
-
Notes 20180508 : Java基本程序设计结构之关键字与标识符
-
2018.3.6 Java web notes:
-
Java concurrency线程池之Callable和Future详解
-
Java多线程设计模式之双重检查加锁实战(Java concurrency patterns:double-checked locking)...
-
Java concurrency线程池之Callable和Future详解
-
Java concurrency线程池之关于Callable和Future的代码详解
-
Java concurrency线程池之关于Callable和Future的代码详解