Java Thread Miscs
Multi-thread
considerations
1-
Synchronize access to shared mutable data and
be ware of insufficient synchronization
When multiple threads share mutable data, each thread that
reads or writes the data must perform synchronization.
The synchronized keyword ensures that only a single thread
can execute a method or block at one time and prevent an object from being
observed in an inconsistent state while it's being modified by another thread,
also it ensures one thread's changes would be visible to other threads.
2-
Avoid excessive synchronization
Excessive synchronization can cause reduced performance,
deadlock, or even nondeterministic behavior.
Avoid synchronization by other approaches.
1. Don't share mutable data, but share immutable data or a
cloned one.
2. Use utilities in java.util.concurrent package, such as
AtomicLong
3-
Limit the amount of work within synchronized regions.
Do as little work as possible inside synchronized, If we
must perform some time-consuming activity, find a way to move the activity out
of the synchronized block.
4-
Prefer executors and tasks to threads
5-
Never use suspend, resume and stop of thread.
6-
For interval timing, always use System.nanoTime
7-
Document thread safety
8-
Avoid multiple locking.
9-
Acquire lock in some consistent well-defined order
volatile
keyword
volatile is used to indicate that a variable's value will
be modified by different threads.
Declaring a volatile Java variable means: it will not be
stored in the thread's local cache. Whenever thread is updating the values, it
is updated to the main memory. So, other threads can access the most recently
written value.
The value of this variable will never be cached
thread-locally: all reads and writes will go straight to "main
memory".
ps: When multiple threads using the same variable, each
thread will have its own copy of the local cache for that variable. So, when
it's updating the value, it is actually updated in the local cache not in the
main variable memory. The other thread which is using the same variable doesn't
know anything about the values changed by the another thread.
Avoid busy-wait
busy-wait is the code repeatedly checks a shared object
waiting for
something to happen.
API on thread
yield()
Causes the currently executing thread object to
temporarily pause and allow other threads to execute.
Other methods such as suspend, resume and stop are
deprecated, and not recommended to use as they are deadlock-prone and not safe.
Thread.stop can result in data corruption.
public final void join() throws InterruptedException
Waits for this thread to die.
public final void join(long millis)
Waits at most millis milliseconds for this thread to die.
Difference
between notify and notifyAll
notify wakes a single waiting thread, and notifyAll wakes
all waiting threads.
It is safer to use notifyAll.
It will always yield correct results. You may wake some
other threads, too, but this won't affect the correctness of your program.
These threads will check the condition for which they're waiting and, finding it
false, will continue waiting.
Using notifyAll in place of notify protects against
accidental or malicious waits by an unrelated thread. Such waits could
otherwise “swallow” a critical notification, leaving its intended recipient
waiting indefinitely.
As an optimization, you can choose to invoke notify
instead of notifyAll.
Timer and
ScheduledThreadPoolExecutor
A timer uses only a single thread for task execution,
which can hurt timing accuracy in the presence of long-running tasks. If a
timer's sole thread throws an uncaught exception, the timer ceases to operate.
A scheduled thread pool executor supports multiple threads
and recovers gracefully from tasks that throw unchecked exceptions.
For interval
timing, always use System.nanoTime
System.nanoTime is used to time the activity rather than
System.currentTimeMillis. For interval
timing, always use System.nanoTime in preference to System.currentTime-
Millis. System.nanoTime is both more accurate and more
precise, and it is not
affected by adjustments to the system's real-time clock.
Improve
Performance for Lazy initialization
Under most circumstances, normal initialization is
preferable to lazy initialization.
Use lazy
initialization holder class idiom on a static field
// Lazy initialization holder class idiom for static fields
private static class FieldHolder {
static final FieldType field = computeFieldValue();
}
static FieldType getField() { return FieldHolder.field; }
When the getField method is invoked for the first time, it
reads Field-
Holder.field for the first time, causing the FieldHolder
class to get initialized.
A modern VM will synchronize field access only to
initialize the class.
Once the class is initialized, the VM will patch the code
so that subsequent access to the field does not involve any testing or synchronization.
Use
Double-check idiom on an instance field
This idiom avoids the cost of locking when access-
ing the field after it has been initialized.
It is critical that the field be declared volatile
private volatile FieldType field;
FieldType getField()
{
FieldType result
= field;
if (result ==
null) { // First check (no locking)
synchronized(this) {
result =
field; // this is important
if (result
== null) // Second check (with locking)
field =
result = computeFieldValue();
}
}
return result;
}
Prefer
concurrency utilities to wait and notify
The higher-level utilities in java.util.concurrent fall
into three categories: the Executor Framework, concurrent collections; and
synchronizers.
The concurrent collections provide high-performance
concurrent implementations of standard collection interfaces such as
ConcurrentHashMap, ConcurrentSkipListMap, ConcurrentLinkedQueue,
ConcurrentSkipListSet.
They combine several primitives into a single atomic operation.
For example, ConcurrentMap extends Map and adds several
methods, including putIfAbsent(key, value).
Prefer
executors and tasks to threads
It provide thread pool, various kinds of executors.
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(runnable);
executor.shutdown();
In a lightly loaded server, use
Executors.newCachedThreadPool.
In a cached thread pool, submitted tasks are not queued
but immediately handed off to a thread for execution. If no threads are
available, a new one is created.
In a heavily loaded server, use
Executors.newFixedThreadPool.
Use executor
service provided in java.unit.concurrent package.
The key abstraction is the unit of work, which is called a
task. There are two kinds of tasks: Runnable and Callable (which is like
Runnable, except that it returns a value).
Deadlock
Deadlock is a situation where two or
more threads are blocked forever, waiting for each other. When a thread holds a
lock forever, other threads attempting to acquire that lock will block forever
waiting. When thread A holds lock L and tries to acquire lock M, but at the
same time thread B holds M and tries to acquire L, both threads will wait
forever.
How to avoid deadlocks
Acquire lock in some consistent
well-defined order
Deadlock usually occurs when multiple
threads need the same locks but obtain them in different order.
Shrink synchronized blocks to avoid
multiple locking
Do as little work as possible inside
synchronized regions.
If you must perform some time-consuming activity, find a way to
move the activity out of the synchronized region.
Never leave control to the client within a synchronized method or block.
Don't call an alien method from within a synchronized region.
If you must perform some time-consuming activity, find a way to
move the activity out of the synchronized region.
Never leave control to the client within a synchronized method or block.
Don't call an alien method from within a synchronized region.
Use Open Calls to Avoiding Deadlock
Between Cooperating Objects
Example:
Inconsistent Lock-ordering Deadlocks
public class LeftRightDeadlock {
private
final Object left = new Object();
private
final Object right = new Object();
public void
leftRight() {
synchronized (left) {
synchronized (right) {doSomething();}
}
}
public void
rightLeft() {
synchronized (right) {
synchronized (left)
{doSomethingElse();}
}
}
}
Dynamic Lock Order Deadlocks
The lock order depends on the order of
arguments passed to transferMoney, and these in turn might depend on external
inputs. Deadlock can occur if two threads call transferMoney at the same time, one
transferring from X to Y, and the other doing the opposite.
public void transferMoney(Account
fromAccount,Account toAccount,DollarAmount amountToTransfer) {
synchronized
(fromAccount) {
synchronized (toAccount) {
if
(fromAccount.hasSufficientBalance(amountToTransfer) {
fromAccount.debit(amountToTransfer);
toAccount.credit(amountToTransfer);
}
}
}}
Consider what happens when thread A
executes:
transferMoney(accountOne, accountTwo,
amount);
While at the same time, thread B
executes:
transferMoney(accountTwo, accountOne,
anotherAmount);
Again, the two threads try to acquire
the same two locks, but in different orders.
Use an ordering to acquire locks in a
fixed sequence
public void transferMoney(Account
fromAccount,Account toAccount,DollarAmount amountToTransfer) {
Account firstLock, secondLock;
if (fromAccount.accountNumber() ==
toAccount.accountNumber())
throw new
Exception("Cannot transfer from account to itself");
else if (fromAccount.accountNumber()
< toAccount.accountNumber()) {
firstLock =
fromAccount; secondLock = toAccount;}
else {
firstLock =
toAccount; secondLock = fromAccount;}
synchronized
(firstLock) {
synchronized (secondLock) {
if
(fromAccount.hasSufficientBalance(amountToTransfer) {
fromAccount.debit(amountToTransfer);
toAccount.credit(amountToTransfer);
}
}}}
Another way to induce an ordering on
objects is to use System.identityHashCode, which
returns the value that would be returned by Object.hashCode.
Starvation
Starvation describes a situation where
a thread is unable to gain regular access to shared resources and is unable to
make progress. This happens when shared resources are made unavailable for long
periods by "greedy" threads.
Difference between
Thread.sleep() and Object.wait()
Thread.sleep
1. Thread.sleep
is static method of Thread class and Thread.sleep would throw
InterruptedException when it is awaken by other thread prematurely.
2. Thread.sleep
can be used anywhere in the code to halt the current thread execution for the
specified time.
3. Thread.sleep
causes the current thread into Non-Runnable state, but, it doesn't release
ownership of the acquired monitors. So, if the current thread is into a
synchronized block/method, then no other thread will be able to enter that
block/method.
4. Thread.sleep(n)
guarantees to sleep for at least n milliseconds. But it may sleep for
considerably more if the system is busy.
5. If somebody
changes the system time, you may sleep for a very long time or no time at all.
The behavior is OS and JDK-dependent.
Object.wait
1.
Object.wait is instance method of Object
class.
2.
The wait method is used to put the thread
aside for up to the specified time. It could wait for much lesser time if it
receives a notify() or notifyAll() call.
3.
Object.wait have to be called inside
synchronized block, otherwise it would throw IllegalMonitorStateException.
4.
Object.wait causes the current thread into
Non-Runnable state, like Thread.sleep, but Object.wait releases the locks
before going into Non-Runnable state. This causes the current thread to wait
either another thread invokes the 'notify()' method or the 'notifyAll()' method
for this object, or a specified amount of time has elapsed.
5.
Using sleep makes your codes non-scalable
across hardwares, where a nicely written wait/notify code scales well.
Resources: