Runnable interface
Runnable is an interface. There are only run methods in it. Thread also implements runnable interface, so to realize multithreading, you need to realize runnable interface finally, not to mention to directly demonstrate the code.
Runnable test code
public class RunnableDemo { public static void main(String[] args) { // You can define a class to implement the Runnable interface RunnableImpl runnable = new RunnableImpl(); new Thread(runnable).start(); // Easy writing new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "Current thread"); } }).start(); // lambda functional interface, more elegant new Thread(() -> System.out.println(Thread.currentThread().getName() + "Current thread")).start(); } } class RunnableImpl implements Runnable { @Override public void run() { // Specific execution logic System.out.println(Thread.currentThread().getName() + "Current thread"); } }
Callable / Future
Callable is also an interface. There are only call methods in it. The difference between callable and Runnable is that callable can get return value, throw exception and so on by cooperating with future. Future represents the result of asynchronous calculation and provides the method to check whether the calculation is completed, wait for the calculation to be completed and get the result. FutureTask is commonly used. In fact, run is executed in run method. Let's look at it directly below Code.
Callable test code
import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class CallableDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { Callable<String> callable = new Callable<String>() { @Override public String call() throws Exception { Random random = new Random(); return "The return value is:" + random.nextInt(200); } }; FutureTask<String> futureTask = new FutureTask<>(callable); new Thread(futureTask).start(); String result = futureTask.get(); // Block, wait for execution result to continue System.out.println(result); } }
Do you see the effect of Callable / Future? Yes, if we apply it to the actual business? I have an idea! Look at code ~
FutureTask test code
import java.util.HashMap; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; public class MyFutureTaskDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { // In the actual business scenario, a method will call multiple interfaces / query multiple database tables / multiple sql // The execution time is cumulative if it is executed in sequence, but what if it is executed with FutureTask? The execution time is the slowest one, which leads to inefficiency~ // Here's a chestnut // Here you can use the MyFutureTask we wrote earlier. The effect is the same FutureTask<String> futureTask1 = new FutureTask<>(() -> { TimeUnit.SECONDS.sleep(3L); return "Task 1 interface completed"; }); FutureTask<String> futureTask2 = new FutureTask<>(() -> { TimeUnit.SECONDS.sleep(5L); return "Task 2 interface execution completed"; }); FutureTask<String> futureTask3 = new FutureTask<>(() -> { TimeUnit.SECONDS.sleep(2L); return "Task 3 interface execution completed"; }); long time = System.currentTimeMillis(); new Thread(futureTask1).start(); new Thread(futureTask2).start(); new Thread(futureTask3).start(); Map<String, String> result = new HashMap<>(); result.put("result1", futureTask1.get()); result.put("result2", futureTask2.get()); result.put("result3", futureTask3.get()); System.out.println(result); System.out.println("Execution time:" + (System.currentTimeMillis() - time)); } }
CountDownLatch use
CountDownLatch can also implement asynchronous multithreading. You can understand that it is an inverted counter. Wait for the counter value to be 0, and then continue to execute. You can also do concurrent testing. Next, test the code directly~
CountDownLatch test code
import java.util.HashMap; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; public class CountDownLatchDemo { public static void main(String[] args) throws InterruptedException { ExecutorService executorService = Executors.newCachedThreadPool(); CountDownLatch count = new CountDownLatch(3); Map<String, String> result = new HashMap<>(); long time = System.currentTimeMillis(); executorService.submit(() -> { try { TimeUnit.SECONDS.sleep(3L); result.put("result1", "Task 1 interface completed"); } catch (InterruptedException e) { e.printStackTrace(); } finally { count.countDown(); } }); executorService.submit(() -> { try { TimeUnit.SECONDS.sleep(5L); result.put("result2", "Task 2 interface execution completed"); } catch (InterruptedException e) { e.printStackTrace(); } finally { count.countDown(); } }); executorService.submit(() -> { try { TimeUnit.SECONDS.sleep(2L); result.put("result3", "Task 3 interface execution completed"); } catch (InterruptedException e) { e.printStackTrace(); } finally { count.countDown(); } }); count.await(); // Waiting counter to zero System.out.println(result); System.out.println("Execution time:" + (System.currentTimeMillis() - time)); executorService.shutdown(); } }
The principle of CountDownLatch is similar to the lock we mentioned before, and it can also be implemented with AQS (see previous sections for AQS). Next, we write our own CountDownLatch.
Realize own CountDownLatch
import java.util.Iterator; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.LockSupport; public class MyCountDownLatch { //MyAqs aqs = new MyAqs() { // // @Override // public int tryAcquireShared() { // return (this.getState().get() == 0) ? 1 : -1; // } // // @Override // public boolean tryReleaseShared() { // return this.getState().decrementAndGet() == 0; // } //}; // //public MyCountDownLatch(int count) { // aqs.getState().set(count); //} // //public void await() { // aqs.acquireShared(); //} // //public void countDown() { // aqs.releaseShared(); //} volatile AtomicInteger count; LinkedBlockingQueue<Thread> waiters = new LinkedBlockingQueue<>(); public MyCountDownLatch(int count) { this.count = new AtomicInteger(count); } public void await() { boolean addQ = true; while (this.count.get() != 0) { if (addQ) { waiters.offer(Thread.currentThread()); addQ = false; } else { LockSupport.park(); // Wake up after receiving unpark notification, continue the cycle } } waiters.remove(Thread.currentThread()); // Remove thread from waiting collection } public void countDown() { if (this.count.decrementAndGet() == 0) { // Notify other waiting threads Iterator<Thread> iterator = waiters.iterator(); while (iterator.hasNext()) { Thread waiter = iterator.next(); LockSupport.unpark(waiter); // Wake up thread to continue } } } }
CyclicBarrier use
CyclicBarrier, also known as "thread fence", specifies the number of fence threads when creating an object; automatically performs the specified task when the number of threads reaches the specified amount; the important difference between CyclicBarrier and CountDownLatch is that CyclicBarrier objects can trigger execution multiple times; typical application scenarios:
- Large amount of data, realizing batch insert operation database
- Statistics for the whole year, 12 threads for each month, after all statistics are completed, the statistics will be summarized;
CyclicBarrier test code
import java.util.concurrent.CyclicBarrier; import java.util.concurrent.LinkedBlockingQueue; public class CyclicBarrierDemo { public static void main(String[] args) throws InterruptedException { LinkedBlockingQueue<String> sqls = new LinkedBlockingQueue<>(); // Task 1 + 2 + 3... 1000 is divided into 100 tasks (1 +.. 10, 11 + 20) - > 100 threads to process. // barrierAction execution is triggered whenever four threads are in wait state CyclicBarrier barrier = new CyclicBarrier(4, new Runnable() { @Override public void run() { // This is to trigger batch execution every 4 database operations System.out.println("There are 4 threads executing, and batch insertion begins: " + Thread.currentThread().getName()); for (int i = 0; i < 4; i++) { System.out.println(sqls.poll()); } } }); for (int i = 0; i < 10; i++) { new Thread(() -> { try { sqls.add("data - " + Thread.currentThread().getName()); // Cache up Thread.sleep(1000L); // Simulation database operation time consuming barrier.await(); // Wait for the fence to open, and four threads will execute this code before continuing to execute System.out.println(Thread.currentThread().getName() + "Insert completed"); } catch (Exception e) { e.printStackTrace(); } }).start(); } Thread.sleep(2000); } }
Semaphore use
Semaphore, also known as semaphore, controls multiple threads to scramble for permission; typical scenario: token bucket, code concurrent processing flow restriction.
Semaphore test code
import java.util.Random; import java.util.concurrent.Semaphore; public class SemaphoreDemo { public static void main(String[] args) { SemaphoreDemo semaphoreTest = new SemaphoreDemo(); int N = 9; // Number of guests Semaphore semaphore = new Semaphore(5); // Number of hands, limit number of requests for (int i = 0; i < N; i++) { String vipNo = "vip-00" + i; new Thread(() -> { try { semaphore.acquire(); // Get token semaphoreTest.service(vipNo); semaphore.release(); // release token } catch (InterruptedException e) { e.printStackTrace(); } }).start(); } } // Limit current control 5 threads access at the same time public void service(String vipNo) throws InterruptedException { System.out.println("Come out upstairs to meet a VIP, VIP number" + vipNo + ",..."); Thread.sleep(new Random().nextInt(3000)); System.out.println("Welcome VIP out, VIP No" + vipNo); } }
Realize your Semaphore
public class MySemaphore { MyAqs aqs = new MyAqs() { @Override public int tryAcquireShared() { // Semaphore acquisition, quantity - 1 for (; ; ) { int count = getState().get(); int n = count - 1; if (count <= 0 || n < 0) { return -1; } if (getState().compareAndSet(count, n)) { return 1; } } } @Override public boolean tryReleaseShared() { // Semaphore release, quantity + 1 return this.getState().incrementAndGet() >= 0; } }; /** * Set the number of tokens */ public MySemaphore(int count) { aqs.getState().set(count); } /** * Get token */ public void acquire() { aqs.acquireShared(); } /** * release token */ public void release() { aqs.releaseShared(); } }
Use of Fork / Join framework
The ForkJoinPool is an implementation of the ExecutorService interface, designed for work that can be recursively broken down into small chunks.
The Fork / Join framework allocates tasks to the working threads in the thread pool, making full use of the advantages of multiprocessors and improving program performance.
The first step in using the Fork / Join framework is to write code that does part of the work. Similar pseudo codes are as follows:
If (the current working part is small enough)
Do the work directly
Other
Split the current work into two parts
Call these two parts and wait for the result
Wrap this code in the ForkJoinTask subclass, usually a recursive task (which can return results) or a recursive action.
Implementation ideas:
- Each Worker thread maintains a task queue, that is, the task queue in the ForkJoinWorkerThread
- Task queue is a two-way queue, so LIFO and FIFO can be realized at the same time
- Subtasks are added to the task queue of the Worker thread where the original task is located
- The Worker thread uses LIFO to get tasks, and the tasks in the last queue get out first (the subtasks always join the queue later, but they need to be executed first)
- When the task queue is empty, a task execution will be taken from the queue of other workers
- If a Worker thread encounters a join operation and is processing other tasks at this time, it will wait until the end of this task. Otherwise, return directly
- If a Worker thread fails to steal a task, it will take a break with yield or sleep and try again (if all threads are idle, i.e. there is no task running, then the thread will enter the blocking state and wait for the new task to arrive)
apply
Use as few thread pools as possible - in most cases, the best decision is to use one for each application or system
If no specific tuning is required, use the default public thread pool
Split the ForkJoinTask into subtasks with reasonable thresholds
Avoid any blocking in the ForkJoinTask
Suitable for data processing, result summary, statistics and other scenarios;
java8 instance: java.util.Arrays class for the parallelSort() method
Conclusion: the theory of performance improvement brought by job theft is that API is more complex, and the controllability in actual research and development is not as good as other APIs.
ForkJoinPool test code
import java.util.concurrent.*; public class ForkJoinDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { // Essentially a thread pool, the default number of threads: the number of cores of the CPU // By default, the number of parallel threads is equal to the number of processors available // The main difference between ForkJoinPool and other types of ExecutorService is that it uses work stealing: // All threads in the pool attempt to find and perform tasks submitted to the pool and / or created by other active tasks // (if there is no work, it will eventually block waiting for work). ForkJoinPool forkJoinPool = new ForkJoinPool(); // Suitable for data processing, result summary, statistics and other scenarios // Take chestnut for example to calculate 1 + 2 + 3 + The result of 100W, split multiple threads for calculation, merge and summarize to return // Query the data of multiple interfaces, merge and return // For other examples, look up multiple table data in the database, and query in multiple times // fork/join // forkJoinPool.submit() // Calculate 1 + 2 + 3 + The result of 100W is just an example long time1 = System.currentTimeMillis(); // Submit a decomposable recursive task ForkJoinTask<Long> forkJoinTask = forkJoinPool.submit(new SumTask(1, 1000000)); System.out.println("The result is:" + forkJoinTask.get() + " execution time" + (System.currentTimeMillis() - time1)); //forkJoinPool.shutdown(); // It takes time to actually perform Fork, Join, access from queue and other operations. Please use it in the appropriate scenario long time2 = System.currentTimeMillis(); long sum = 0L; for (int i = 1; i <= 1000000; i++) { sum += i; } System.out.println("The result is:" + sum + " execution time" + (System.currentTimeMillis() - time2)); // Submit a decomposable recursive action task forkJoinPool.submit(new PrintAction(1, 1000)); //Block the current thread until all tasks in the ForkJoinPool are completed forkJoinPool.awaitTermination(2, TimeUnit.SECONDS); forkJoinPool.shutdown(); } } class SumTask extends RecursiveTask<Long> { private static final int THRESHOLD = 1000; private int start; // Start subscript private int end; // End subscript public SumTask(int start, int end) { this.start = start; this.end = end; } @Override protected Long compute() { // If the calculation amount of the current task is within the threshold, the calculation will be performed directly if (end - start < THRESHOLD) { return computeByUnit(); } else { // If the calculation amount of the current task exceeds the threshold range, split the calculation task // Calculate intermediate index int middle = (start + end) / 2; //Define subtask - iteration idea SumTask left = new SumTask(start, middle); SumTask right = new SumTask(middle + 1, end); //Divide subtask fork left.fork(); right.fork(); //Consolidated calculation results return left.join() + right.join(); } } private long computeByUnit() { long sum = 0L; for (int i = start; i <= end; i++) { sum += i; } return sum; } } class PrintAction extends RecursiveAction { private static final int THRESHOLD = 20; private int start; // Start subscript private int end; // End subscript public PrintAction(int start, int end) { this.start = start; this.end = end; } @Override protected void compute() { //Start printing when the end start value is less than MAX if ((end - start) < THRESHOLD) { computeByUnit(); } else { // Break a big task into two small tasks int middle = (start + end) / 2; PrintAction left = new PrintAction(start, middle); PrintAction right = new PrintAction(middle + 1, end); left.fork(); right.fork(); } } private void computeByUnit() { for (int i = start; i <= end; i++) { System.out.println(Thread.currentThread().getName() + "Value:" + i); } } }