Introduction to JUC programming

Posted by RaheimSG on Tue, 01 Feb 2022 09:33:03 +0100

preface:

In Java, the thread part is a key point. The JUC mentioned in this article is also about threads. JUC is Java util . Short for concurrent toolkit. This is a toolkit for processing threads. JDK 1.5 began to appear. Let's see how it works.

1, volatile keyword and memory visibility

1. Memory visibility:
Let's take a look at the following code:

public class TestVolatile {
    public static void main(String[] args){ //This thread is used to read the value of flag
        ThreadDemo threadDemo = new ThreadDemo();
        Thread thread = new Thread(threadDemo);
        thread.start();
        while (true){
            if (threadDemo.isFlag()){
                System.out.println("Read by the main thread flag = " + threadDemo.isFlag());
                break;
            }
        }
    }
}

@Data
class ThreadDemo implements Runnable{ //This thread is used to modify the value of flag
    public  boolean flag = false;
    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        flag = true;
        System.out.println("ThreadDemo Thread modified flag = " + isFlag());
    }
}

This code is very simple. A ThreadDemo class inherits Runnable to create a thread. It has a member variable flag as false, and then rewrites the run method. In the run method, change the flag to true, and there is an output statement. Then is the main method. The main thread reads the flag. If the flag is true, it will break the while loop, otherwise it is an dead loop. Logically, the following thread changes the flag to true. What the main thread reads should also be true, and the loop should end. Look at the running results:

Operation results

As can be seen from the figure, the program is not over, that is, an endless loop. It indicates that the flag read by the main thread is still false, but another thread clearly changed the flag to true and printed it. What's the reason? This is the memory visibility problem.

  • Memory visibility problem: when multiple threads operate to share data, they are invisible to each other.

See the figure below to understand the above code:

graphic

To solve this problem, you can lock it. As follows:

while (true){
        synchronized (threadDemo){
            if (threadDemo.isFlag()){
                System.out.println("Read by the main thread flag = " + threadDemo.isFlag());
                break;
            }
        }
 }

With a lock, the while loop can read data from main memory every time, so that it can read true. However, once a lock is added, only one thread can access it at a time. When one thread holds the lock, the others will block, and the efficiency is very low. If you don't want to lock and solve the memory visibility problem, you can use the volatile keyword.

2. volatile keyword:

  • Usage:

Volatile keyword: when multiple threads operate to share data, it can ensure that the data in memory is visible. Modifying the shared data with this keyword will refresh the data in the thread cache to the main memory in time. It can also be understood as directly operating the data in the main memory. Therefore, volatile can be used without locks. As follows:

public  volatile boolean flag = false;

This solves the memory visibility problem.

  • The difference between volatile and synchronized:
    volatile is not mutually exclusive (when one thread holds a lock, other threads cannot enter, which is mutual exclusion).
    volatile is not atomic.

2, Atomicity

1. Understanding atomicity:
As mentioned above, volatile does not have atomicity, so what is atomicity? First look at the following code:

public class TestIcon {
    public static void main(String[] args){
        AtomicDemo atomicDemo = new AtomicDemo();
        for (int x = 0;x < 10; x++){
            new Thread(atomicDemo).start();
        }
    }
}

class AtomicDemo implements Runnable{
    private int i = 0;
    public int getI(){
        return i++;
    }
    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(getI());
    }
}

This code is to let i + + in the run method, and then start ten threads to access it. Look at the results:

Operation results

It can be found that there is duplicate data. Obviously, there is a multithreading safety problem, or atomicity problem. The so-called atomicity means that the operation cannot be subdivided, and the i + + operation is divided into three steps: read and rewrite, as follows:

int temp = i;
i = i+1;
i = temp;

So i + + is obviously not an atomic operation. When the above 10 threads perform i + +, the memory diagram is as follows:

graphic

It looks like the memory visibility problem above. Is it OK to add a volatile keyword? In fact, it's not. Because volatile is added, it's just equivalent to that all threads operate data in main memory, but it's not mutually exclusive. For example, if two threads read the 0 in the main memory at the same time, and then increase at the same time and write to the main memory at the same time, duplicate data will still appear.

2. Atomic variables:
After JDK 1.5, Java provides atomic variables in Java util. concurrent. Atomic package. Atomic variables have the following characteristics:

  • volatile ensures memory visibility.
  • Ensure atomicity with CAS algorithm.

