Java Concurrent Programming Learning Part 1 day08 - Customizing Concurrent Classes

Posted by masson on Fri, 23 Aug 2019 04:42:07 +0200

Original Link: https://juejin.im/post/5d550f126fb9a06b122f3670#heading-0

This blog is a personal learning note. If there are any errors, please correct them.

(Search for a partner who wants to learn programming by himself Circle T Community , more industry-related information and free video tutorials.Completely free!)

This Content

  1. Customize the ThreadPoolExecutor class
  2. Implement priority-based Executor classes
  3. Implement ThreadFactory interface to generate custom threads
  4. Using ThreadFactory in Executor Objects
  5. Customize tasks running in the timer thread pool
  6. Generate custom threads for the Fork/Join framework through the ForkJoinWorkerThreadFactory interface
  7. Customize tasks running in the Fork/Join framework
  8. Implement custom Lock classes

Java's concurrent API has provided a large number of interfaces and classes to help us write concurrent programs, but sometimes these classes still don't meet our needs.At this point we can customize our own concurrency classes, which we can usually achieve by inheriting existing concurrency classes and modifying and expanding some methods

1. Customize the ThreadPoolExecutor class

We can customize our own executors by inheriting the ThreadPoolExecutor class and overriding some of its parent classes

Sample implementation

MyExecutor:

package day08.code_01;

import java.util.Date;
import java.util.List;
import java.util.concurrent.*;

public class MyExecutor extends ThreadPoolExecutor {

    //map storing task start time
    private ConcurrentHashMap<String, Date> startTime;

    //Overlay construction method
    public MyExecutor(int corePoolSize, int maximumPoolSize,
                      long keepAliveTime, TimeUnit unit,
                      BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
        startTime = new ConcurrentHashMap<>();
    }

    @Override
    public void shutdown() {
        //Output thread pool related information in shutdown method
        System.out.printf("MyExecutor: Going to shutdown\n");
        //Number of tasks completed
        System.out.printf("MyExecutor: Executed tasks: %d\n",
                getCompletedTaskCount());
        //Number of tasks in progress
        System.out.printf("MyExecutor: Running tasks: %d\n",
                getActiveCount());
        //Number of tasks waiting to be executed
        System.out.printf("MyExecutor: Pending tasks: %d\n",
                getQueue().size());
        //Call shutdown method of parent class
        super.shutdown();
    }

    @Override
    public List<Runnable> shutdownNow() {
        //Output thread pool related information in shutdownNow method
        System.out.printf("MyExecutor: Going to immediately shutdown\n");
        //Number of tasks completed
        System.out.printf("MyExecutor: Executed tasks: %d\n",
                getCompletedTaskCount());
        //Number of tasks in progress
        System.out.printf("MyExecutor: Running tasks: %d\n",
                getActiveCount());
        //Number of tasks waiting to be executed
        System.out.printf("MyExecutor: Pending tasks: %d\n",
                getQueue().size());
        //Call shutdownNow method of parent class
        return super.shutdownNow();
    }

    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        //Print the thread name and task hash before the task starts execution
        System.out.printf("MyExecutor: A task is beginning: %s: %s\n",
                t.getName(), r.hashCode());
        //Load map with task hash code as key and date as value
        startTime.put(String.valueOf(r.hashCode()), new Date());
        //Call the parent's beforeExecute method
        super.beforeExecute(t, r);
    }

    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        //Type override of tasks
        Future<?> result = (Future<?>) r;
        try {
            //Print Task End Prompt
            System.out.println("*****************************");
            System.out.println("MyExecutor: A task is finishing");
            //Print results
            System.out.printf("MyExecutor: Result: %s\n", result.get());
            //Calculate the time taken to execute
            Date startDate = startTime.remove(String.valueOf(r.hashCode()));
            Date finishDate = new Date();
            long diff = finishDate.getTime() - startDate.getTime();
            //Time spent printing execution
            System.out.printf("MyExecutor: Duration: %d\n", diff);
            System.out.println("*****************************");
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        //Call parent method
        super.afterExecute(r, t);
    }
}

Task class:

package day08.code_01;

import java.util.Date;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

public class SleepTwoSecondsTask implements Callable<String> {
    @Override
    public String call() throws Exception {
        //Hibernate for 2 seconds
        TimeUnit.SECONDS.sleep(2);
        //Return Time String
        return new Date().toString();
    }
}

main method:

package day08.code_01;

import java.util.ArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;

public class Main {

