66th: Synchronizing access to shared mutable data
The Java language Specification guarantees that reading or writing a variable is atomic (atomic), unless the variable is of type long or double.
[Non-atomic exploration of long and double type operations in Java] (
HTTP://BLOG.CSDN.NET/ZHAIFENGMIN/ARTICLE/DETAILS/46315003)
You may expect this program to run for about a second or so, after which the main thread sets staprequested to true, causing the background thread's loop to terminate. But on my machine, this program never terminates: Because the background thread is always looping!
Active Failure
The problem is that, because there is no synchronization, there is no guarantee that the background thread will "see" The changes made to the stoprequested value by the main thread. Without synchronization, the virtual machine will have this code:
while(!done){
i++;
}
Change to:
if(!done){
while(true){
i++;
}
}
This is acceptable. This optimization, called elevation (hoisting), is the work of the hotspot Server VM. The result is an activity failure (likeness failure): This program cannot go forward.
The first type of correction: synchronous Read and Write
One way to fix this problem is to synchronize access to the stoprequested domain. This program terminates as expected within about a second:
Note that the Write method (RequestStop) and the Read method (stoprequested) are synchronized. Only synchronous write method is not enough! In fact, synchronization does not work if both the read and write operations are not synchronized .
using the volatile modifier
If stoprequested is declared volatile, the lock in the second version of Stopthread can be omitted. Although the volatile modifier does not perform mutually exclusive access, it guarantees that any thread reading the domain will see the value that was recently written:
Security Failure
Always be careful when using volatile. Consider the following method, assuming that it produces a serial number:
The problem is that the increment operator (+ +) is not atomic. It performs two operations in the Nextserialnumber domain: first it reads the value and then writes back a new value. Equivalent to the original value plus 1. If the second thread reads the domain while the first thread reads the old value and writes back the new value, the second thread will see the same value along with the first thread and return the same sequence number. This is a security failure (Safety failure): This program calculates the wrong result.
Fix One: Use the synchronised adornment method
One way to fix the Generateserialnumber method is to add the synchronised modifier to its declaration. This ensures that multiple calls do not cross-access, ensuring that each call will see the effect of all previous calls. Once this is done, you can and should remove the volatile modifier from the nextserialnumber.
fix One: use Atomiclong
With class Atomiclong, the work it does is exactly what you want, and it is possible to perform better than the synchronous version of Generateserialnumber:
In short, when multiple threads share variable data, each thread that reads or writes data must perform synchronization.
67th: Avoid over-synchronization
In order to avoid rabbit activity failure and security failure, in a synchronized method or code block, never give up when the client's press-in other words, within a synchronized region, do not call the method designed to be overwritten, or the client is provided as a function object in the form of a method.
Typically, you should do as little work as possible within the sync area. Get the lock, check the shared data, convert the data as needed, and then drop the lock. If you have to perform a time-consuming action, you should try to move the action outside the sync area without violating the guidelines in 66th.
In this multicore era, the actual cost of over-synchronization does not refer to the CPU time spent acquiring locks, but the chance of losing parallelism, and the delay caused by the need to ensure that each core has a consistent memory view. Another potential cost of over-synchronization is that it limits the ability of the VM to optimize code execution.
If you synchronize classes internally, you can use different methods to achieve high concurrency, such as split lock (lock splitting), split lock (lack striping), and non-blocking (nonblocking) well control. These methods are beyond the scope of this book.
68th: Executor and tasks take precedence over threads
If you are writing a small program, or a light-loaded server, using Executors.newcachedthreadpool is usually a good choice because it does not need to be configured and is normally able to do the work correctly. But for heavy-duty servers, the cached thread pool is not a good choice! In a high-load product server, it is best to use Executors.newfixedthreadpool, which provides you with a thread pool that contains a fixed number of threads, or you can use the Threadponlexecutor class directly for maximum control.
Not only should you try not to write your own work queue, but you should also try not to use threads directly. Now the key abstraction is no longer a thread, it used to serve both as a unit of work and as an execution mechanism. Now the unit of work and execution mechanism are separate. Now the key abstraction is the unit of work, called the task. There are two kinds of tasks: runnable and its close relatives callable (it is similar to runnable, but it returns a value). The common mechanism for performing tasks is executor services.
69th: Concurrency tools take precedence over wait and notify
It is more difficult to use wait and notify artifacts correctly, instead of using more advanced concurrency tools.
The more advanced tools in Java.util.concurrent are divided into three categories: the Executor Framework (described), the concurrent collections (Concurrent Collection), and the Synchronizer (Synchronizer).
Concurrent Collections
Concurrent collections provide a high-performance concurrency implementation for standard collection interfaces such as list, queue, and map. To provide high concurrency, these implementations manage their own synchronization internally (see article 67th). Therefore, it is not possible to exclude concurrent activities in a concurrent collection, and locking it up has no effect, only slowing the program.
Prefer to use Concurrentnashmap instead of using Colfections.synchronizedmap or Hashtable. Replacing old-fashioned synchronous maps with concurrent maps can greatly improve the performance of concurrent applications. More generally, concurrent collections should be preferred instead of using externally synchronized collections.
Some of the collection interfaces have been extended by blocking operations (blocking operation), and they wait (or block) until they can be executed successfully. For example, Blockingqueue extends the queue interface and adds several methods, including take, which are removed from the queue and return the header element, and wait if the queue is empty. This allows blocking queues to be used in work queues, also known as producer one consumer queues (Producer-consumer
Queue), one or more producer threads (producer thread) adds work items to the work queue, and when the work item day is available, one or more consumer threads (consumer thread), Shell Al, pulls the queue from the work queue and processes the work item. Unsurprisingly. Most Executarservice implementations (including threadpoolexecutor) use Blockingqueue.
Synchronous Device
Synchronizer (Synchronizer) is an object that enables threads to wait on another thread, allowing them to coordinate actions. The most commonly used Synchronizer is countdownlatch and semaphore. Less commonly used are cyclicbarrier and exchanger.
For intermittent timing, it is always preferable to use the system.nanotime instead of the system.currenttimemills. The system.nanotime is more accurate and accurate, and it is not affected by the system's real-time clock adjustments.
70th: Documentation of thread safety
In order for a class to be used safely by multiple threads, the level of thread security it supports must be clearly stated in the documentation. The following list outlines several levels of thread safety. This list does not cover all the possibilities, but it is a common scenario:
- Immutable (immutable)
Instances of this class are immutable. Therefore, no external synchronization is required. Examples of this include string, long, and BigInteger.
- Unconditional thread safety (unconditionally Thread-safe)
Instances of this class are mutable, but the class has enough internal synchronization so that its instances can be used concurrently without any external synchronization. Examples include random and concurrentjashmap.
- Conditional thread Safety (conditionally Thread-safe)
This level of thread security is the same as unconditional thread safety, except that some methods require external synchronization for secure concurrent use. Examples include the collection returned by the Cnllections.synchronized wrapper, whose iterators (Iteratar) require external synchronization.
- Non-thread safe (not Thread-safe)
Instances of this class are mutable. In order to use them concurrently, the customer must surround each method call (or sequence of calls) with an external synchronization of their choice. Examples include generic collection implementations, such as Larraylist and HashMap.
71st: Delay initialization with caution
Use lazy initialization If you need lazy initialization for a static domain for performance reasons holder
Class mode. This mode (also known as Initialize-on-demand Holder class idiom) ensures that classes are initialized only when they are used. As shown below:
When the GetField method is first called, it reads the Fieldholder.field for the first time, causing the Fieidhalder class to be initialized. The charm of this pattern is that the GetField method is not synchronized, and only one domain access is performed, so delayed initialization actually does not add any access cost to the well.
If you need to use lazy initialization for your instance domain for performance reasons, use double-check mode (double-check idiom). This mode avoids the locking overhead of accessing the domain after the domain is initialized. The idea behind this pattern is: Two check the value of the field [so the name is called double check (double-check)], the first check is not locked, to see if the domain is initialized; There is a lock on the second check. The Computefieidvalue method is called to initialize the domain only if the second check indicates that the domain is not initialized. Because if the domain has been initialized there will be no locks, and it is important that the domain is declared volatile (see 66th). Here is the habit of the camphor-type:
This code may seem a bit confusing. In particular, it may be somewhat confusing to use the local variable result. The purpose of this variable is to ensure that the field is read only once, and improves performance, only once it has been initialized.
"Effective Java," the 10th chapter issued and