3. CAS algorithm:
CAS algorithm is the support of computer hardware for concurrent operation of shared data. CAS contains three operands:

  • Memory value V
  • Estimated value A
  • Update value B

When and only when V==A, the value of B will be assigned to V, that is, V=B, otherwise no operation will be done. For the above i + + problem, CAS algorithm deals with this: first, V is the value 0 in main memory, and then the estimated value A is also 0. Because there is no operation at this time, V=B, so it increases automatically and changes the value in main memory to 1 at the same time. It doesn't matter if the second thread reads 0 from the main memory, because the estimated value has changed to 1, and V is not equal to A, so no operation is performed.

4. Using atomic variables to improve i + + problems:
The usage of atomic variables is similar to that of wrapper classes, as follows:

 //private int i = 0;
 AtomicInteger i = new AtomicInteger();
 public int getI(){
     return i.getAndIncrement();
 }

Just change these two places.

3, Lock segmentation mechanism

After JDK 1.5, in Java util. The concurrent package provides a variety of concurrent container classes to improve the performance of synchronization container classes. The most important one is ConcurrentHashMap.

1,ConcurrentHashMap:
ConcurrentHashMap is a thread safe Hash Table. We know that HashMap is thread unsafe, and Hash Table is thread safe with lock, so its efficiency is low. HashTable locking is to lock the entire Hash Table. When multiple threads access it, only one thread can access it at the same time, and parallel becomes serial, so the efficiency is low. So jdk1 After 5, the concurrent HashMap is provided, which adopts the lock segmentation mechanism.

ConcurrentHashMap lock segmentation

As shown in the above figure, the ConcurrentHashMap is divided into 16 segments by default. Each segment corresponds to a Hash table and has independent locks. Therefore, in this way, each thread can access a segment, which can be accessed in parallel, thus improving the efficiency. This is the lock segment. java 8 has been updated again. It no longer adopts the lock segmentation mechanism, but also adopts the CAS algorithm.

2. Usage:
java. util. The concurrent package also provides collection implementations designed for multithreading contexts: ConcurrentHashMap, ConcurrentSkipListMap, ConcurrentSkipListSet, CopyOnWriteArrayList, and CopyOnWriteArraySet. When many threads are expected to access a given collection, ConcurrentHashMap is usually better than synchronized HashMap, and ConcurrentSkipListMap is usually better than synchronized TreeMap. CopyOnWriteArrayList is better than synchronized ArrayList when the expected reading and traversal are much larger than the number of updates to the list. Let's look at some usage:

public class TestConcurrent {
    public static void main(String[] args){
        ThreadDemo2 threadDemo2 = new ThreadDemo2();
           for (int i=0;i<10;i++){
               new Thread(threadDemo2).start();
           }
    }
}
//10 threads access at the same time
class ThreadDemo2 implements Runnable{
    private static List<String> list = Collections.synchronizedList(new ArrayList<>());//Common practice
    static {
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
    }
    @Override
    public void run() {
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());//read
            list.add("ddd");//write
        }
    }
}

10 threads access the collection concurrently, read the collection data and add data to the collection at the same time. Running this code will report errors and modify exceptions concurrently.

Concurrent modification exception

Change the set creation method to:

private static CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();

In this way, there will be no concurrent modification exceptions. Because this is written and copied, and a new one is generated each time, if there are many adding operations, the overhead is very large, which is suitable for use when there are many iterative operations.

4, Locking

java. util. The concurrent package provides a variety of concurrent container classes to improve the performance of the synchronization container. ContDownLatch is a synchronous auxiliary class. When some operations are completed, the current operation will continue only after the operations of all other threads are completed. This is called locking. Look at the following code:

public class TestCountDownLatch {
    public static void main(String[] args){
        LatchDemo ld = new LatchDemo();
        long start = System.currentTimeMillis();
        for (int i = 0;i<10;i++){
            new Thread(ld).start();
        }
        long end = System.currentTimeMillis();
        System.out.println("Time consuming:"+(end - start)+"second");
    }
}

class LatchDemo implements Runnable{
    private CountDownLatch latch;
    public LatchDemo(){
    }
    @Override
    public void run() {
        for (int i = 0;i<5000;i++){
            if (i % 2 == 0){//Even number within 50000
                System.out.println(i);
            }
        }
    }
}

