java concurrent programming combat finishing notes

First, the security thread

    In the security thread, the core concept of correctness, but the correctness of the meaning is: a class act and its specification exactly. The specification can be roughly understood here as defined under various conditions, the results are consistent with the expected class object. Single thread, the accuracy can be approximately defined as a "what you see is known (we know it when we see it)". In clear about the concept of "safe sex", we can consider thread safety is this: when multiple threads access a class that can consistently show the correct behavior, then this class can be considered thread-safe of. 

When multiple threads access a class, regardless of the operating environment using or scheduling these threads will alternate how to perform and does not require any additional synchronization or synergistic in the calling code, the class can show proper behavior , then call this class is thread-safe.

   Thread-safe class can also be considered as a concurrent environment and the single-threaded environment will not be disrupted classes. If a class in a single-threaded environment is not thread-safe class, then it is certainly not thread-safe class. Here is an example of a thread-safe class:

public class StatelessFactorizer implements Servlet{
    public void service(ServletRequest req, ServletResponse resp){
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = factor(i);
        encodeIntoResponse(resp, factors);
    }
}
复制代码

   This StatelessFactorizer is stateless: it contains neither any domain, does not contain any references to other classes in the domain. The method of local variables can only be accessed by executing thread. If there are multiple threads simultaneously access StatelessFactorizer, it will not affect each other between these threads, because there is no shared state between threads and, as if to access different instances.

   Since the behavior of threads access the object is stateless and does not affect the validity of other threads in operation, therefore stateless objects are thread-safe and stateless objects must be thread-safe.

Second, atomicity

   What is the atomicity of it? Atomicity is a fact no longer divided nature, can not be subdivided into finer granularity.

   If we add a status (either a counter) is just an example, the statistics for the number of requests have been processed, the processing of each request will be added to the value 1, the following procedure:

public class StatelessFactorizer 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);
    }
} 
复制代码

  In the program example above, at first glance, no problem, ++ count looks like an operation, but the increment operation is not atomic. Because in fact, it contains three operations: "reading - modification - writing" sequence of operations. Each operation is dependent on a state before the front. If at this time there are two threads A, B, if A thread has been to modify the operation at this time if the read thread B, then the final A, B thread writes the value is the same, so that deviations from expected results 1. 

  Although this seems, some of which may deviate from the acceptable results, but if the value of the counter value is used to generate sequences or unique object identifier, then returns the same value will result in multiple calls serious data integrity issues .

  In concurrent programming, as this is a very important case due to improper execution timing of the emergence of an incorrect result, this situation is called a "race condition (Race Condition)".

Race Conditions

  When a calculation accuracy depends on the timing of the execution of multiple threads alternately when a race condition that occurs. Common types of race condition "Implementation of the first check" operation, not only to determine the next action by the observations of a possible failure .

  For chestnut: you and your friends made an appointment to go with black cafe open, you get a cafe, I found your friend is not, then you might choose to stay in the cafe waiting for him, he could go to his house looking for him, if you go find him, then when you are out of the cafe after your observations in Internet cafes (friend not) may fail, because he might you go to his house to find his way has come to the cafe, but you have to look for him.

  The chestnut, the result is correct (you met at the bar), but the results depend on the timing of events (both long and who should wait for the other side to the cafe). Failure of this observation is that the nature of most race conditions - based on the observation result of a possible failure to perform a calculation or judgment.

  As another chestnut, assume there are two threads A, B, A, B threads are used to determine if a folder exists, create it does not exist, provided that when A thread finds the folder does not exist, is planning to create a file folder, but this time B thread has finished creating the folder, then this time the results of observations a thread would have expired, but a thread is still based on this observation expired during the next move, which could lead to various problems.

A common use case "check after the first execution" is lazy initialization. For example, there would in a single mode of one embodiment worded as follows:

public class LazyInitRace {
	private static LazyInitRace instance = null;
	
	public LazyInitRace getInstance(){
		if(instance == null){
			instance = new LazyInitRace();
		}
		return instance;
	}
} 复制代码

  This is a typical lazy initialization, in a single thread write nothing wrong, but in a multithreaded environment, if there are A, B threads execute getInstance () method, then the result may be in line with expectations, you may also get two different Object. Since the discovery instace A thread is null, B threads may also find instace is null.

  Like with most concurrency errors, race conditions do not always produce the error, you need some kind of inappropriate timing of the execution, but if a problem occurs, it may lead to very serious problems.

  In the above example it contains a set of actions need to be performed (or integral) atomically. To avoid race conditions problem, it is necessary to modify the variable when a thread, some way to prevent other threads to use this variable to ensure that other threads can read and modify before or after the operation is completed modifying state, rather than in the modification process .

  When the above statistics have been processing requests example of the number, we can replace long use AtomicLong object because AtmoicLong class is thread-safe class, so you can guarantee examples are also examples safe, but add a state variable, whether can also use thread-safe class of objects to manage and to maintain its status thread safety it? As follows:

public class UnsafeCachingFactorizer implements Servlet {
	private final AtomicReference<BigInteger> lastNumber = new AtomicReference<>();
 
	private final AtomicReference<BigInteger[]> lastFactors = new AtomicReference<>();
 
	public void service(ServletRequest req, ServletResponse resp) {
		BigInteger i = extractFromRequest(req);
		if (i.equals(lastNumber.get())) {
			encodeIntoResponse(resp, lastFactors.get());
		} else {
			BigInteger[] factors = factor(i);
			lastNumber.set(i);
			lastFactors.set(factors);
			encodeIntoResponse(resp, factors);
		}
	}
} 复制代码

