Home  >  Article  >  Java  >  The principles and usage scenarios of Synchronized in Java and the usage and difference analysis of the Callable interface

The principles and usage scenarios of Synchronized in Java and the usage and difference analysis of the Callable interface

WBOY
WBOYforward
2023-04-21 08:04:071073browse
    ##1. Basic features

    1. It starts with optimistic locking. If lock conflicts are frequent, it will be converted to pessimistic locking.

    2. Start with a lightweight lock implementation. If the lock is held for a long time, it will be converted into a heavyweight lock.

    3. Spin locks are most likely to be used when implementing lightweight locks. Strategy

    4. It is an unfair lock

    5. It is a reentrant lock

    6. It is not a read-write lock

    2. Locking process

    JVM divides synchronized locks into lock-free, biased lock, lightweight lock, and heavyweight lock states. It will be upgraded sequentially according to the situation.

    The principles and usage scenarios of Synchronized in Java and the usage and difference analysis of the Callable interface

    Biased lock

    Assume that the male protagonist is a lock and the female protagonist is a thread. If only this thread uses this lock, then the male protagonist and the female protagonist Even if the protagonist does not get a marriage certificate (avoiding high-cost operations), he can still live happily. However, the female protagonist appears and tries to compete for the male protagonist. At this time, no matter how expensive the operation of obtaining a marriage certificate is, the heroine must also This action is completed, let the female protagonist give up

    The biased lock is not really "locking", it just makes a "biased lock mark" in the object header to record which thread the lock belongs to. If there is no other follow-up threads to compete for the lock, then there is no need to perform other synchronization operations (avoiding the overhead of locking and unlocking). If other threads compete for the lock later (it has just been recorded in the lock object which thread the current lock belongs to, it is easy Identify whether the thread currently applying for the lock is the previously recorded thread), then cancel the original biased lock state and enter the general lightweight lock state

    The biased lock is essentially equivalent to "delayed locking". Can If you don’t lock, don’t lock. Try to avoid unnecessary locking overhead. However, you still have to do the markings, otherwise it will be impossible to distinguish when real locking is needed.

    Biased locks are not real locks , but only records a mark in the object header of the lock (recording the thread to which the lock belongs). If no other threads participate in competing for the lock, then the locking operation will not actually be performed, thus reducing program overhead. Once other threads are really involved Thread competition, then cancel the biased lock state, enter the lightweight lock state

    Lightweight lock

    As other threads enter the competition, the biased lock state is eliminated, enter the lightweight lock state Status (adaptive spin lock). The lightweight lock here is implemented through CAS.

    Check and update a piece of memory through CAS (such as null => referenced by this thread)

    If the update is successful, the lock is considered to be successful.

    If the update fails, the lock is considered to be occupied, and the spin wait is continued (without giving up the CPU).

    The spin operation is Keeping the CPU idling is a waste of CPU resources. Therefore, the spin here will not continue forever, but will stop spinning after reaching a certain time/number of retries. This is the so-called "adaptive"

    Heavyweight lock

    If the competition becomes more intense and the spin cannot obtain the lock status quickly, it will expand into a heavyweight lock. The heavyweight lock here refers to the use of the mutex provided by the kernel.

    To perform the locking operation, first enter the kernel state.

    Determine whether the current lock has been occupied in the kernel state

    If the lock is not occupied, the locking is successful and the switch Return to user mode.

    If the lock is occupied, the lock fails. At this time, the thread enters the waiting queue of the lock and hangs. Waiting to be awakened by the operating system.

    Goed through a series of Over time, the lock was released by other threads, and the operating system also remembered the suspended thread, so it woke up the thread and tried to reacquire the lock

    3. Other optimization operations

    Lock elimination

    The compiler JVM determines whether the lock can be eliminated. If it can, it will be eliminated directly

    Some applications use synchronized in their code, but they are not actually used in a multi-threaded environment (such as StringBuffer) )

    StringBuffer sb = new StringBuffer();
    sb.append("a");
    sb.append("b");
    sb.append("c");
    sb.append("d");

    At this time, each append call will involve locking and unlocking. But if this code is only executed in a single thread, then these locking and unlocking operations are not necessary, and some resource overhead is wasted. .

    Lock coarsening

    If multiple locks and unlocks occur in a piece of logic, the compiler JVM will automatically coarsen the lock.

    The principles and usage scenarios of Synchronized in Java and the usage and difference analysis of the Callable interface

    Leader, explain work tasks to subordinates

    Method 1:

    Make a phone call, explain task 1, hang up the phone.

    Make a phone call, explain task 2, hang up the phone .

    Make a phone call, give task 3, hang up the phone

    Method 2:

    Make a phone call, give task 1, task 2, task 3, hang up the phone

    4. Callable interface

    What is Callable

    Callable is an interface. It is equivalent to encapsulating a "return value" in a thread. It is convenient for programmers to calculate results using multi-threading.

    Callable 和 Runnable 相对, 都是描述一个 "任务". Callable 描述的是带有返回值的任务, Runnable 描述的是不带返回值的任务.Callable 通常需要搭配 FutureTask 来使用. FutureTask 用来保存 Callable 的返回结果. 因为 Callable 往往是在另一个线程中执行的, 啥时候执行完并不确定. FutureTask 就可以负责这个等待结果出来的工作.

    代码示例: 创建线程计算 1 + 2 + 3 + ... + 1000, 不使用 Callable 版本

    public class Text {
     
        static class Result{
            public int sum = 0;
            public Object locker = new Object();
        }
     
        public static void main(String[] args) throws InterruptedException {
            Result result = new Result();
     
            Thread t = new Thread(){
                @Override
                public void run() {
                    int sum = 0;
                    for (int i = 0; i <=10000; i++){
                        sum += i;
                    }
                    result.sum = sum;
     
                    synchronized (result.locker){
                        result.locker.notify();
                    }
                }
            };
            t.start();
            synchronized (result.locker){
                while (result.sum == 0){
                    result.locker.wait();
                }
            }
            System.out.println(result.sum);
        }
    }

    代码示例: 创建线程计算 1 + 2 + 3 + ... + 1000, 使用 Callable 版本

    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
     
    public class Text1 {
     
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            Callable<Integer> callable = new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    int sum = 0;
                    for (int i = 0; i <=1000; i++){
                        sum += i;
                    }
                    return sum;
                }
            };
            //由于Thread不能直接传一个callable实例,就需要一个辅助类来包装
            FutureTask<Integer> futureTask = new FutureTask<>(callable);
            Thread t = new Thread(futureTask);
            t.start();
            //尝试在主线程获取结果
            //如果FutureTask中的结果还没生成。此时就会阻塞等待
            //一直等到最终的线程把这个结果算出来,get返回
            Integer result = futureTask.get();
            System.out.println(result);
        }
    }

    The above is the detailed content of The principles and usage scenarios of Synchronized in Java and the usage and difference analysis of the Callable interface. 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