Java Concurrency Programming Guide (7): Custom Concurrency Classes

1. Customize the ThreadPoolExecutor class

The Executor framework is a mechanism that allows you to separate thread creation from execution. It is based on the Executor and ExecutorService interfaces and the ThreadPoolExecutor class that implements these two interfaces. It has an internal thread pool and provides methods that allow you to submit two kinds of tasks to the thread pool for execution. These tasks are:

  • Runnable interface, which implements tasks that do not return results
  • Callable interface, implements tasks that return results
In both cases, you only submit the task to the executor. This executor uses threads from the thread pool or creates a new thread to perform these tasks. The executor also decides when the task is executed.

Override some methods of the ThreadPoolExecutor class to calculate the execution time of the tasks you execute in the executor and write statistics to the console about the executor completing its execution.

//1. Create the MyExecutor class and specify that it inherits the ThreadPoolExecutor class.
class MyExecutor extends ThreadPoolExecutor {
    //2. Declare a private property of type ConcurrentHashMap and parameterize it as String and Date classes named startTimes.
    private ConcurrentHashMap<String, Date> startTimes;
    //3. Implement the constructor of this class, use the super keyword to call the constructor of the parent class, and initialize the startTime property.
    public MyExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
                      TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
        startTimes = new ConcurrentHashMap<>();
    }

    //4. Override the shutdown() method. Writes information about executed tasks, running tasks, and pending tasks to the console. Then, use the super keyword to call the shutdown() method of the parent class.
    @Override
    public void shutdown() {
        System.out.printf("MyExecutor: Going to shutdown.\n");
        System.out.printf("MyExecutor: Executed tasks: %d\n", getCompletedTaskCount());
        System.out.printf("MyExecutor: Running tasks:  %d\n", getActiveCount());
        System.out.printf("MyExecutor: Pending tasks:  %d\n", getQueue().size());
        super.shutdown();
    }

    //5. Override the shutdownNow() method. Writes information about executed tasks, running tasks, and pending tasks to the console. Then, use the super keyword to call the shutdownNow() method of the parent class.
    @Override
    public List<Runnable> shutdownNow() {
        System.out.printf("MyExecutor: Going to immediately  shutdown.\n");
        System.out.printf("MyExecutor: Executed tasks: %d\n", getCompletedTaskCount());
        System.out.printf("MyExecutor: Running tasks:  %d\n", getActiveCount());
        System.out.printf("MyExecutor: Pending tasks:  %d\n", getQueue().size());
        return super.shutdownNow();
    }

    //6. Override the beforeExecute() method. Writes a message (the thread name of the task to be executed and the hash code of the task) to the console. In the HashMap, use the hash code of this task as the key to store the start date.
    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        System.out.printf("MyExecutor: A task is beginning: %s : %s\n", t.getName(), r.hashCode());
        startTimes.put(String.valueOf(r.hashCode()), new Date());
    }

    //7. Override the afterExecute() method. Writes the result of the task and information about the running time of the task (the current time minus the start time of the task stored in the HashMap) to the console.
    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        Future<?> result = (Future<?>) r;
        try {
            System.out.printf("*********************************\n");
            System.out.printf("MyExecutor: "+ Thread.currentThread().getName()+" task is finishing.\n");
            System.out.printf("MyExecutor: Result: %s\n", result.get());
            Date startDate = startTimes.remove(String.valueOf(r.hashCode()));
            Date finishDate = new Date();
            long diff = finishDate.getTime() - startDate.getTime();
            System.out.printf("MyExecutor: Duration: %d\n", diff);
            System.out.printf("*********************************\n");
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace ();
        }
    }
}

//8. Create a SleepTwoSecondsTask class that implements the Callable interface parameterized as String class. Implement the call() method. Sleep the current thread for 2 seconds and return the current time converted to String type.
class SleepTwoSecondsTask implements Callable<String> {
    public String call() throws Exception {
        TimeUnit.SECONDS.sleep(2);
        return new Date().toString();
    }
}

