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
- Customize the ThreadPoolExecutor class
- Implement priority-based Executor classes
- Implement ThreadFactory interface to generate custom threads
- Using ThreadFactory in Executor Objects
- Customize tasks running in the timer thread pool
- Generate custom threads for the Fork/Join framework through the ForkJoinWorkerThreadFactory interface
- Customize tasks running in the Fork/Join framework
- 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"); } }