Home  >  Article  >  Java  >  How to implement thread safety in Java concurrent programming

How to implement thread safety in Java concurrent programming

WBOY
WBOYforward
2023-05-21 15:14:131493browse

    1. What is thread safety

    When multiple threads access a class, regardless of the calling method used by the runtime environment or the threads How will the execution be executed alternately, and this class can show the correct behavior without any additional synchronization or coordination in the main calling code, then this class is said to be thread-safe.

    Stateless objects must be thread-safe, such as: Servlet.

    2. Atomicity

    2.1 Race condition

    A race condition occurs when incorrect results occur due to improper execution timing.

    The "check first and then execute" operation is to determine the next action through a possible and effective observation result. For example: lazy initialization.

    if(instance == null) {
        instance = new SomeObject();
    }

    The result state of the "read-modify-write" operation depends on the previous state. Such as: increment operation.

    long count = 0;
    count++;

    2.2 Composite operation

    Atomic operation means that for all operations that access the same state (including the operation itself), this operation is executed in an atomic manner (indivisible) operation.

    In order to ensure thread safety, a set of operations that must be performed atomically is included, called compound operations.

    Incremental operations can use an existing thread-safe class to ensure thread safety. For example:

    AtomicLong count = new AtomicLong(0);
    count.incrementAndGet();

    3. Locking mechanism

    If a class has only one state variable, you can ensure the thread safety of the class by using a thread-safe state variable. When a class has more state, it's not enough to just add more thread-safe state variables. To ensure state consistency, all relevant state variables must be updated in a single atomic operation.

    3.1 Built-in lock

    Java provides a built-in lock: Synchronization code block, It includes: An object reference as a lock, an object as The block of code protected by this lock.

    The method modified with the keyword synchronized is a synchronized code block that spans the entire method body, and the lock of the synchronized code block is the object where the method is called. The static synchronized method uses the Class object as the lock.

    When the thread enters the synchronized code block, it will automatically acquire the lock; and when the thread exits the synchronized code block, it will automatically release the lock. At most one thread can hold this lock, so synchronized code is executed atomically.

    3.2 Reentrancy

    The built-in lock is reentrant, which means that the granularity of the operation to acquire the lock is the thread, not the call. When a thread attempts to reacquire a lock already held by it, the request also succeeds.

    Reentrancy further improves the encapsulation of locking behavior and simplifies the development of object-oriented concurrent code.

    public class Widget {
        public synchronized void doSomething() {
            //......
        }
    }
    public class LoggingWidget extends Widget {
        public synchronized void doSomething() {
            //......
            super.doSomething();//假如没有可重入的锁,该语句将产生死锁。
        }
    }

    4. Use locks to protect state

    For mutable state variables that may be accessed by multiple threads at the same time, you need to hold the same lock when accessing it. In this case , it is said that the state variable is protected by this lock.

    5. Activity and performance

    Coarse-grained use of locks ensures thread safety, but it may cause performance problems and activity problems, such as:

    @ThreadSafe
    public class SynchronizedFactorizer implements Servlet {
        @GuardedBy("this") private BigInteger lastNumber;
        @GuardedBy("this") private BigInteger[] lastFactors;
    
        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);
            }
        }
    }

    You can ensure the concurrency of the servlet and maintain thread safety by shrinking the synchronization code block. Do not split what should be atomic operations into multiple synchronized code blocks. Try to separate operations that do not affect shared state and take a long time to execute from synchronized code. Such as:

    public class CachedFactorizer implements Servlet {
        @GuardedBy("this") private BigInteger lastNumber;
        @GuardedBy("this") private BigInteger[] lastFactors;
    
        public void service(ServletRequest req, ServletResponse resp) {
            BigInteger i = extractFromRequest(req); 
            BigInteger[] factors = null;      
            synchronized (this) {
                if (i.equals(lastNumber)) {
                   factors = lastFactors.clone();
                }         
            }    
            if (factors == null) {        
                factors = factor(i);
                synchronized (this) {
                   lastNumber = i;
                   lastFactors = factors.clone();
                }
            }
            encodeIntoResponse(resp, factors);
        }
    }

    The above is the detailed content of How to implement thread safety in Java concurrent programming. For more information, please follow other related articles on the PHP Chinese website!

    Statement:
    This article is reproduced at:yisu.com. If there is any infringement, please contact admin@php.cn delete