cool hit counter LockSupport's source code implementation principles and applications_Intefrankly

LockSupport's source code implementation principles and applications


I. Why use LockSupport class

If only LockSupport was simpler to use than Object's wait/notify in

Then there's really no need to explain LockSupport specifically. The main thing is flexibility.

In the example code above, the main thread calls the Thread.sleep(1000) method to wait for Thread A to complete its computation and enter the wait state. If the Thread.sleep() call is removed, the code is as follows.

Note: This scenario needs some attention to prevent this bug from occurring in the business scenario.

public class TestObjWait {

    public static void main(String[] args)throws Exception {
        final Object obj = new Object();
        Thread A = new Thread(new Runnable() {
            @Override
            public void run() {
                int sum = 0;
                for(int i=0;i<10;i++){
                    sum+=i;
                }
                try {
                    synchronized (obj){
                        obj.wait();
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }
                System.out.println(sum);
            }
        });
        A.start();
        // One second of sleep., Guaranteed threadsA Already calculated, blockagewait approach
        //Thread.sleep(1000);
        synchronized (obj){
            obj.notify();
        }
    }
}

Run the above code a few more times, Sometimes it prints the results properly and exits the program, But there are times when threads fail to print results blocking。 The reason for this is that: After the main thread callsnotify back, threadsA pre-entrywait approach,

causing thread A to keep blocking. Since thread A is not a background thread, the entire program cannot exit.

And what if we switch to LockSupport? Does LockSupport just support the main thread calling unpark first and then thread A calling park without getting blocked? Yes, that's right. The code is as follows.

public class TestObjWait {

    public static void main(String[] args)throws Exception {
        final Object obj = new Object();
        Thread A = new Thread(new Runnable() {
            @Override
            public void run() {
                int sum = 0;
                for(int i=0;i<10;i++){
                    sum+=i;
                }
                LockSupport.park();
                System.out.println(sum);
            }
        });
        A.start();
        // One second of sleep., Guaranteed threadsA Already calculated, blockagewait approach
        //Thread.sleep(1000);
        LockSupport.unpark(A);
    }
}

No matter how many times you execute it, this code prints the result properly and exits. This is where LockSupport's greatest flexibility lies.

To summarize, LockSupport has two major advantages over Object's wait/notify

①LockSupport does not need to be in the synchronization code block . So there is no need to maintain a shared synchronization object between threads either, achieving decoupling between threads.

②The unpark function can be called before park, so there is no need to worry about the order of execution between threads.

III. Wide range of applications

LockSupport in Java's tool class with a wide range of applications, let's find a few examples here to feel the feeling.

Take the most commonly used class in Java, ThreadPoolExecutor, as an example. Let's start with the following code.

public class TestObjWait {

    public static void main(String[] args)throws Exception {
        ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(1000);
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5,5,1000, TimeUnit.SECONDS,queue);

        Future<String> future = poolExecutor.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                TimeUnit.SECONDS.sleep(5);
                return "hello";
            }
        });
        String result = future.get();
        System.out.println(result);
    }
}

In the code we throw a task into the thread pool and then call Future's get method to synchronously block and wait for the result of the thread pool's execution.

The question to ask here is. How does the get method group stuff the current thread? And how does a thread pool wake up a thread after it has executed a task?

Let's follow the source code step by step, Let's look at the thread pool firstsubmit approach realization of the:

(located) atsubmit approach Li (surname), The thread pool takes our commits based on theCallable Tasks achieved, Encapsulated as aRunnableFuture Tasks achieved, Then the task is submitted to the thread pool for execution, and returns to the current threadRunnableFutrue。

go intonewTaskFor approach, Just one word.:return new FutureTask<T>(callable);

consequently, Our main thread callsfuture ofget approach evenFutureTask ofget approach, The task objects executed by the thread pool are alsoFutureTask examples of。

Read next.FutureTask ofget approach realization of the:

It is relatively simple, is to determine whether the current task is executed, if the execution is completed directly return the task results, otherwise enter the waitDone method blocking wait.

awaitDone approach Li (surname), The first will use thecas operations, Wrapping threads asWaitNode, Keep it down., for subsequent wakeups of the thread。 Then there's the call toLockSupport ofpark/parkNanos Group Stuffed Current Threads。

The logic for blocking and waiting for the result of a task has been covered above, so let's look at the implementation of the logic for waking up a waiting thread after the thread pool has finished executing the task.