This code is 10 threads to output an even number within 5000 at the same time, and then calculate the execution time at the main thread. In fact, this can not calculate the execution time of the 10 threads, because the main thread and the 10 threads are also executed at the same time. Maybe the 10 threads are only half executed, and the main thread has output the sentence "time consuming is x seconds". If you want to calculate the execution time of these 10 threads, you have to let the main thread wait first and wait until all 10 branching processes are executed. This requires locking. See how to use:

public class TestCountDownLatch {
    public static void main(String[] args) {
        final CountDownLatch latch = new CountDownLatch(10);//How many threads is this parameter
        LatchDemo ld = new LatchDemo(latch);
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10; i++) {
            new Thread(ld).start();
        }
        try {
            latch.await();//Wait before these 10 threads finish executing
        } catch (InterruptedException e) {
        }
        long end = System.currentTimeMillis();
        System.out.println("Time consuming:" + (end - start));
    }
}

class LatchDemo implements Runnable {
    private CountDownLatch latch;
    public LatchDemo(CountDownLatch latch) {
        this.latch = latch;
    }
    @Override
    public void run() {
        synchronized (this) {
            try {
                for (int i = 0; i < 50000; i++) {
                    if (i % 2 == 0) {//Even number within 50000
                        System.out.println(i);
                    }
                }
            } finally {
                latch.countDown();//Decrease one for each execution
            }
        }
    }
}

The above code mainly uses latch Countdown() and latch Await() implements locking. Please refer to the notes above for details.

5, How to create a thread - implement the Callable interface

Look directly at the code:

public class TestCallable {
    public static void main(String[] args){
        CallableDemo callableDemo = new CallableDemo();
        //The implementation of the callable method requires the support of the FutureTask implementation class to receive the operation results
        FutureTask<Integer> result = new FutureTask<>(callableDemo);
        new Thread(result).start();
        //Receive thread operation results
        try {
            Integer sum = result.get();//The result will not be printed until the above thread is executed. It's like locking. All futureTask can also be used for locking
            System.out.println(sum);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class CallableDemo implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
       int sum = 0;
       for (int i = 0;i<=100;i++){
           sum += i;
       }
       return sum;
    }
}

The difference between the Callable interface and the Runable interface is that Callable is generic and its call method has a return value. When using, you need to use FutureTask to receive the return value. Moreover, it will not be executed until the thread has finished executing and calling the get method. It can also be used for locking operation.

6, Lock synchronization lock

In jdk1 Before 5, there were two ways to solve the problem of multithreading safety (synchronized implicit lock):

  • Synchronous code block
  • Synchronization method

In jdk1 After 5, there is a more flexible way (Lock explicit Lock):

