The four kings + part of the thread pool knowledge under the JUC package

1) Semphore: I am right to use the current limiter

The semaphore in Java encapsulates the native semaphore of the operating system. It is essentially a counter that describes the number of available resources and mainly involves two operations.

If the counter is 0 and the operation continues, blocking and waiting will occur.

P operation: apply for an available resource, counter -1
V operation: Release an available resource, counter + 1

There is a light sign at the entrance of the parking lot, which will show how many parking spaces are left. Every time a car enters, the number of parking spaces displayed is -1, which is equivalent to a P operation. Every time a car goes out, the number of parking spaces displayed is -1. The number of parking spaces is + 1, which is equivalent to performing a V operation. When the remaining parking spaces in the parking lot are 0, the number of parking spaces displayed is 0;

1) Create a Semaphore example, initialized to 4, which means there are 4 available resources.

2) The acquire method represents applying for resources (P operation), and the release method represents releasing resources (V operation).

public class Main{
    public static void main(String[] args) {
        Semaphore semaphore=new Semaphore(10);
        Runnable runnable=new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "Start applying for resources");
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "The resource has been obtained");
                    semaphore.release();
                    System.out.println(Thread.currentThread().getName() + "Starting to release resources");
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        };
        for(int i=0;i<10;i + + ){
            Thread t=new Thread(runnable);
            t.start();
        }
    }
}
public class Main{
    public static int count=0;
    public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore=new Semaphore(1);
        Thread t1=new Thread(()-> {
            for (int i = 0; i < 10000; i + + ) {
                try {
                    semaphore.acquire();
                    count + + ;
                    semaphore.release();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

            }
        });
        Thread t2=new Thread(()->{
            for(int i=0;i<10000;i + + ){
            try {
                semaphore.acquire();
                count + + ;
                semaphore.release();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

        }});
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);

    }
}

A current limiter can be implemented based on Semphore:

What is current limiting: For example, if a certain square has a daily capacity of 6W, then if 100,000 people come one day, but only 60,000 people can enter, then you can only queue to enter the park. Because the staff will always control the number of people to 60,000 people

Let’s go back to the program from the examples in life. Suppose that a program can only provide services for 100,000 people. Suddenly one day due to a hot event, the number of visits to the system in a short period of time quickly increased to 50,000. Then the direct consequences will be The result is that the system crashes and no one can use the system. Obviously, only a small number of people can use it, which is far more in line with expectations than everyone being unable to use it. Therefore, at this time, current limiting should be used.

Semphore itself is implemented by relying on the idea of a counter. It can control the number of accesses to shared resources. When a thread needs to access the resource, it must first obtain a permission, which is to obtain the resource from the counter< /strong>

When the counter itself is greater than 0, the thread can obtain this available resource and continue executing

When the counter itself is equal to 0, the thread will be blocked until other threads release resources

Semphore itself has two important operations, acquire() and realse() operations

1) When a thread needs to access a shared resource, it will call the acquire method to acquire the resource. If the counter value is greater than 0, the acquire() method will decrement the counter value by 1 and allow the thread to continue running. If the counter value Equal to 0, then the acquire() method will cause the thread to block until other threads release resources.

2) When a thread finishes using the shared resource, the thread can call the realse method to release the resource. The realse() method will increase the value of the counter by 1, indicating that there is a resource available for use, and other blocked threads can have the opportunity to obtain available resources. and + 1;

About fair mode and unfair mode:

The so-called fair mode here refers to the order in which threads call acquire to obtain the available resources. The fair mode follows the first-in, first-out principle, so the unfair mode is preemptive, which means that there may be a new The acquisition thread just got the license after the license was released, but there are some other threads in front of the thread that has acquired the license. Of course, the performance of the unfair mode is relatively high;

Suppose that sometimes you need to wait for the execution of certain threads to complete before executing the code of the main thread. What should you do at this time? Some people may say that it is simple, just use the join() method to wait for the thread execution to complete before executing the main thread. Of course, if Thread is used to execute the task, then this writing method is also feasible. HoweverIn the real (coding) environment, we will not use Thread to perform multitasking, but will use the thread pool to perform multitasking. This can avoid the performance overhead caused by repeated startup and destruction of threads;< /strong>

2) CountDownLatch: Don’t worry, we’ll start the group when everyone is here

Hitting the line: calling latch.countDown()

At the end of the game, statistical results: latch.await(). As long as there is any player who does not hit the line, the game cannot end. Only if all players hit the line, then the final game will be can end

public class Main {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch=new CountDownLatch(10);
        for(int i=0;i<10;i + + ){
            Thread t=new Thread(()->{
                System.out.println("Thread" + Thread.currentThread().getName() + "Start");
                try {
                    Thread.sleep(new Random().nextInt(10000));
                System.out.println("Thread" + Thread.currentThread().getName() + "Start hitting the line");
                    latch.countDown();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            });
            t.start();
        }
        latch.await();
        System.out.println("Complete the game");
    }
}