// 9. Implement the main class of this example by creating the Main class and implementing the main() method.
class Main7 {
    public static void main(String[] args) {
        //10. Create a MyExecutor object named myExecutor.
        MyExecutor myExecutor = new MyExecutor(2, 4, 1000, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>());
        //11. Create an array of Future objects parameterized as String class to store the result object of the task you will submit to the executor.
        List<Future<String>> results = new ArrayList<>();
        //12. Submit 10 Task objects.
        for (int i = 0; i < 10; i++) {
            SleepTwoSecondsTask task = new SleepTwoSecondsTask();
            Future<String> result = myExecutor.submit(task);
            results.add(result);
        }
        //13. Use the get() method to get the execution results of the first 5 tasks. Write this information to the console.
        for (int i = 0; i < 5; i++) {
            try {
                String result = results.get(i).get();
                System.out.printf("Main: Result for Task %d : %s\n", i, result);
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace ();
            }
        }
        //14. Use the shutdown() method to end the execution of this executor.
        myExecutor.shutdown();
        //15. Use the get() method to get the execution results of the last 5 tasks. Write this information to the console.
        for (int i = 5; i < 10; i++) {
            try {
                String result = results.get(i).get();
                System.out.printf("Main: Result for Task %d :  %s\n", i, result);
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace ();
            }
        }
        //16. Use the awaitTermination() method to wait for the completion of this executor.
        try {
            myExecutor.awaitTermination(1, TimeUnit.DAYS);
        } catch (InterruptedException e) {
            e.printStackTrace ();
        }
        //17. Write a message indicating the end of this program execution.
        System.out.printf("Main: End of the program.\n");
    }
}


2,PriorityBlockingQueue

Inside the executor framework, an executor uses a blocking queue to store pending tasks. Store tasks in the order in which they arrive at the executor. A possible alternative is to use a priority queue to store new tasks. This way, if a new high-priority task arrives at the executor, it will be executed before other low-priority tasks that are already waiting to be executed.

//1. Create a MyPriorityTask class that implements the Runnable interface and the Comparable interface parameterized as the MyPriorityTask class.
class MyPriorityTask implements Runnable, Comparable<MyPriorityTask> {
    //2. Declare a private property priority of type int.
    private int priority;
    private String name;

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

    //5. Implement a method to return the value of the priority property.
    public int getPriority() {
        return priority;
    }

    // 6. Implement the compareTo() method declared in the Comparable interface. It receives a MyPriorityTask object as a parameter,
    // Compare the priority of these two objects (the current object and the parameter object). Let higher-priority tasks execute before lower-priority tasks.
    @Override
    public int compareTo(MyPriorityTask o) {
        if (this.getPriority() < o.getPriority()) {
            return 1;
        }
        if (this.getPriority() > o.getPriority()) {
            return -1;
        }
        return 0;
    }

    // 7. Implement the run() method. Put the current thread to sleep for 2 seconds.
    @Override
    public void run() {
        System.out.printf("MyPriorityTask: %s Priority :  %d\n", name, priority);
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace ();
        }
    }
}

//8. Implement the main class of this example by creating the Main class and implementing the main() method.
class Main8 {
    public static void main(String[] args) {
        //9. Create a ThreadPoolExecutor object named executor. Use the PriorityBlockingQueue parameterized as the Runnable interface as the queue used by the executor to store pending tasks.
        ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 1, TimeUnit.SECONDS, new PriorityBlockingQueue<>());
        //10. Submit 4 tasks to the executor using the loop counter as the priority. Submit these tasks to the executor using the execute() method.
        for (int i = 0; i < 4; i++) {
            MyPriorityTask task = new MyPriorityTask("Task " + i, i);
            executor.execute(task);
        }
        //11. Make the current thread sleep for 1 second.
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace ();
        }
        //12. Submit 4 additional tasks to the executor using the loop counter as the priority. Submit these tasks to the executor using the execute() method.
        for (int i = 4; i < 8; i++) {
            MyPriorityTask task = new MyPriorityTask("Task " + i, i);
            executor.execute(task);
        }
        //13. Use the shutdown() method to shut down the executor.
        executor.shutdown();
        //14. Use the awaitTermination() method to wait for the end of this executor.
        try {
            executor.awaitTermination(1, TimeUnit.DAYS);
        } catch (InterruptedException e) {
            e.printStackTrace ();
        }
        //15. Write a message indicating the end of this program.
        System.out.printf("Main: End of the program.\n");
    }
}

3. Implement the ThreadFactory interface to generate custom threads


Using this factory, we centralize the creation of objects, taking advantage of easily changing the classes that create objects, or the way we create those objects, easily limiting the limited resources for creating objects. For example, we can only have N objects of one type, and it is easy to generate statistics about object creation.
class MyThreadFactory implements ThreadFactory {
    //11. Declare a private, int type property counter.
    private int counter;
    //12. Declare a private property prefix of type String.
    private String prefix;
    //13. Implement the constructor of this class and initialize its properties.
    public MyThreadFactory(String prefix) {
        this.prefix = prefix;
        counter = 1;
    }