  • Synchronous lock

Lock needs to be locked through the lock() method and released through the unlock() method. In order to ensure that the lock can be released, all unlock methods are generally executed in finally.

Let's take another look at ticket selling cases:

public class TestLock {
    public static void main(String[] args) {
        Ticket td = new Ticket();
        new Thread(td, "Window 1").start();
        new Thread(td, "Window 2").start();
        new Thread(td, "Window 3").start();
    }
}

class Ticket implements Runnable {
    private int ticket = 100;
    @Override
    public void run() {
        while (true) {
            if (ticket > 0) {
                try {
                    Thread.sleep(200);
                } catch (Exception e) {
                }
                System.out.println(Thread.currentThread().getName() + "The remaining tickets are:" + (--ticket));
            }
        }
    }
}

Multiple threads operate shared data tickets at the same time, so thread safety problems will occur. The same ticket will be sold several times or the number of votes is negative. Previously, it was solved with synchronization code block and synchronization method. Now let's see how to solve it with synchronization lock.

class Ticket implements Runnable {
    private Lock lock = new ReentrantLock();//Create lock lock
    private int ticket = 100;
    @Override
    public void run() {
        while (true) {
            lock.lock();//Lock
            try {
                if (ticket > 0) {
                    try {
                        Thread.sleep(200);
                    } catch (Exception e) {
                    }
                    System.out.println(Thread.currentThread().getName() + "The remaining tickets are:" + (--ticket));
                }
            }finally {
                lock.unlock();//Release lock
            }

        }
    }
}

Directly create a lock object, then lock it with the lock() method, and finally release the lock with the unlock() method.

7, Wait for wake-up mechanism

1. False wake-up problem:
Production and consumption mode is a classic case of waiting for wake-up mechanism. See the following code:

public class TestProductorAndconsumer {
    public static void main(String[] args){
           Clerk clerk = new Clerk();
           Productor productor = new Productor(clerk);
           Consumer consumer = new Consumer(clerk);
           new Thread(productor,"producer A").start();
           new Thread(consumer,"consumer B").start();
    }
}
//clerk
class Clerk{
    private int product = 0;//shared data 
    public synchronized void get(){ //Purchase
        if(product >= 10){
            System.out.println("Product full");
        }else {
            System.out.println(Thread.currentThread().getName()+":"+ (++product));
        }
    }
    public synchronized void sell(){//Sell goods
        if (product <= 0){
            System.out.println("Out of stock");
        }else {
            System.out.println(Thread.currentThread().getName()+":"+ (--product));
        }
    }
}
//producer
class Productor implements Runnable{
    private Clerk clerk;
    public Productor(Clerk clerk){
        this.clerk = clerk;
    }
    @Override
    public void run() {
        for (int i = 0;i<20;i++){
            clerk.get();
        }
    }
}
//consumer
class Consumer implements Runnable{
    private Clerk clerk;
    public Consumer(Clerk clerk){
        this.clerk = clerk;
    }
    @Override
    public void run() {
        for (int i = 0;i<20;i++){
            clerk.sell();
        }
    }
}

This is the case of production and consumption mode. There is no waiting wake-up mechanism. The operation result is that even if it is out of stock, it will continue to consume and print "out of stock" all the time. Even if the product is full, it will continue to purchase. Improved by waiting for wake-up mechanism:

//clerk
class Clerk{
    private int product = 0;//shared data 
    public synchronized void get(){ //Purchase
        if(product >= 10){
            System.out.println("Product full");
            try {
                this.wait();//Wait when you're full
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else {
            System.out.println(Thread.currentThread().getName()+":"+ (++product));
            this.notifyAll();//You can purchase goods when they are not full
        }
    }
    public synchronized void sell(){//Sell goods
        if (product <= 0){
            System.out.println("Out of stock");
            try {
                this.wait();//Wait when out of stock
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else {
            System.out.println(Thread.currentThread().getName()+":"+ (--product));
            this.notifyAll();//It can be sold without shortage
        }
    }
}

In this way, the above problems will not occur. Produce when there is no, notify consumption when production is full, and notify production when consumption is over. However, there are still some problems. Make the following changes to the above code:

if(product >= 1){ //Change the original 10 to 1
            System.out.println("Product full");
         ......
public void run() {
        try {
            Thread.sleep(200);//Sleep for 0.2 seconds
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0;i<20;i++){
            clerk.sell();
        }
}

Make these two modifications and run again. It is found that although the result is OK, the program has not stopped. This happens because one thread is waiting and the other thread has no chance to execute. The waiting thread cannot be awakened, so the program cannot end. The solution is to remove the else in the get and sell methods and don't wrap it up with else. But even so, if you add two more threads, there will be a negative number.

new Thread(productor, "producer C").start();
new Thread(consumer, "consumer D").start();

Operation results:

Operation results

When a consumer thread grabs the execution right and finds that product is 0, it waits. At this time, another consumer grabs the execution right again. Product is 0 or wait. At this time, the two consumer threads wait at the same place. Then, when the producer produces a product, it will wake up two consumers and find that the product is 1 and consumes at the same time, resulting in 0 and - 1. This is false awakening. The solution is to change the if judgment to while. As follows:

 public synchronized void get() { //Purchase
        while (product >= 1) {
            System.out.println("Product full");
            try {
                this.wait();//Wait when you're full
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
            System.out.println(Thread.currentThread().getName() + ":" + (++product));
            this.notifyAll();//You can purchase goods when they are not full
    }
    public synchronized void sell() {//Sell goods
        while (product <= 0) {//In order to avoid false wake-up problems, we should always use the wait method in the loop
            System.out.println("Out of stock");
            try {
                this.wait();//Wait when out of stock
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
            System.out.println(Thread.currentThread().getName() + ":" + (--product));
            this.notifyAll();//It can be sold without shortage
    }

Just change if to while and judge it every time.

2. Wait for wake-up with Lock:

class Clerk {
    private int product = 0;//shared data 
    private Lock lock = new ReentrantLock();//Create lock object
    private Condition condition = lock.newCondition();//Get condition instance
    public  void get() { //Purchase
        lock.lock();//Lock
        try {
            while (product >= 1) {
                System.out.println("Product full");
                try {
                    condition.await();//Wait when you're full
                } catch (InterruptedException e) {
                }
            }
            System.out.println(Thread.currentThread().getName() + ":" + (++product));
            condition.signalAll();//You can purchase goods when they are not full
        }finally {
            lock.unlock();//Release lock
        }
    }

    public  void sell() {//Sell goods
        lock.lock();//Lock
        try {
            while (product <= 0) {
                System.out.println("Out of stock");
                try {
                    condition.await();//Wait when out of stock
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + ":" + (--product));
            condition.signalAll();//It can be sold without shortage
        }finally {
            lock.unlock();//Release lock
        }
    }
}

Using lock synchronization lock, you don't need the synchronized keyword. You need to create lock object and condition instance. Condition's await() method, signal() method and signalAll() method correspond to wait() method, notify() method and notifyAll() method respectively.

3. Threads alternate sequentially:
Let's start with a question:

Write a program, open three threads, the of these three threads ID Respectively A,B,C,
Each thread will its own ID Print 10 times on the screen, and the output results must be displayed in order.
For example: ABCABCABC...... Sequential recursion

analysis:

Threads are preemptive and alternate in order, so thread communication must be realized,
Then you need to wait for wake-up. You can use the synchronization method or the synchronization lock.

Coding implementation:

public class TestLoopPrint {
    public static void main(String[] args) {
        AlternationDemo ad = new AlternationDemo();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    ad.loopA();
                }
            }
        }, "A").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    ad.loopB();
                }
            }
        }, "B").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    ad.loopC();
                }
            }
        }, "C").start();
    }
}