1) Using CountDownLatch can realize the function of waiting for all tasks to be completed before executing the main task. It is similar to waiting for all athletes to complete the game before announcing the ranking. Of course, it is the same when we are playing Glory. , it only says that the group will start after everyone has gathered.

2) CountDownLatch implements the waiting function through a counter. When a CountDownLatch is created, a counter greater than 0 will be created. Each time the countDown() method is called, the counter value will be decremented by 1 until the counter value becomes 0. , the waiting tasks can continue to be executed.

3) The underlying implementation of countDownLatch relies on internal creation and maintenance of a voltaile counter. When the countDown() method is called, it will try to reduce the integer counter to -1. CountDownLatch is required when creating Pass in an integer. Before the integer “counts down” to 0, the main thread needs to hang and wait until other threads are executed before the main thread can continue to execute.

 public static void main(String[] args) throws InterruptedException {
      //Create CountDownLatch to implement two counters
        CountDownLatch latch=new CountDownLatch(2);
        //Create a thread pool to execute tasks
        ExecutorService service= Executors.newFixedThreadPool(2);
        service.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("I am the first task submitted by the thread pool");
                latch.countDown();
            }
        });
        service.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("I am the second task submitted by the thread pool");
                latch.countDown();
            }
        });
        latch.await();
        System.out.println("All tasks in the thread pool have been executed");
    }

3) Cycbarrier: When the crowd is gathered, the veteran driver can start the train

Circular Fence implements a barrier that can be recycled

https://img-blog.csdnimg.cn/img_convert/f10e1adb034e3ebaa2c02b11596386ee.gif

1) The function of CycliBarrier is to allow a group of threads to wait for each other. When a common point is reached, all previously waiting threads will break through the barrier and execute downwards together.

2) Now for example: Viagra is going to take the last bus home. The driver at the bus station will wait for all the passengers on the bus to be full before starting the bus. For example, in the case of King of Glory, you have to wait for 5 teammate games. You can enter the game only after everything is loaded

3) Essentially, multiple threads wait for each other. It is known that when all threads reach the barrier point, all previous threads can continue to execute downwards. CycBarrier itself is like an experienced driver driving. If the car is on If there are still free seats, the driver will have to wait. Only when the seats are full will the veteran driver leave

 public static void main(String[] args) {
        CyclicBarrier barrier=new CyclicBarrier(10, new Runnable() {
            @Override
            public void run() {
                System.out.println("Now everyone above the driver has arrived to start the departure");
                System.out.println("All tasks in the current thread pool have been executed");
            }
        });
        ExecutorService service=Executors.newFixedThreadPool(10);
        for(int i=0;i<10;i + + ){
            service.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(new Random().nextInt(5000));
                        System.out.println("The current passenger starts to get on the bus" + Thread.currentThread().getName());
                        barrier.await();//The current task in the thread pool is judged to be completed and can be executed multiple times
                        System.out.println("The current thread gets off the bus" + Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    } catch (BrokenBarrierException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
        }
    }
}

The bottom layer of CycliBarrier is implemented based on a counter. When the count is not 0, each thread will first call the await() method to block itself after reaching the barrier point. At this time, the counter will be decremented by 1. At this time, the thread will Blocked at this barrier, when the counter of the cycle barrier is reduced to 0, all threads calling await() will be awakened, break through the barrier, and execute together. CountDownLatch and CycliBarrier both rely on counters at the bottom level. , but CountDownLatch can only be used once, but CycliBarrier can be used multiple times. This is the biggest difference between the two.

Summary: CycliBarrier relies on ReentranLock to implement atomic updates of counters at the bottom level. The most commonly used method in CycliBarrier is the await() method. Using this method will decrement the counter value by 1 and determine whether the current counter is 0. If If it is not 0, it will block and wait, and when the counter becomes 0, the thread, that is, the thread blocked on the loop fence, can continue to perform the remaining tasks;

3) Thread pool status:

1) Running state: Running state. It enters this state after the thread pool is created. If the shutdown method is not called manually, the thread pool will be in this state throughout the entire program running process;

2) ShutDown state: In the closed state, the thread pool itself will no longer accept the submission of new tasks, but the existing tasks in the thread pool will be processed first

3) Stop state: no longer accept the submission of new tasks, and will interrupt the tasks being executed and abandon the existing tasks in the task queue

4) Tidying status: Organizing status, after all tasks are completed, including tasks in the task queue, and the number of active threads in the current thread pool drops to 0, After reaching this state, the terminated method of the thread pool will be called

5) Terminated state: Destruction state. This state will be entered when the terminated method of the thread pool is called.

 ThreadPoolExecutor executor=new ThreadPoolExecutor(10, 10,
                100, TimeUnit.SECONDS, new LinkedBlockingDeque<>(100), new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r);
            }
        }){
            @Override
            protected void terminated() {
                super.terminated();
                System.out.println("Thread pool terminated");
            }
        };
    