In the above example, although the two variables are thread-safe, but there is still a race condition in the service method, since in the above example, the class invariant conditions have been destroyed, only to ensure that this condition is not invariant destroyed, it is correct. When invariance condition relates to a plurality of variables, each variable among not independent, but the value of other variables have the value of a variable in another constraint generation. Thus, when a certain variable update, need to update the other variables in the same atomic operation.

   In the above example, although the atomic operation is set, but can not be updated simultaneously lastFactors lastNumber and set methods. If and when a thread executes lastNumber.set () method has not executed the next set method, if at this time there is a thread to access the service method, then the result will get inconsistent with what we expected.

Therefore, to maintain the consistency of the state, it is necessary to update all the associated state variables in a single atomic operation.

Third, the locking mechanism

3.1 Built-in lock

   Providing a built-in Java locking mechanism to support atomic: synchronization code blocks (Synchronized Block). Sync block consists of two parts: one is a reference to the object lock, this block is composed of a lock as a code protection. Object (this) is a method to lock to modify the keyword synchronized across the method is a method of synchronization code blocks in the entire body, wherein the block synchronization code call is made. Static method Class object is synchronized as a lock. 

   Every Java object can be used as a synchronization lock to achieve, these locks are called built-in lock (Intrinsic Lock) locks or monitor (Monitor Lock). Thread will automatically lock before entering the synchronized block, and automatically releases the lock when you exit the synchronized block.

   Java's built-in lock equivalent of mutual exclusion lock up only one thread can hold this lock . When a thread A thread B tries to get hold of a lock, blocking or thread A must wait to know the thread B releases the lock. If the thread B does not release the lock, then thread A will wait forever. Any thread executing a synchronized block, and can not see any other threads are executing a synchronized block protected by the same lock.

The following examples of the application of the built-in lock:

public class SynchronizedFactorizer implements Servlet {
	private BigInteger lastNumber;
 
	private BigInteger[] lastFactors;
 
	public synchronized void service(ServletRequest req, ServletResponse resp) {
		BigInteger i = extractFromRequest(req);
		if (i.equals(lastNumber)) {
			encodeIntoResponse(resp, lastFactors.get());
		} else {
			BigInteger[] factors = factor(i);
			lastNumber = i;
			lastFactors = factors;
			encodeIntoResponse(resp, factors);
		}
	}
} 复制代码

   Although synchrnoized keyword to ensure the correctness of the result, but only one thread can perform the service methods at the same time, which leads to very low responsiveness of services, concurrency is very bad, it becomes a performance issue, not thread-safety issues.

3.2 reentrant

   When a thread requests a lock is held by another thread, the requesting thread will be blocked, however, due to the built-in locks are reentrant, that is, if a thread has been trying to get a hold of its own when the lock, then the request will succeed. "Re-entry" means the operation to acquire the lock granularity is "thread" rather than "calling." A Method is reentrant, each associated with a count value of a lock owner and a thread. When the count value is 0, I think that this lock is not held by any thread. When a thread requests the lock is not held, the JVM note the lock holder, and the acquired count value is set to 1. If you acquire the lock again with a thread count value is incremented, and when the thread exits the synchronized block, the count value is decremented accordingly. When the count value is 0, the lock is released. 

The following is an example of a re-entry:

public class Widget{
    public synchronized void doSomething(){
        System.out.println(toString() + ": calling doSomething");
    }
}
public class LoggingWidget extends Widget{
    public synchronzied void doSomething(){
           System.out.println(toString() + ": calling doSomething");
           super.doSomething();
    }
} 复制代码

In the above example, LoggingWidget inherited Widget and rewrite the parent class, and are synchronized with the keyword modifications doSomething method, if the object is a subclass calling doSomething method. If no reentrant lock, then this code will deadlock. Because each doSomething way to get a lock on the Widget before executing the will, if the built-in lock is not reentrant, so when you call super.doSomething can not get a lock on the Widget, because this lock has been held up, so that the thread I will always go on pause, waiting for a lock never obtained. Note: Here is the synchronized keyword modified the method body, which means it is locked object itself (this), so when you first enter the doSomething method, is locked LoggingWidget object, calling super.doSomething when the parent does not create a new class object, the object or lock this. 

Fourth, the use locks to protect state

   For variable state variable may be accessed simultaneously by multiple threads when you access it needs to hold the same lock, in this case, we call this state variable is a lock protection. No inherent association between an object and its built-in lock state, domain object is not necessarily protected by the built-in locks. When acquiring associated with the object lock does not prevent other threads access the object after obtaining a thread lock object, can only prevent other thread to acquire the same lock, each object has a built-in lock. 

   Each shared variables and variable should only be protected by a lock. A common convention is locked, the state variable are all encapsulated within the object, and synchronize all access code Road King built-variable state by the lock object, such that concurrent access does not occur on the object. However, if you forget when you add a new method or use synchronous code path, then this agreement will lock can easily be destroyed.

   We should know that not all data needs protection locks, variable data only accessed by multiple threads at the same time it needs to be protected by a lock. When a variable is protected by a lock, you need to first obtain the lock means that every time when access to this variable, thus ensuring that only one thread can access a variable at the same time. When invariance condition relates to more state variables of the class, then there is another demand: Each variable invariant conditions must be protected by the same lock. 

   Although synchronization to avoid race conditions problem, but that does not mean you can use the keywords are synchronized. If there will be too much synchronization method program, activity may cause problems or performance issues when each method declaration.

   We should try to shared state and will not affect the time to execute an operation separated from the block the synchronization code, the synchronization code blocks as much as possible to ensure that there is only atomic operation.

   In use the lock, it should be clear the function implemented in the code block, and whether it takes a long time when the execution of the code block, when a long computation time or execution of operations may not be completed quickly (e.g., network I / O or console I / O), must not hold the lock !!!


Guess you like

Origin juejin.im/post/5de7a9876fb9a016402d06af