class AlternationDemo {
    private int number = 1;//Tag of the thread currently executing
    private Lock lock = new ReentrantLock();
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    Condition condition3 = lock.newCondition();

    public void loopA() {
        lock.lock();
        try {
            if (number != 1) { //judge
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName());//Print
            number = 2;
            condition2.signal();
        } catch (Exception e) {
        } finally {
            lock.unlock();
        }
    }

    public void loopB() {
        lock.lock();
        try {
            if (number != 2) { //judge
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName());//Print
            number = 3;
            condition3.signal();
        } catch (Exception e) {
        } finally {
            lock.unlock();
        }
    }

    public void loopC() {
        lock.lock();
        try {
            if (number != 3) { //judge
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName());//Print
            number = 1;
            condition1.signal();
        } catch (Exception e) {
        } finally {
            lock.unlock();
        }
    }
}

The above codes meet the requirements. Create three threads and call loopA, loopB and loopC methods respectively. These three threads use condition to communicate.

8, ReadWriterLock read / write lock

When reading data, we can read data by multiple threads at the same time without any problem, but when writing data, if multiple threads write data at the same time, which thread is writing data? Therefore, if there are two threads, write / read / write needs to be mutually exclusive, and read does not need to be mutually exclusive. You can use the read-write lock at this time. Take an example:

public class TestReadWriterLock {
    public static void main(String[] args){
           ReadWriterLockDemo rw = new ReadWriterLockDemo();
           new Thread(new Runnable() {//One thread write
               @Override
               public void run() {
                   rw.set((int)Math.random()*101);
               }
           },"write:").start();
           for (int i = 0;i<100;i++){//100 thread reads
               Runnable runnable = () -> rw.get();
               Thread thread = new Thread(runnable);
               thread.start();
           }
    }
}

class ReadWriterLockDemo{
    private int number = 0;
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    //Read (multiple threads can operate simultaneously)
    public void get(){
        readWriteLock.readLock().lock();//Lock
        try {
            System.out.println(Thread.currentThread().getName()+":"+number);
        }finally {
            readWriteLock.readLock().unlock();//Release lock
        }
    }
    //Write (only one thread can operate at a time)
    public void set(int number){
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName());
            this.number = number;
        }finally {
            readWriteLock.writeLock().unlock();
        }
    }
}