As I said before., Our submission is based onCallable Tasks achieved, has been encapsulated asFutureTask The task was submitted to the thread pool for execution, The execution of the mission isFutureTask ofrun approach execute。 The following areFutureTask ofrun approach:

c.call() It's the execution of the task we submitted, After the task is executed, theset approach, go intoset approach find thatset approach CalledfinishCompletion approach, Guess that's where the wake-up threads come in, Take a look at the code implementation:

That's right right here, first all the waiting threads are taken out by the cas operation, then it uses LockSupport's unpark to wake up each thread.

In the process of using thread pools, I wonder if you have this question: what are the threads in the thread pool doing when there are no tasks in the thread pool?

The answer is. The thread will call the queue'stake approach Blocking waiting for new assignments。 That queue'stake approach Is it the same asFuture ofget approach Achieving the same thing??

Using ArrayBlockingQueue as an example, the take method is implemented as follows.

It's not what I expected., He was using theLock ofCondition ofawait approach Implementing thread blocking。 But as we continue to chase it down intoawait approach, It was found that it was still usedLockSupport:

Due to space limitations, more applications in jdk will not be pursued further.

IV. Implementation of LockSupport

It is important to learn to know what you know, but also to know what you know. Next it is worth looking at the implementation of LockSupport.

Enter the park method of LockSupport and find that it calls the park method of Unsafe, which is a native native method and can only be implemented natively by looking at the source code of openjdk, which shows that the underlying source code is implemented in C++.

It calls the thread'sParker type object of thepark approach, The following areParker Definition of classes: main thing to look at Private members constructor (computing) destructor as well as its parker harmony unparker approach。

The class defines a _counter variable of type int, and the section above on flexibility says that you can execute unpark first and then park, which is achieved by this variable, see the implementation code of the park method (since the method is rather long I won't take a screenshot of the whole thing).

park approach will callAtomic::xchg approach, this one approach It will atomically bring_counter assign a value of0, and returns the value before the assignment。 If the call topark approach formerly,_counter more than, >0, then it means that a previous call tounpark approach, consequentlypark approach Direct return。 will_counterf The value is set to0;

Moving on to the following.

Actually the Parker class uses Posix's mutex, condition to implement the blocking wakeup. If you are not familiar with mutex and condition, you can simply understand that mutex is synchronized in Java, and condition is the wait/notify operation in Object.

park approach call from insidepthread_mutex_trylock approach, is equivalent toJava Thread entryJava The synchronization code block of the, Then judge again_counter Is it greater than zero, If greater than zero then the_counter Set to zero。 Final callpthread_mutex_unlock release,

equivalent toJava Exit the synchronization block after execution。 in case_counter Not greater than zero, then continue to the next steppthread_cond_wait approach, Implementing blocking for the current thread。

And one last look.unpark approach The realization of the bar, This piece is much simpler, Straight to the code:

1 and 4 in the figure are the Java equivalent of the locking and unlocking operations for entering and exiting synchronized, and code 2 sets _counter to 1

Also judge the previous_counter Is the value of less than1, That is, this code:if(s<1) , If greater than or equal to1, then there will be no threads beingpark, consequently approach Direct execution is complete, If less than1 Indicates that a thread has been park finish Then the code will be executed3, to wake up a thread that is blocked。

By readingLockSupport The local implementation of, It's easy to see the problem.: Multiple callsunpark approach and calling onceunpark approach Same effect, Because it's all straightforward to put_counter assign a value of1, Instead of adding1。 Simply put.: threadsA Two consecutive calls toLockSupport.unpark(B) approach Wake up threadsB, Then the threadB Called twice.LockSupport.park() approach, threadsB Still blocked。 Because twiceunpark The call works just like a single call, Only let threadsB The first call topark approach Not blocked, The second call will still block。


Recommended>>
1、silverlight image partial zoom effect
2、Least common denominator and greatest common multiple
3、hdu4033RegularPolygondichotomous cosine theorem
4、Detailed steps for dynamic scaling of Zookeeper
5、HandsOnHow to Advance to Deep Learning Engineer next part

    已推荐到看一看 和朋友分享想法
    最多200字,当前共 发送

    已发送

    朋友将在看一看看到

    确定
    分享你的想法...
    取消

    分享想法到看一看

    确定
    最多200字,当前共

    发送中

    网络异常,请稍后重试

    微信扫一扫
    关注该公众号