Update Time: 2017-06-03
"Java Concurrency Programming Practical" Digest, interested friends can buy a paper book carefully under study.
Threads Security 1.1 What is thread safety
This class is thread-safe when multiple threads access a class, regardless of how the runtime environment is scheduled or how the threads are alternately executed, and if no additional synchronization or collaboration is required in the keynote code, and the class behaves correctly.
The necessary synchronization mechanisms are encapsulated in the thread security class, so the client does not need to take further synchronization measures.
Example: A non-stateful servlet
/** * @author Brian Goetz and Tim Peierls */@ThreadSafepublic class StatelessFactorizer extends GenericServlet implements Servlet { public void service(ServletRequest req, ServletResponse resp) { BigInteger i = extractFromRequest(req); BigInteger[] factors = factor(i); encodeIntoResponse(resp, factors); } void encodeIntoResponse(ServletResponse resp, BigInteger[] factors) { } BigInteger extractFromRequest(ServletRequest req) { return new BigInteger("7"); } BigInteger[] factor(BigInteger i) { // Doesn‘t really factor return new BigInteger[] { i }; }}
As with most servlets, this class is stateless: it contains neither a domain nor any references to fields in other classes. The temporary state in the calculation is stored only in local variables on the thread stack, and can only be accessed by the executing process. Stateless objects are thread-safe because the behavior of threads accessing stateless objects does not affect the correctness of operations in other threads.
1.2 atomicity
An example of a thread that is not secure
@NotThreadSafepublic class UnsafeCountingFactorizer extends GenericServlet implements Servlet { private long count = 0; public long getCount() { return count; } public void service(ServletRequest req, ServletResponse resp) { BigInteger i = extractFromRequest(req); BigInteger[] factors = factor(i); ++count; encodeIntoResponse(resp, factors); } void encodeIntoResponse(ServletResponse res, BigInteger[] factors) { } BigInteger extractFromRequest(ServletRequest req) { return new BigInteger("7"); } BigInteger[] factor(BigInteger i) { // Doesn‘t really factor return new BigInteger[] { i }; }}
count
A domain (instance variable) can be shared across multiple threads, ++count
not an atomic operation, which contains read-modify-write operations. When two threads do not synchronize at the same time count
will result in incorrect results, these two threads are likely to read the same value and both have performed an increment operation.
This incorrect result due to improper execution timing is a very important case, it has a formal name: Race condition.
To avoid race condition problems, you must somehow prevent other threads from using this variable when a thread modifies the variable, ensuring that other threads can read and modify the state only before or after the modification operation is complete, rather than in the process of modifying the state. Each thread performs atomic operations, either the thread is fully executed, or it is not executed at all.
Let's modify the previous UnsafeCountingFactorizer
class so that it becomes a thread-safe class:
@ThreadSafepublic class CountingFactorizer extends GenericServlet implements Servlet { private final AtomicLong count = new AtomicLong(0); public long getCount() { return count.get(); } public void service(ServletRequest req, ServletResponse resp) { BigInteger i = extractFromRequest(req); BigInteger[] factors = factor(i); count.incrementAndGet(); encodeIntoResponse(resp, factors); } void encodeIntoResponse(ServletResponse res, BigInteger[] factors) {} BigInteger extractFromRequest(ServletRequest req) {return null; } BigInteger[] factor(BigInteger i) { return null; }}
java.util.concurrent.atomic
There are some atomic variable classes in the package that implement atomic state transitions on numeric and object references. By using AtomicLong
long
a counter that replaces the type, you can ensure that all access to the counter state is atomic.
In practice, you should use existing thread-safe objects to manage the state of your classes whenever possible. It is easier to determine the possible state of a thread-safe object and its state transitions than non-thread-safe objects, making it easier to maintain and validate thread security.
1.2 Plus lock mechanism
When a state variable is added to a servlet, it can be managed through thread-safe objects. If you want to add more states, is it enough to simply add more thread-safe state variables?
The answer is no, in order to maintain state consistency, it is necessary to update all related state variables in a single atomic operation.
Java provides a built-in locking mechanism to support atomicity: synchronizing blocks of code. It consists of two parts: an object reference as a lock, and a block of code that is protected by this lock. A method that synchronized
is decorated with a keyword is a synchronous block of code that spans the entire method body, where the lock of the synchronous block is the object on which the method call resides. The static synchronized
method takes a class object as a lock.
synchronized (lock) { // 访问或修改由锁保护的共享状态}
Each Java object can be used as a lock for synchronization, which is called a built-in lock or a monitor lock. The thread automatically obtains the lock before it enters the synchronization code block, and automatically releases the lock when exiting the synchronization code block, whether it exits gracefully or by throwing an exception from the code block. The only way to get a built-in lock is to enter a synchronous code block or method protected by this lock.
Java's built-in lock is equivalent to a mutex, which means that only one thread can hold this lock at most. If a thread holds the lock for some reason and does not release the lock, then the other threads will wait forever. Although this lock-protected synchronous code block is executed atomically, which ensures thread safety, this approach is too extreme because multiple clients cannot use the servlet at the same time, and the responsiveness of the service is very low and unacceptable.
Fortunately, by narrowing the scope of the synchronized code block, it is easy to ensure that both the servlet and the release are maintained while maintaining thread security. Make sure the synchronization code block is not too small, and do not split the operation that should have been atomic into more than one synchronous block of code. Operations that do not affect shared state and take longer to execute should be detached from the synchronization code block as much as possible so that other threads can access the shared state during the execution of these operations.
Do not hold a lock when the execution takes longer to calculate or when an operation that may not be completed quickly (for example: Network I/O) must not be held.
Sharing of two objects 2.1 non-atomic 64-bit operations
When a thread reads a variable without synchronization, it may get a practical value, but at least it is a value set by a previous thread, not a random value. This security guarantee is called minimum security.
The minimum security applies to most variables, but there is one exception: non- volatile
Type 64-bit numeric variables ( double
and long
). The JVM allows 64-bit read and write operations to be decomposed into two 32-bit operations. If a read and write operation on a variable of a non- volatile
type is long
performed on a different thread, it is likely to read the high 32 bits of a value and the low 32 bits of the other value. Therefore, it is unsafe to use shared and variable 64-bit numeric variables in multithreaded programs, and there is no guarantee of minimum security. Unless we use keywords volatile
to declare them, or to use locks to protect them.
2.2 Plus lock and visibility
The meaning of the lock is not only limited to mutex behavior, but also includes memory visibility. To ensure that all threads can see the latest values for shared variables, all threads that perform read and write operations must synchronize on the same lock.
2.3 Volatile variable
The Java language provides a slightly weaker synchronization mechanism, which is a variable that is volatile
used to ensure that updates to variables are notified to other threads. When a variable is declared as a volatile
type, the compiler and the runtime will notice that the variable is shared, so the operation on that variable is not reordered with other memory operations. volatile
variables are not cached in registers or in places that are not visible to other processors, so the volatile
most recently written value is always returned when a variable of type is read.
volatile
You should use variables only if they simplify the implementation of your code and validate your synchronization policies. If you need to make complex judgments about visibility when validating correctness, do not use volatile
variables. volatile
the correct use of variables includes:
- Ensure their own visibility.
- Ensure the visibility of the state of the objects they reference
- Identifies the occurrence of some important program life cycle events (for example, initialization or shutdown)
The locking mechanism ensures both visibility and atomicity, while volatile
variables only ensure visibility.
Not finished, to be continued ...
"Java Concurrency Programming Practice" Digest