_02 concurrent programming thread safety

When multiple threads to access a state variable which has a thread and a write operation, the synchronization mechanism must be used to access these synergistic thread variables. Primary synchronization mechanism is a keyword in the synchronized Java, which provides exclusive locking way, but this part of the synchronization further comprises volatile variable, and atomic explicit lock variable.

2.1 What is the thread safety

When multiple threads access a class that can consistently show the correct behavior, then call this class is thread-safe.

The use of security thread is not directly derived from the use of threads, but the use of such as servlet framework.

//一个无状态的servlet
//这是一个简单的因数分解servlet

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

Most of the same servlet, above servlet is stateless: it contains neither any domain, does not contain any reference to other classes in the domain.

Stateless objects must be thread-safe.

Most are stateless servlet, which greatly reduces the complexity in implementing the servlet thread safety. Only when the servlet when processing requests need to save some of the information, thread safety will become a problem.

 

2.2 Atomicity

++ count: it appears to be an operation, but this operation is not yard atoms, which contains three separate operations - read the value of the count, the value plus 1, the calculation result is written count.

Race conditions: Incorrect results in concurrent programming occurs due to improper execution timing.

@NotThreadSafe

public class LazyInitRace{
    private ExpensiveObject instance = null;

    public ExpensiveObject getInstance(){
        if(instance == null)
            instance = new ExpensiveObject();
        return instance;
    }
}

A common use case "check after the first execution" is lazy initialization. The purpose is to delay the initialization of object initialization operation is carried out only postpone the actual use, while ensuring only be initialized once.

A, B while performing this procedure, it is possible to have read the instance is null, this is creates a new instance, will return different results when the final call getInstance.

It contains a number of atoms in the variable classes Java.util.concurrent.atomic package for realizing value in atomic state and convert object reference. Long type instead of the counter by using AtomicLong, ensures that all access operations are atomic counter state. Below is an example:

@ThreadSafe

public class CountingFactorizer 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);
    }
}

2.3 locking mechanism

2.3.1 Built-in lock

Java provides built-in locking mechanism to support atomic: synchronization code blocks (Synchronized Block). Sync block consists of two parts: a lock as an object reference, a block of code of the lock as a protection. 

Every Java object can be used as a synchronization lock to achieve, these locks are called built-in locks or monitor locks. Thread will automatically lock before entering the synchronized block, and automatically releases the lock when you exit the synchronized block. And whether or exit through the only way to quit unexpectedly thrown from the code, get a lock is built into the lock by the synchronization code of this fast or methods of protection by the normal control path.

Java's built-equivalent mutual exclusion lock (or mutex), which means that at most only one thread can hold this lock.

//这个servlet能正确的缓存最新的计算结果,但并发性却非常糟。

@ThreadSafe 
public class SynchronizedFactorizer implements Servlet{
    @GuardedBy("this") private BigInteger lastNumber;
    @GuardedBy("this") private BigInteger[] lasstFactors;

    public synchronized void service(ServletRequest req, ServletResponse resp){
        BigInteger i = extractFromRequest(req); 
        if(i.equals(lastNumber))
            encodeIntoResponse(resp,lastFactors);
        else{
            BigInteger[] factors = factor(i);
            lastNumber = i;
            lastFactors = factors;
            encodeIntoResponse(resp,factors);
        }
    }
}

2.3.2 reentrant

When a thread requests a lock held by another thread, the requesting thread will be blocked. However, due to the built-in locks are reentrant, so if a thread trying to get a lock already held by its own, then the request will succeed.

If the built-in lock is not re-entrant, then this code will deadlock

public class Widget{
    public synchronized void doDomething(){
        ...
    }
}

public class LoggingWidget extends Widget(){
    System.out.println(toString()+":calling doSomething");
    super.doSomething();
}

2.4 activity and performance

When multiple requests arrive simultaneously factorization Servlet, these requests are queued for processing. We refer to this web application called bad concurrent applications: the number of simultaneous calls is not only limited by available processing resources, is also limited by the structure of the application itself.

Solution: by reducing the scope of the synchronized block, it is easy to do both to ensure concurrency servlet at the same time maintaining thread safety. To ensure synchronization code block not too small, and should not to be split into a plurality of operation atoms synchronization code blocks. It should not affect the shared state as far as possible and performs the operation longer separated from the synchronization code block, so that during the execution of these operations, other threads can access the shared state.

The following program listing, CatchedFactorized the Servlet code sync block is modified to use two separate, each sync block contains only a short code. Wherein a sync block is responsible for protecting determines whether the cached results simply return "check performed after" sequence of operations, another synchronization code is responsible for ensuring that cache block value and a synchronization factor decomposition updated. In addition, we also re-introduced a "hit counter", add a "cache hit" counter, and updates these two variables in the first synchronization code block. Since these two counters also share part of the variable state, so you must use to access their synchronized in all locations. Located outside the synchronization code block code will exclusive access to the local (located on the stack) variables that do not shared among multiple threads again, so no synchronization.

@ThreadSafe
public class CatchedFactorizer implements Servlet{
    @GuardedBy("this") private BigInteger lastNumber;
    @GuardedBy("this") private BigInteger[] lastFactors;
    @GuardedBy("this") private long hits;
    @GuardedBy("this") private long catchHits;

    public synchronized long getHits(){
        return hits;
    }
    public synchronized double getCatchedHitRatio(){
        return (double)catchHits/(double)hits;
    }
    public void service(ServletRequest req,ServletResponse resp){
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = factor(i);
        synchronized (this){
            ++hits;
            if(i.equals(lastNumber)){
                ++catcheHits;
                factors = lastFactors.clone();
            }
        }
        if(factors == null){
            factors = factor(i);
            synchronized this(){
                lastNumber = i;
                lastFactors = factors.clone();
            }
        }
        encodeIntoResponse(resp,factors);
    }
}

 

Guess you like

Origin blog.csdn.net/strawqqhat/article/details/91400631