This is the usage of read-write lock. The above code implements the operation of one thread writing and one hundred threads reading at the same time.

9, Thread pool

When using threads, we need a new one and destroy it when it is used up. This frequent creation and destruction also consumes resources, so we provide a thread pool. The reason is similar to the connection pool. The connection pool is to avoid frequent creation and release of connections, so there are a certain number of connections in the connection pool. When it needs to be used, take them out of the connection pool and return them to the connection pool. The same is true for thread pools. There is a thread queue in the thread pool, which holds all waiting threads. Let's look at the usage:

public class TestThreadPool {
    public static void main(String[] args) {
        ThreadPoolDemo tp = new ThreadPoolDemo();
        //1. Create thread pool
        ExecutorService pool = Executors.newFixedThreadPool(5);
        //2. Assign tasks to threads in the thread pool
        pool.submit(tp);
        //3. Close thread pool
        pool.shutdown();
    }
}

class ThreadPoolDemo implements Runnable {
    private int i = 0;
    @Override
    public void run() {
        while (i < 100) {
            System.out.println(Thread.currentThread().getName() + ":" + (i++));
        }
    }
}

The usage of thread pool is very simple, which is divided into three steps. First, create a thread pool with the tool class Executors, then assign tasks to the thread pool, and finally close the thread pool.

10, Fork/Join

Fork/Join framework: when necessary, fork a large task into several small tasks (up to the given critical value), and then join and summarize the operation results of each small task

Adopt "work stealing" mode:

When executing a new task, it can split it into smaller tasks, add the small task to the thread queue, and then steal one from the queue of a random thread and put it in its own queue.

Compared with the common thread pool implementation, the advantage of fork/join framework lies in the processing of the tasks contained in it In a general thread pool, if the task being executed by a thread cannot continue to run for some reason, the thread will be in a waiting state. In the implementation of fork/join framework, if a sub problem cannot continue to run because it waits for the completion of another sub problem. Then the thread dealing with the subproblem will actively look for other subproblems that have not yet run to execute This method reduces the waiting time of threads and improves the performance.

[the external chain picture transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-08qi6lwz-1622875371504) (Art of thread concurrency. assets/1218623-20170826104001074-1317309267.png)]

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;

public class TestForkJoinPool {

    public static void main(String[] args) {
        ForkJoinPool pool = new ForkJoinPool();
        //Calculate the sum of 0l and 10000000l. 100000000 minus 0 is greater than the critical value of 10000 given by us, so it will be split,
        //Split into 0.5 million; 5000000001 100000000 two parts, 50000000 minus 0 is greater than our critical value, and will continue to split until it is not greater than the critical value given by us,
        //In this way, it is divided into many small tasks. Each thread is assigned some small tasks to form its own task queue. When some threads finish executing their own tasks, they will not directly end the thread,
        //Instead, steal a task from the task queue of other threads and put it into its own task queue
        //If a thread is blocked, it will not cause the task assigned to this thread to be blocked and unable to execute due to the blocking of this thread. Other threads will come to help him after execution
        ForkJoinTask<Long> task = new ForkJoinSumCalculate(0L, 100000000L);
        long sum = pool.invoke(task);
        System.out.println(sum);
    }
}

class ForkJoinSumCalculate extends RecursiveTask<Long> {

    private static final long serialVersionUID = 6145975974734867182L;
    private long start;
    private long end;

    private static final long THURSHOLD = 10000L;      //critical value

    public ForkJoinSumCalculate(long start, long end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
        long length = end - start;
        if (length <= THURSHOLD) {
            long sum = 0L;
            for (long i = start; i <= end; i++) {
                sum += i;
            }
            return sum;
        } else {
            long middle = (start + end) / 2;
            //compute() will be executed again, but the parameters passed in are split
            ForkJoinSumCalculate left = new ForkJoinSumCalculate(start, middle);
            left.fork();   //Split and push into the thread queue at the same time

            //compute() will be executed again, but the parameters passed in are split
            ForkJoinSumCalculate right = new ForkJoinSumCalculate(middle + 1, end);
            right.fork();    //
            return left.join() + right.join();   //Result merging
        }
    }

}

Topics: Java Multithreading queue Concurrent Programming