    // Implement the newThread() method. Create a MyThread object and increment the counter property value.
    @Override
    public Thread newThread(Runnable r) {
        MyThread myThread = new MyThread(r, prefix + "-" + counter);
        counter++;
        return myThread;
    }
}

4. Use our ThreadFactory in an Executor object

Inside the Executor framework, it provides a ThreadFactory interface to create threads, which is used to spawn new threads.
MyThreadFactory threadFactory=new MyThreadFactory("MyThreadFactory");
ExecutorService executor=Executors.newCachedThreadPool(threadFactory);

You have created an Executor object using the newCachedThreadPool() method of the Executors class. You have passed in the factory object created earlier as a parameter, so the created Executor object will use this factory to create the threads it needs and it will execute threads of class MyThread.

5. Customize the tasks that run in the scheduled thread pool

The scheduled thread pool is an extension of the Executor framework's basic thread pool, allowing you to customize a schedule to execute tasks that need to be executed after a period of time. It is implemented through the ScheduledThreadPoolExecutor class, which allows running both of the following tasks:
  • Delayed task: This kind of task is executed only once after a certain period of time.
  • Periodic tasks: These tasks are executed after a delay and then usually run periodically
Delayed tasks can execute Callable and Runnable objects, but periodic tasks can only execute Runnable objects. All tasks executed through the schedule pool must implement the RunnableScheduledFuture interface.
//1. Create a class called MyScheduledTask that parameterizes a generic type called V. It extends the FutureTask class and implements the RunnableScheduledFuture interface.
class MyScheduledTask<V> extends FutureTask<V> implements RunnableScheduledFuture<V> {
    //2. Declare a private RunnableScheduledFuture property named task.
    private RunnableScheduledFuture<V> task;
    //3. Declare a private ScheduledThreadPoolExecutor named executor.
    private ScheduledThreadPoolExecutor executor;
    //4. Declare a private long property named period.
    private long period;
    //5. Declare a private long property named startDate.
    private long startDate;
    //6. Implement the constructor of the class. It receives the task: the Runnable object to be run, the result to be returned by the task,
    // The RunnableScheduledFuture task that will be used to create the MyScheduledTask object, and the ScheduledThreadPoolExecutor object to execute this task.
    // Call the constructor of its superclass and store the task and executor properties.
    public MyScheduledTask(Runnable runnable, V result, RunnableScheduledFuture<V> task, ScheduledThreadPoolExecutor executor) {
        super(runnable, result);
        this.task = task;
        this.executor = executor;
    }
    //7. Implement the getDelay() method. If it is a periodic task and the value of the startDate image is not 0, calculate and return the difference between the startDate property and the current date.
    // Otherwise, return the previous task's delay value stored in the task property. Don't forget to pass the time unit as a parameter when you want to return the result.
    @Override
    public long getDelay(TimeUnit unit) {
        if (!isPeriodic()) {
            return task.getDelay(unit);
        } else {
            if (startDate == 0) {
                return task.getDelay(unit);
            } else {
                Date now = new Date();
                long delay = startDate - now.getTime();
                return unit.convert(delay, TimeUnit.MILLISECONDS);
            }
        }
    }
    //8. Implement the compareTo() method. Call the compareTo() method of the original task.
    @Override
    public int compareTo(Delayed o) {
        return task.compareTo(o);
    }
    //9. Implement the isPeriodic() method. Call the isPeriodic() method of the original task.
    @Override
    public boolean isPeriodic() {
        return task.isPeriodic();
    }
    //10. Implement the method run(). If this is a recurring task, you update its startDate property with the start date of the next task to execute.
    // Calculate it with the sum of the current date and time interval. Then, add the task again to the queue of the ScheduledThreadPoolExecutor object.
    @Override
    public void run() {
        if (isPeriodic() && (!executor.isShutdown())) {
            Date now = new Date();
            startDate = now.getTime() + period;
            executor.getQueue().add(this);
        }
        //11. Print the information of the current date to the console, call the runAndReset() method to run the task, and then print another piece of information about the current date to the console.
        System.out.printf("Pre-MyScheduledTask: %s\n", new Date());
        System.out.printf("MyScheduledTask: Is Periodic:%s\n", isPeriodic());
        super.runAndReset ();
        System.out.printf("Post-MyScheduledTask: %s\n", new Date());
    }
    //12. Implement the setPeriod() method to establish the cycle time of the task.
    public void setPeriod(long period) {
        this.period = period;
    }
}