    public static void main(String[] args) {
        //Create a custom ThreadPoolExecutor object
        MyExecutor myExecutor = new MyExecutor
                (2, 4, 1000,
                        TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>());
        //Create a collection to load Future objects
        ArrayList<Future<String>> results = new ArrayList<>();
        //Send Ten Tasks
        for (int i = 0; i < 10; i++) {
            //Create Task
            SleepTwoSecondsTask task = new SleepTwoSecondsTask();
            //Send Task to Executor
            Future<String> result = myExecutor.submit(task);
            //Load the resulting Future object into the collection
            results.add(result);
        }
        //Try to get results from the first five tasks
        for (int i = 0; i < 5; i++) {
            try {
                //Get the results returned after the task is executed
                String result = results.get(i).get();
                //Print task number and results
                System.out.printf("Main: Result for Task %d : %s\n",
                        i, result);
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }
        //Close Executor
        myExecutor.shutdown();
        //Try to get results for the last five tasks
        for (int i = 5; i < 10; i++) {
            try {
                //Get the results returned after the task is executed
                String result = results.get(i).get();
                //Print task number and results
                System.out.printf("Main: Result for Task %d : %s\n",
                        i, result);
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }
        //Waiting for the executor to close
        try {
            myExecutor.awaitTermination(1, TimeUnit.DAYS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //Printer End Prompt
        System.out.printf("Main: End of the program\n");
    }
}

2. Implement priority-based Executor classes

A blocking queue is used inside the executor to load tasks waiting to be executed, and an object reference implementing the BlockingQueue <E>interface can be passed in through the constructor of the ThreadPoolExecutor class.Java offers us blocking queue implementation classes with different characteristics, such as the PriorityBlockingQueue class, which we've used before.It is important to note that in this case our task class not only implements the Runnable interface, but also the Comparable interface. The reason for this is documented in day07 and is not discussed here.

Sample implementation

Task class:

package day08.code_02;

import java.util.concurrent.TimeUnit;

public class MyPriorityTask implements Runnable,
        Comparable<MyPriorityTask> {

    //priority
    private int priority;

    //Task Name
    private String name;

    public MyPriorityTask(String name, int priority) {
        this.priority = priority;
        this.name = name;
    }

    public int getPriority() {
        return priority;
    }

    @Override
    public int compareTo(MyPriorityTask o) {
        //If priority is high, rank first in the queue
        if (this.getPriority() > o.getPriority()) {
            return -1;
            //Lower priority, lower priority
        } else if (this.getPriority() < o.getPriority()) {
            return 1;
        }
        //Same priority without clear order
        return 0;
    }

    @Override
    public void run() {
        //Print Task Name and Priority
        System.out.printf("MyPriorityTask: %s Priority : %d\n",
                name, priority);
        //Hibernate for two seconds
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

main method:

package day08.code_02;

import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Main {

    public static void main(String[] args) {
        //Create executors, task queues use priority queues
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                2, 2,
                1, TimeUnit.SECONDS,
                new PriorityBlockingQueue<Runnable>());
        //Create four tasks
        for (int i = 0; i < 4; i++) {
            //Set task name and priority by construction method
            MyPriorityTask task = new MyPriorityTask("Task" + i, i);
            //Send task to executor
            executor.execute(task);
        }
        //Hibernate for 1 second
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //Create four more tasks
        for (int i = 4; i < 8; i++) {
            //Set task name and priority by construction method
            MyPriorityTask task = new MyPriorityTask("Task" + i, i);
            //Send task to executor
            executor.execute(task);
        }
        //Close Executor
        executor.shutdown();
        //Wait for the executor to finish executing all tasks
        try {
            executor.awaitTermination(1, TimeUnit.DAYS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //Printer End Information
        System.out.printf("Main: End of the program\n");
    }
}

3. Implement ThreadFactory interface to generate custom threads

Special thread factory classes can be customized by implementing the ThreadFactory interface, which makes it easier to create thread objects and the number of threads that can be created by threads.Of course, if we only use the custom thread factory class as a separate class, then the ThreadFactory interface may not be implemented at all; but it must be implemented if we intend to use it in combination with other concurrent API s, such as passing a thread factory reference as a parameter into other methods.In addition, we can use the defaultThreadFactory() method of the Executors class to get the most basic thread factory, which generates the basic thread objects belonging to the same thread group

Sample implementation

In this example, we will use a custom thread factory to create custom threads
Custom Thread Factory Class:

package day08.code_03;

import java.util.concurrent.ThreadFactory;

public class MyThreadFactory implements ThreadFactory {

    //Counter
    private int counter;

    //name prefix
    private String prefix;

    public MyThreadFactory(String prefix) {
        this.prefix = prefix;
        counter = 1;
    }

    @Override
    public Thread newThread(Runnable r) {
        //Create thread with name prefix plus counter number
        MyThread myThread = new MyThread(r, prefix + "-" + counter);
        //Counter self-increment
        counter++;
        //Return to the created thread
        return myThread;
    }
}

Custom Thread Class:

package day08.code_03;

import java.util.Date;

public class MyThread extends Thread {

    //Thread Creation Time
    private Date creationDate;
    //Thread Start Execution Time
    private Date startDate;
    //Thread Execution End Time
    private Date finishDate;

    //Override Constructor
    public MyThread(Runnable target, String name) {
        super(target, name);
        setCreationDate();
    }

    @Override
    public void run() {
        //Set start time
        setStartDate();
        //Execute Tasks
        super.run();
        //Set end time
        setFinishDate();
    }

    //Set the time a thread was created
    public void setCreationDate() {
        creationDate = new Date();
    }

    //Set the time at which a thread begins execution
    public void setStartDate() {
        startDate = new Date();
    }

    //Set the end time of thread execution
    public void setFinishDate() {
        finishDate = new Date();
    }

    //Gets the time spent by a thread executing a task
    public long getExecutionTime() {
        return finishDate.getTime() - startDate.getTime();
    }

    //Override toString method
    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        //Thread Name
        builder.append(getName());
        builder.append(" : ");
        //Creation Time
        builder.append("Creation Date: ");
        builder.append(creationDate);
        //Run time
        builder.append(" Running time: ");
        builder.append(getExecutionTime());
        builder.append(" Milliseconds");
        return builder.toString();
    }
}

Task class:

package day08.code_03;

import java.util.concurrent.TimeUnit;

public class MyTask implements Runnable {
    @Override
    public void run() {
        //Hibernate for 2 seconds
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

main method:

package day08.code_03;

public class Main {

    public static void main(String[] args) {
        //Create a custom thread factory
        MyThreadFactory myTactory = new MyThreadFactory("MyThreadFactory");
        //Create Task
        MyTask myTask = new MyTask();
        //Create custom Thread objects
        Thread thread = myTactory.newThread(myTask);
        //Open Thread
        thread.start();
        //Waiting for thread execution to end
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //Print thread execution end information
        System.out.printf("Main: Thread information\n");
        System.out.printf("%s\n", thread);
        System.out.printf("Main: End of the example\n");
    }

}

4. Use ThreadFactory in Executor objects

In the third section, we wrote our own thread factory class and thread class.Because we implement the ThreadFactory interface, custom thread factory objects can be passed in as parameters when the executor is created.This executor will use our custom thread factory when creating threads

Sample implementation

In this example, the MyThread, MyThreadFactory, and MyTask classes in the third section are used, and the code is exactly the same, so only the main method is given here.
main method:

package day08.code_04;

import day08.code_03.MyTask;
import day08.code_03.MyThreadFactory;

import java.util.concurrent.*;

public class Main {

    public static void main(String[] args) throws InterruptedException {
        //Create custom factory object
        MyThreadFactory myTactory = new MyThreadFactory("MyThreadFactory");
        //Create an executor and pass in a custom factory object as a parameter
        ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newCachedThreadPool(myTactory);
        //Create Task
        MyTask myTask = new MyTask();
        //Submit Tasks
        executor.submit(myTask);
        //Close Executor
        executor.shutdown();
        //Wait for the executor to finish executing all tasks
        executor.awaitTermination(1, TimeUnit.DAYS);
        //Printer End Information
        System.out.printf("Main: End of the program\n");

    }
}

5. Customize tasks running in the timer thread pool

Scheduled Thread Pool can perform delayed and periodic tasks.Delayed tasks can execute objects that implement the Callable or Runnable interface, but periodic tasks can only execute objects that implement the Runnable interface.In addition, although the tasks we send to the timer thread pool are all tasks that implement the Callable or Runnable interface, in fact, to run in the timer thread pool, we must implement the Runnable ScheduledFuture interface, but this work is done by a method within the thread pool.Before completing the following examples, we need to be familiar with the mechanisms of timed thread pools, otherwise we won't be able to start overriding methods in custom classes

Let's start with a brief analysis of some of the source code for the ScheduledThreadPoolExecutor class and the running process:
There are two internal classes in the ScheduledThreadPoolExecutor class, one is the DelayedWorkQueue class, which is a queue to load tasks. Whenever a task is ready to load into the queue, the task's compareTo method is called to determine where the task is in the queue; the other is the ScheduledFutureTask class, which implements theThe RunnableScheduledFuture interface inherits the FutureTask class.This internal class has the following variables and methods that have important associations with this example:

  • private final long period;: variable period is used to save the execution cycle of the current task
  • RunnableScheduledFuture<V> outerTask = this;: The variable outerTask is used to save tasks that need to be queued next time, pointing to the current object by default
  • private long time;: the variable time represents the next execution time (in nanoseconds) of a periodic task
  • getDelay(TimeUnit unit): This method returns the next time a task will start executing based on the time variable and the nanosecond value of the current time. The source code is as follows, where the now() method returns the nanosecond value of the current time

public long getDelay(TimeUnit unit) {
return unit.convert(time - now(), NANOSECONDS);
}

  • compareTo(Delayed other): This method is called when a task is loaded in the task queue.Waiting for a queue is actually the smallest heap maintained using an array, and elements entering the queue are compared with those in the queue based on the time when the next task begins to execute, and shorter ones are ahead of the queue.From the source code, we can see that the compareTo method of the internal class first determines if the incoming element is the current object itself, if not sorted, then whether the incoming element is an internal class object, and if so, compares it against the time variable.If neither of the first two judgments is valid, it means that the task class in the timer thread pool is the user's custom task class, in which case the compareTo method of the incoming parameter itself is called

    public int compareTo(Delayed other) {
    //Determine if it is yourself
    if (other == this) // compare zero if same object
    return 0;
    //Determine whether it is a ScheduledFutureTask class
    if (other instanceof ScheduledFutureTask) {
    ScheduledFutureTask<?> x = (ScheduledFutureTask<?>)other;
    //Compare based on member variables of internal classes
    long diff = time - x.time;
    if (diff < 0)
    return -1;
    else if (diff > 0)
    return 1;
    else if (sequenceNumber < x.sequenceNumber)
    return -1;
    else
    return 1;
    }
    long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS);
    return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
    }

  • run(): In this run method, it is worth noting that after the run method of FutureTask is executed, we need to reset the execution time of the task, which in this case is equivalent to updating the member variable time of the ScheduledFutureTask class.In addition, since it is a periodic task, we have to put the task back in the queue

    public void run() {
    //Determine if the current task is a periodic task
    boolean periodic = isPeriodic();
    //Determine if the current state is executable
    if (!canRunInCurrentRunState(periodic))
    cancel(false);
    //If not a periodic task
    else if (!periodic)
    //Call the run method of FutureTask to execute the run method of a Runnable or Callable instance
    ScheduledFutureTask.super.run();
    //Otherwise call the runAndReset method to execute and initialize the state
    else if (ScheduledFutureTask.super.runAndReset()) {
    //Set the next execution time of the periodic task
    setNextRunTime();
    //Re-queue the current task and start thread execution
    reExecutePeriodic(outerTask);
    }
    }

Next comes the ScheduledThreadPoolExecutor class, whose parent is ThreadPoolExecutor.Looking at the source code, we can see that the construction method of the ScheduledThreadPoolExecutor class invokes the construction method of the ThreadPoolExecutor class by super, passing in an internal class DelayedWorkQueue object as the task queue for the thread pool, which was still used at the bottom of the timer thread pool.The main methods for timer thread pools are as follows:

  • scheduleAtFixedRate(Runnable command,long initialDelay, long period,TimeUnit unit): This method needs to be overridden in this example.From the source code, we can see that after two checks, a ScheduledFutureTask object is created in the method and used as a parameter to the decorateTask method.The decorateTask method is the key to using custom task classes in the thread pool and we need to override it to return a custom task class instance.

    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
    long initialDelay,
    long period,
    TimeUnit unit) {
    if (command == null || unit == null)
    throw new NullPointerException();
    if (period <= 0)
    throw new IllegalArgumentException();
    //Create internal class objects
    ScheduledFutureTask sft =
    new ScheduledFutureTask(command,
    null,
    triggerTime(initialDelay, unit),
    unit.toNanos(period));
    //This method needs to be overridden and returns sft by default
    RunnableScheduledFuture t = decorateTask(command, sft);
    //Save task for next submission to queue
    sft.outerTask = t;
    //Add tasks to the queue and execute them
    delayedExecute(t);
    return t;
    }

  • DecorateTask (Runnable runnable, RunnableScheduledFuture <V> task): This method is reserved for user expansion, where only internal class objects are returned and nothing is done. We can override this method to return our custom task class instance

    protected RunnableScheduledFuture decorateTask(
    Runnable runnable, RunnableScheduledFuture task) {
    return task;
    }

  • DelayedExecute (RunnableScheduledFuture<?> task): Delays the submission of a task to a queue, where the super.getQueue() method gets the internal queue class recorded above

    private void delayedExecute(RunnableScheduledFuture<?> task) {
    //Check
    if (isShutdown())
    reject(task);
    else {
    //Call parent method to add task to queue
    super.getQueue().add(task);
    //Check
    if (isShutdown() &&
    !canRunInCurrentRunState(task.isPeriodic()) &&
    remove(task))
    task.cancel(false);
    else
    //Start a thread to wait for a task
    ensurePrestart();
    }
    }

Sample implementation

In this example, we will customize a class and run it in a timer thread pool.We need to do a series of things, such as implementing interfaces, inheriting existing classes, and overriding parent methods.

First we need to define a class and implement the RunnableScheduledFuture interface as a custom task class running in a timed thread pool. First we will look at the inheritance relationship of this interface.

From the diagram above, we can see that the RunnableScheduledFuture interface inherits multiple interfaces, which requires a large number of methods to be overridden if implemented directly.However, it is found that the FutureTask class already implements the RunnableFuture interface, and we can reduce the workload simply by inheriting this class.In the construction method of the custom task class, we will call the construction method of the FutureTask class, pass in the object that implements the Runnable or Callable interface and the corresponding return value type, and assign values to the member variables of the current class.There is a member variable in the custom class that implements the RunnableScheduledFuture interface.It will save the internal classes returned by the timer thread pool, which will further reduce the workload because methods such as isPeriodic(), getDelay() can be called directly on this object in subsequent methods.It should be noted that the original book also calls the compareTo() method of this object directly here, which is incorrect.Because custom task classes are used to execute tasks in the timed thread pool and refresh the execution time instead of internal task classes, that is, the time variable is not changed after it is first assigned, but the compareTo() method of internal classes inevitably uses the time variable, which is obviously an error.Unpredictable problems occur when we add more than one cycle task to the timer thread pool.

MyScheduledTask (custom task class running in timer thread pool):

package day08.code_05;


import java.util.Date;
import java.util.concurrent.*;

public class MyScheduledTask<V> extends FutureTask<V>
        implements RunnableScheduledFuture<V> {

    //Save ScheduledFutureTask object
    private RunnableScheduledFuture<V> task;

    //Timer Executor
    private ScheduledThreadPoolExecutor executor;

    //Execution cycle
    private long period;

    //start time
    private long startDate;

    public MyScheduledTask(Runnable runnable, V result,
                           RunnableScheduledFuture<V> task,
                           ScheduledThreadPoolExecutor executor) {
        //Call the construction method of the parent FutureTask
        super(runnable, result);
        this.task = task;
        this.executor = executor;
    }

    public void setPeriod(long period) {
        this.period = period;
    }

    @Override
    public boolean isPeriodic() {
        return task.isPeriodic();
    }

    @Override
    public long getDelay(TimeUnit unit) {
        //Non-periodic tasks directly invoke methods of ScheduledFutureTask objects
        if (!isPeriodic()) {
            return task.getDelay(unit);
        } else {
            //Periodic task but not yet executed, calling the method of the ScheduledFutureTask object directly
            if (startDate == 0) {
                return task.getDelay(unit);
            } else {
                //Periodic task and previously executed
                //Calculate time from next run based on custom attributes
                Date now = new Date();
                long delay = startDate - now.getTime();
                return unit.convert(delay, TimeUnit.MILLISECONDS);
            }
        }
    }

    @Override
    public int compareTo(Delayed o) {
        //Get time difference using custom task class's own method
        long diff = this.getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS);
        //Earlier tasks are ahead of the queue
        if (diff < 0) {
            return -1;
        //Later tasks are behind the queue
        } else if (diff > 0) {
            return 1;
        }
        return 0;
    }

    @Override
    public void run() {
        //Determine if the task is periodic and the executor is not closed
        if (isPeriodic() && (!executor.isShutdown())) {
            //Get the current time
            Date now = new Date();
            //Calculate the execution time of the next task
            startDate = now.getTime() + period;
            //Join the task again
            executor.getQueue().add(this);
        }
        //Print the date when the task starts executing
        System.out.printf("Pre-MyScheduledTask: %s\n", new Date());
        //Cycle of Print Task Execution
        System.out.printf("MyScheduledTask: Is Periodic: %s\n", isPeriodic());
        //Call the FutureTask method to perform the incoming task and reset
        super.runAndReset();
        //Print Task End Time
        System.out.printf("Post-MyScheduledTask: %s\n", new Date());
    }

}

To use a custom task class, we need a pool of custom timer threads to go with it, directly inherit the ScheduledThreadPoolExecutor class, and override the methods in it.In this example, the custom thread pool overrides only two methods and can be changed to suit different needs in practice:
MyScheduledThreadPoolExecutor (custom timer thread pool class):

package day08.code_05;

import java.util.concurrent.RunnableScheduledFuture;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class MyScheduledThreadPoolExecutor extends
        ScheduledThreadPoolExecutor {

    //Specify how many core threads to construct
    public MyScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize);
    }

    @Override
    public ScheduledFuture<?> scheduleAtFixedRate
            (Runnable command, long initialDelay, long period, TimeUnit unit) {
        //Invoke parent class's methods to perform incoming tasks
        ScheduledFuture<?> task = super.scheduleAtFixedRate
                (command, initialDelay, period, unit);
        //Force return values to custom tasks
        MyScheduledTask myTask = (MyScheduledTask) task;
        //Set task execution cycle
        myTask.setPeriod(TimeUnit.MILLISECONDS.convert(period, unit));
        //Return to Task
        return myTask;
    }

    //Decoration task method, which is called in the scheduleAtFixedRate method of the parent class
    @Override
    protected <V> RunnableScheduledFuture<V> decorateTask(
            Runnable runnable, RunnableScheduledFuture<V> task) {
        //Create our own custom task class
        //The third parameter task passes in here is ScheduledThreadPoolExecutor's internal class ScheduledFutureTask
        MyScheduledTask<V> myTask =
                new MyScheduledTask<>(runnable, null, task, this);
        //Return to custom task class
        return myTask;

    }
}

Task (Task class submitted to thread pool for simple dormant work):

package day08.code_05;

import java.util.concurrent.TimeUnit;

public class Task implements Runnable {
    @Override
    public void run() {
        //Print Task Start Prompt
        System.out.printf("Task: Begin\n");
        //Hibernate for 2 seconds
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //Print Task End Prompt
        System.out.printf("Task: End\n");
    }
}

main method:

package day08.code_05;

import java.util.Date;
import java.util.concurrent.TimeUnit;

public class Main {

    public static void main(String[] args) throws InterruptedException {
        //Create our own timer
        MyScheduledThreadPoolExecutor executor = new MyScheduledThreadPoolExecutor(2);
        //Create a task
        Task task = new Task();
        //The time when the print program started
        System.out.printf("Main: %s\n", new Date());
        //Execute tasks at specified times
        executor.scheduleAtFixedRate(task, 1, 3, TimeUnit.SECONDS);
        //The current thread sleeps for 10 seconds
        TimeUnit.SECONDS.sleep(10);
        //Close Executor
        executor.shutdown();
        //Waiting for the executor to close
        executor.awaitTermination(1, TimeUnit.DAYS);
        //Printer End Prompt
        System.out.printf("Main: End of the program\n");
    }
}

6. Generate custom threads for the Fork/Join framework through the ForkJoinWorkerThreadFactory interface

Previously, we created a thread factory by implementing the ThreadFactory interface to generate custom threads.We can also create a thread factory by implementing the ForkJoinWorkerThreadFactory interface to generate custom threads for the Fork/Join framework.

Sample implementation

By creating custom threads in the Fork/Join framework, we can inherit the ForkJoinWorkerThread class and provide the appropriate construction methods.In this example, the custom thread class also overrides the onStart() and onTermination() methods:

  • OnStart(): This method is automatically executed after the thread is created and before the first task starts executing. We can override this method to initialize the internal state of the thread or print the log.Based on the comments in the source code, if you want to override this method, you need to start with the super.onStart() code

  • OnTermination (): This method executes before the thread closes, and we can override it to free up resources or print logs before the thread closes.Depending on the comments in the source code, if you want to override this method, you need to put the super.onTermination() code at the end
    package day08.code_06;

    import java.util.concurrent.ForkJoinPool;
    import java.util.concurrent.ForkJoinWorkerThread;

    public class MyWorkerThread extends ForkJoinWorkerThread {

     //Thread-level counters
     private static ThreadLocal<Integer> taskCounter = new ThreadLocal<>();
    
     //Construction method
     protected MyWorkerThread(ForkJoinPool pool) {
         super(pool);
     }
    
     @Override
     protected void onStart() {
         //The onStart method of the parent class must be called first
         super.onStart();
         //Print Thread Information
         System.out.printf("MyWorkerThread %d: Initializing task counter\n",
                 getId());
         //Initialize Task Counter
         taskCounter.set(0);
     }
    
     @Override
     protected void onTermination(Throwable exception) {
         //Print thread information and number of tasks performed
         System.out.printf("MyWorkerThread %d: %d\n",
                 getId(), taskCounter.get());
         //The onTermination method of the parent class must be called last
         super.onTermination(exception);
     }
    
     //Calling this method can change the value of the task counter
     public void addTask() {
         //Get the value of the counter
         int counter = taskCounter.get().intValue();
         //Self-increasing
         counter++;
         //Update Counter Value
         taskCounter.set(counter);
     }
    

    }

MyWorkerThreadFactory (custom factory class, returns custom thread using factory method)

package day08.code_06;

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinWorkerThread;

public class MyWorkerThreadFactory implements
        ForkJoinPool.ForkJoinWorkerThreadFactory {
    @Override
    public ForkJoinWorkerThread newThread(ForkJoinPool pool) {
        return new MyWorkerThread(pool);
    }
}

MyRecursiveTask (Task class with return value, in this example the sum of very large arrays is not important)

package day08.code_06;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.TimeUnit;

public class MyRecursiveTask extends RecursiveTask<Integer> {

    //Very Large Array
    private int array[];

    //Task start and end locations
    private int start, end;

    //Construction method
    public MyRecursiveTask(int[] array, int start, int end) {
        this.array = array;
        this.start = start;
        this.end = end;
    }

    @Override
    protected Integer compute() {
        //Initialization results
        int ret = 0;
        //Get Current Thread
        MyWorkerThread thread = (MyWorkerThread) Thread.currentThread();
        //Call the thread's addTask method to increase the value of the task counter
        thread.addTask();
        //Break down if the task is too large
        if (end - start > 10000) {
            int middle = (start + end) / 2;
            MyRecursiveTask task1 = new MyRecursiveTask(array, start, middle);
            MyRecursiveTask task2 = new MyRecursiveTask(array, middle, end);
            //Asynchronous execution of tasks
            task1.fork();
            task2.fork();
            //Merge results
            return addResults(task1, task2);
        }
        //Find the sum of arrays in range
        for (int i = start; i < end; i++) {
            ret += array[i];
        }
        //Return results
        return ret;

    }

    private Integer addResults(MyRecursiveTask task1, MyRecursiveTask task2) {
        int value;
        //Trying to get the return value of two tasks
        try {
            value = task1.get().intValue() + task2.get().intValue();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
            value = 0;
        }
        //Hibernate for 1 second
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //Return Result Value
        return value;
    }
}

main method:

package day08.code_06;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;

public class Main {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //Create a custom thread factory
        MyWorkerThreadFactory factory = new MyWorkerThreadFactory();
        //Create a thread pool and pass in a custom thread factory as a parameter
        ForkJoinPool pool = new ForkJoinPool(4, factory, null, false);
        //Create a very large array and initialize
        int[] array = new int[100000];
        for (int i = 0; i < array.length; i++) {
            array[i] = i;
        }
        //Create Task
        MyRecursiveTask task = new MyRecursiveTask(array, 0, array.length);
        //Asynchronous execution
        pool.execute(task);
        //Waiting for Task Execution to End
        task.join();
        //Close Thread Pool
        pool.shutdown();
        //Waiting for the end of task execution in the thread pool
        pool.awaitTermination(1, TimeUnit.DAYS);
        //Print the results returned by the task and the program execution end prompt
        System.out.printf("Main: Result: %d\n", task.get());
        System.out.println("Main: End of the program");
    }

}

7. Customize tasks running in the Fork/Join framework

Previously, tasks we created that could be performed using the Fork/Join framework were typically inherited from and overridden by two abstract classes, RecursiveAciton or RecursiveTask.In fact, we can also create custom task abstract classes based on the construction of these two abstract classes.First look at the source code for two abstract classes, RecursiveAciton and RecursiveTask

  • RecursiveAciton class: the task has no return value
public abstract class RecursiveAction extends ForkJoinTask<Void> {
    private static final long serialVersionUID = 5232453952276485070L;
    
    //An abstract method used primarily to override task logic
    protected abstract void compute();

    //Get task result, current task class has no return value, so this method must return null
    public final Void getRawResult() { return null; }

   //Set task result, current task class has no return value, so this method is empty
    protected final void setRawResult(Void mustBeNull) { }

    //Thread pool invokes this method of task execution
    //This method calls the compute method, which we can override to extend
    protected final boolean exec() {
        compute();
        return true;
    }
}
  • RecursiveTask class: The method has a return value
public abstract class RecursiveTask<V> extends ForkJoinTask<V> {
    private static final long serialVersionUID = 5232453952276485270L;

   //Task results
    V result;

    //An abstract method used primarily to override task logic
    protected abstract V compute();
    
    //Get the task result and return the member variable result directly
    public final V getRawResult() {
        return result;
    }
    
    //Set task results, assign values to member variable result
    protected final void setRawResult(V value) {
        result = value;
    }

    //Thread pool invokes this method of task execution
    //This method calls the compute method, which we can override to extend
    protected final boolean exec() {
        result = compute();
        return true;
    }
}

Sample implementation

Based on the source code of the above two classes, we will create our own non-return value abstract task class in this example and have the task class inherit this custom abstract class instead of RecursiveAciton or RecursiveTask
MyWorkerTask (custom abstract task class):

package day08.code_07;

import java.util.Date;
import java.util.concurrent.ForkJoinTask;

public abstract class MyWorkerTask extends ForkJoinTask<Void> {

    //Task Name
    private String name;

    //Construction method
    public MyWorkerTask(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public Void getRawResult() {
        //Return null because the current task has no return value
        return null;
    }

    @Override
    protected void setRawResult(Void value) {
        //The method is empty because there is no return value
    }

    @Override
    protected boolean exec() {
        //Create Task Start Time
        Date startDate = new Date();
        //Start Task Execution
        compute();
        //Create Task Execution End Time
        Date finishDate = new Date();
        //Calculate time difference
        long diff = finishDate.getTime() - startDate.getTime();
        //Print Task Execution Time
        System.out.printf("MyWorkerTask: %s : %d Milliseconds to complete\n",
                name, diff);
        return true;
    }

    protected abstract void compute();
}

Task (Task class, inheriting custom abstract task class):

package day08.code_07;

public class Task extends MyWorkerTask {

    //Essential elements
    private static final long serialVersionUID = 1L;

    //array
    private int array[];

    //Task start and end locations
    private int start, end;

    //Construction method
    public Task(String name, int[] array, int start, int end) {
        super(name);
        this.array = array;
        this.start = start;
        this.end = end;
    }

    @Override
    protected void compute() {
        //If the task is too large, split it up
        if (end - start > 100) {
            int mid = (start + end) / 2;
            Task task1 = new Task(this.getName() + "1", array, start, mid);
            Task task2 = new Task(this.getName() + "2", array, mid, end);
            //Synchronous execution
            invokeAll(task1, task2);
        } else {
            //Increase Array Elements in Range
            for (int i = start; i < end; i++) {
                array[i]++;
            }
        }
        //Temple 50ms
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

main method:

package day08.code_07;

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;

public class Main {

    public static void main(String[] args) throws InterruptedException {
        //Create arrays, elements default to 0
        int[] array = new int[10000];
        //Create Thread Pool
        ForkJoinPool pool = new ForkJoinPool();
        //Create Task
        Task task = new Task("Task", array, 0, array.length);
        //Execute tasks synchronously
        pool.invoke(task);
        //Close Thread Pool
        pool.shutdown();
        //Wait for the thread pool to close after performing all tasks
        pool.awaitTermination(1, TimeUnit.DAYS);
        //Check if the task completed properly
        //Since the initial elements are all zero, the correct increment should be 1
        for (int i = 0; i < array.length; i++) {
            if (array[i] != 1) {
                System.out.println("Error!");
            }
        }
        //Printer End Prompt
        System.out.printf("Main: End of the program\n");
    }
}

8. Implement custom Lock classes

The ReentrantLock class has been used as a lock before, and in this section we will customize our Lock class.Take the ReentrantLock class as an example, and by looking at the source code, you can see that the bottom of the lock is implemented by a subclass of AbstractOwnableSynchronizer, an abstract class (hereinafter referred to as the AQS class).Looking at the source code of the AQS class, we find that there is a counter (state) inside this class and several methods to manipulate this counter. The original AQS class is the real'lock'. The previously used Lock class is only encapsulated on the real lock.When we try to acquire a lock, the current thread is actually trying to change the value of the AQS class internal counter, which will be updated with a CAS operation.If the update fails, the current thread fails to acquire the lock, and the thread is loaded into a queue (chain table implementation) maintained internally in the CAS class and is constantly trying to change the counter value, which is what we see when we use the lock and the thread is blocked until the lock is acquired.Additionally, if you want customized locks to be reentrant, we can invoke the setExclusiveOwnerThread() and getExclusiveOwnerThread() methods in AbstractOwnableSynchronizer, the parent of the AQS class, to set and get the thread that currently holds the lock, so that when the thread attempts to modify the counter value, we can determine the currentWhether the thread already holds a lock and operates accordingly.After inheriting the AQS Abstract class, we must override the tryAcquire() and tryRelease() methods because the abstract class does not give the correct implementation of the two methods but throws an exception directly.

Sample implementation

In this example, we will inherit the AQS class and override some of its methods to implement custom AQS classes and implement custom Lock classes based on this class.Finally, we'll use a custom Lock class object to synchronize the code
MyLock (custom Lock class, need to implement Lock interface):

package day08.code_08;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyLock implements Lock {

    //Customize AQS Class Objects
    private AbstractQueuedSynchronizer sync;

    public MyLock() {
        //Assigning values to AQS objects by construction
        sync = new MyAbstractQueuedSynchronizer();
    }

    @Override
    public void lock() {
        //Calling a method of the AQS class attempts to modify the value of the counter
        //This method calls the tryAcquire method inside the custom AQS class
        sync.acquire(1);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        //Calling a method of the AQS class attempts to modify the value of the counter (interruptible)
        //This method calls the tryAcquire method inside the custom AQS class
        sync.acquireInterruptibly(1);
    }

    @Override
    public boolean tryLock() {
        //Attempt to acquire a lock, return directly without blocking if failed
        try {
            return sync.tryAcquireNanos(1, 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        //Attempts to acquire a lock within a specified time, returns directly without blocking if failed
        return sync.tryAcquireNanos(1, TimeUnit.NANOSECONDS.convert(time, unit));
    }

    @Override
    public void unlock() {
        //Calling a method of the AQS class attempts to reduce the value of the counter
        //The tryRelease method of the custom AQS class is called inside this method
        sync.release(1);
    }

    @Override
    public Condition newCondition() {
        //Create AQS internal class object and return
        return sync.new ConditionObject();
    }
}

MyAbstractQueuedSynchronizer (custom AQS class):

package day08.code_08;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;

public class MyAbstractQueuedSynchronizer extends
        AbstractQueuedSynchronizer {

    //Use atomic variables as internal counters
    private volatile AtomicInteger state;

    public MyAbstractQueuedSynchronizer() {
        //Initialize counters in construction methods
        state = new AtomicInteger(0);
    }

    @Override
    protected boolean tryAcquire(int arg) {
        //Get the current thread
        Thread now = Thread.currentThread();
        //Determine if the current thread is locked
        if (getExclusiveOwnerThread() == now) {
            //Increase Counter Value
            state.set(state.get() + arg);
            return true;
            //Otherwise, try to increase the value of the counter
        } else if (state.compareAndSet(0, arg)) {
            //Successfully modified, set current thread to lock thread
            setExclusiveOwnerThread(now);
            return true;
        }
        //Modification failure returns false
        return false;
    }

    @Override
    protected boolean tryRelease(int arg) {
        //Get the current thread
        Thread now = Thread.currentThread();
        //The current thread throws an exception directly if it is not a latched thread
        if (now != getExclusiveOwnerThread()) {
            throw new RuntimeException("Error!");
        }
        //Get the current value of the counter
        int number = state.get();
        //Determines whether 0 is achieved by reducing the specified parameter
        if (number - arg == 0) {
            //A value of 0 means that the thread released the lock and set the lock thread to null
            setExclusiveOwnerThread(null);
        }
        //Decrease Counter Value
        return state.compareAndSet(number, number - arg);
    }
}

Task (Task Class):

package day08.code_08;

import java.util.concurrent.TimeUnit;

public class Task implements Runnable {

    //Custom Lock
    private MyLock lock;

    //Task name ever
    private String name;

    public Task(MyLock lock, String name) {
        this.lock = lock;
        this.name = name;
    }

    @Override
    public void run() {
        //Acquire locks
        lock.lock();
        //Print hints for acquiring locks
        System.out.printf("Task: %s: Take the lock\n", name);
        //Call the hello method, primarily to test the reentrancy of custom locks
        hello();
        //Hibernate for two seconds
        try {
            TimeUnit.SECONDS.sleep(2);
            //Print release lock hints
            System.out.printf("Task: %s: Free the lock\n", name);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //Release lock
            lock.unlock();
        }
    }

    private void hello() {
        //Acquire locks
        lock.lock();
        //Print Hello
        System.out.println("Hello!");
        //Release lock
        lock.unlock();
    }
}

main method:

package day08.code_08;

import java.util.concurrent.TimeUnit;

public class Main {

    public static void main(String[] args) {
        //Create custom lock object
        MyLock lock = new MyLock();
        //Create ten tasks and start thread execution separately
        for (int i = 0; i < 10; i++) {
            Task task = new Task(lock, "Task-" + i);
            Thread thread = new Thread(task);
            thread.start();
        }
        //Main thread sleeps for two seconds
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        boolean value;
        //Continuous spin attempts to acquire locks
        do {
            try {
                value = lock.tryLock(1, TimeUnit.SECONDS);
                //Get Lock Failure Print Related Information
                if (!value) {
                    System.out.printf("Main: Trying to get the Lock\n");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                value = false;
            }
        } while (!value);
        //Print successfully acquired lock information
        System.out.println("Main: Got the lock");
        //Release lock
        lock.unlock();
        //Printer End Information
        System.out.println("Main: End of the program");
    }

}

Topics: Java Hibernate Programming