1) When the shutdown method is called, the state in the thread pool will change from the Running state to the shutdown state, and finally to the tidying state, and finally to the terminated state.

2) When the shutDownNow method is called, the state in the thread pool will change from the running state to the stop state, and finally to the tidying state, and finally to the terminated state.

3) Call the terminated method. The thread pool will directly reach the terminated state from the tidying state. You can rewrite this method when blocking the queue. By default, this method is empty

4) How to judge that all tasks in the thread pool have been completed?

1) In many scenarios, you want to wait for all tasks in the thread pool to be executed before performing the next step. For the Thread class, such an implementation is very simple. It can be solved by adding a join method. But it is more troublesome to judge the thread pool.

2) It can be seen from the above execution results that the program first prints that the task execution is completed, and then continues to print and execute the tasks of the thread pool. The result of this chaotic execution sequence is not what we want to see. The expected result is to wait until All tasks in Orange Juice have been executed, and then the information about the completion of the thread pool execution is printed;

3) The reason for a few problems is that the main thread main and the thread pool are executed concurrently, so when the thread pool has not finished executing main, the ready-made print results have already been executed. If you want to solve this problem, you need to print Before the result, first determine whether the task in the thread pool has been completed. If it has not been completed, wait until the task is completed before printing the result

 public static void main(String[] args) {
        ExecutorService service=Executors.newFixedThreadPool(10);
        for(int i=0;i<10;i + + ){
            service.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("Start executing tasks in the thread pool");
                }
            });
        }
        System.out.println("All tasks in the thread pool have been executed");
    }
1) Use the isTerminated() method to determine:

1) Use the termination status of the thread pool to determine whether all tasks in the thread pool have been completed. However, if you want to change the status of the thread, you need to call the shutDown() method, otherwise the thread pool will always be in the Running state. Then there is no way to judge whether it is in a terminated state or whether all tasks in the thread pool have been executed.

2) The shutdown method is a method to start the orderly shutdown of the thread pool. It will complete all submitted tasks before closing, and will not receive new tasks. When all tasks in the thread pool are completed, From now on, the thread pool will be in the terminated state, and the result returned by the isTerminated() method will be true;

Disadvantage: Need to close the thread pool

 ThreadPoolExecutor executor=new ThreadPoolExecutor(10, 10,
                100, TimeUnit.SECONDS, new LinkedBlockingDeque<>(100), new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r);
            }
        }){
            @Override
            protected void terminated() {
                super.terminated();
                System.out.println("Thread pool terminated");
            }
        };
        executor.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("Execute task 1");
            }
        });
        executor.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("Execute task 2");
            }
        });
        executor.shutdown();
        while(!executor.isTerminated()){

        }
        System.out.println("The task in the thread pool has been completed");

    
2) Determine whether getCompletedTaskCount and getTaskCount are equal

getTaskCount() returns the total number of scheduled tasks, but because the status of the tasks and threads are constantly changing, the returned value is an approximate value;

getCompetedTaskCount() returns the total number of tasks that have been executed. However, because the status of the tasks and threads is constantly changing, the returned value is an approximate value, but it will not decrease in consecutive calls

Although there is no need to close the thread pool, it may cause certain errors

3) Call countDownLatch and CycliBarrier

It should be noted that the countDown() method in countDownLatch and the await() method in CycliBarrier need to be called at the end of the run method of the thread pool.

4) Use FutureTask

The advantage of FutureTask is that the judgment is more accurate. The get method of calling the FutureTask of each thread is to wait for the completion of the task. You need to use submit to submit:

 public static void main(String[] args) throws ExecutionException, InterruptedException {
        ThreadPoolExecutor executor=new ThreadPoolExecutor(10,10,0,TimeUnit.SECONDS,new LinkedBlockingDeque<>(100));
       FutureTask<Integer> task1=new FutureTask<>(new Callable<Integer>() {
           @Override
           public Integer call() throws Exception {
               int a=10;
               a + + ;
               System.out.println("a + + completed");
               return a;
           }
       });
        FutureTask<Integer> task2=new FutureTask<>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int b=11;
                b + + ;
                System.out.println("b + + completed");
                return b;
            }
        });
      executor.submit(task1);
      executor.submit(task2);
      Integer result1= task1.get();
      Integer result2=task2.get();
        System.out.println("All tasks in the thread pool have been executed");
    }

5) The difference between submit and execute:

1) The parameters received are different: the submit method can only accept tasks of the runnable interface, but the submit method can accept tasks of the runnable method, and can also receive tasks of callable and futureTask types. The former has no return value, but the latter can. return value;

2) The return value of execute() is void. The return value of the thread cannot be obtained after the thread is submitted. The return value of submit() is Future. The return value of the thread execution can be obtained through the get() method of Future. The get() method It is synchronous. When executing the get() method, if the thread has not finished executing, it will wait synchronously until the thread execution is completed.

Note: Although the submit() method can submit Runnable type parameters, when executing the get() of the Future method, the thread will return null after execution, and there will be no actual return value. This is because Runable has no return value.