//13. Create a class named MyScheduledThreadPoolExecutor to implement a ScheduledThreadPoolExecutor object that runs MyScheduledTask tasks. Specifically extends the ScheduledThreadPoolExecutor class.
class MyScheduledThreadPoolExecutor extends ScheduledThreadPoolExecutor {
    //14. To implement the constructor of a class, just call the constructor of its parent class.
    public MyScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize);
    }
    //15. Implement the method decorateTask(). It receives as parameters the Runnable object to be run and the RunnableScheduledFuture task that will run the Runnable object. Use these objects to construct to create and return MyScheduledTask tasks.
    @Override
    protected <V> RunnableScheduledFuture<V> decorateTask(Runnable runnable, RunnableScheduledFuture<V> task) {
        MyScheduledTask<V> myTask = new MyScheduledTask<V>(runnable, null, task, this);
        return myTask;
    }
    //16. Override the method scheduledAtFixedRate(). Call the method of its parent class and convert the returned result into a MyScheduledTask object,
    //Call the setPeriod() method of this object to set its period.
    @Override
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
        ScheduledFuture<?> task = super.scheduleAtFixedRate(command, initialDelay, period, unit);
        MyScheduledTask<?> myTask = (MyScheduledTask<?>) task;
        myTask.setPeriod(TimeUnit.MILLISECONDS.convert(period, unit));
        return task;
    }
}

//17. Create a class named Task that implements the Runnable interface.
class Task implements Runnable {
    //18. Implement the method run(). Print a message at the start of the task and put the current thread to sleep for 2 seconds. Finally at the end of the task, another message is printed.
    @Override
    public void run() {
        System.out.printf("Task: Begin.\n");
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace ();
        }
        System.out.printf("Task: End.\n");
    }
}

//19. Create the main class of the example by creating a class called Main and adding the main() method.
class Main9 {
    public static void main(String[] args) throws Exception {
        //20. Create a MyScheduledThreadPoolExecutor object named executor. Use 2 as parameter to get 2 threads in the pool.
        MyScheduledThreadPoolExecutor executor = new MyScheduledThreadPoolExecutor(2);
        //21. Create a Task object named task. Write the current date to the console.
        Task task = new Task();
        System.out.printf("Main: %s\n", new Date());
        //22. Use the schedule() method to send a delayed task to the executor. This task runs after a delay of one second.
        executor.schedule(task, 1, TimeUnit.SECONDS);
        //23. Let the main thread sleep for 3 seconds.
        TimeUnit.SECONDS.sleep(3);
        //24. Create another Task object. Print the current date on the console again.
        task = new Task();
        System.out.printf("Main: %s\n", new Date());
        //25. Use the method scheduleAtFixedRate() to send a periodic task to the executor. This task is run after a delay of one second and then every 3 seconds.
        executor.scheduleAtFixedRate(task, 1, 3, TimeUnit.SECONDS);
        //26. Let the main thread sleep for 10 seconds.
        TimeUnit.SECONDS.sleep(10);
        //27. Use the shutdown() method to shut down the executor. Use the awaitTermination() method to wait for the executor to terminate.
        executor.shutdown();
        executor.awaitTermination(1, TimeUnit.DAYS);
        //28. Write a message to the console to indicate the end of the task.
        System.out.printf("Main: End of the program.\n");
    }
}
  • decorateTask() method: This class extends the ScheduledThreadPoolExecutor executor and its methods provide a mechanism to convert the ScheduledThreadPoolExecutor executor's default scheduled tasks into MyScheduledTask tasks. So, when you implement your version of the scheduled task, you must implement your version of the scheduled executor.
  • The getDelay() method is called by the scheduled executor to confirm whether it needs to run the task. This method responds differently to delayed tasks and periodic tasks. As mentioned earlier, the constructor of the MyScheduledTask class receives the original ScheduledRunnableFuture object that will execute the Runnable object, and stores it as a class property to access its methods and its data. When we want to run a delayed task, the getDelay() method returns the delay of the original task, but in the case of periodic tasks, the getDelay() method returns the difference between the value of the startDate property and the current time.
  • The run() method is used to execute tasks. A special feature of periodic tasks is that you must put the next execution of the task as a new task into the executor's queue if you want to run the task again. So, if you execute a periodic task, you determine the value of the startDate property by adding the current time to the task's execution period, and then storing the task in the executor's queue. The startDate property stores the time when the next task will start running. Then, use the runAndReset() method provided by the FutureTask class to run the task. The deferred tasks in this example do not need to put them in the executor's queue since they are only executed once.
  • scheduleAtFixedRate() method: As we mentioned earlier, for periodic tasks, you use the period of the task to determine the value of the startDate property, but you haven't initiated the period yet. You must override this method to receive the period as a parameter and pass it to the MyScheduledTask class so it can be used.



Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325947861&siteId=291194637