How do I get two threads to execute in sequence?
Suppose you have two threads: thread A and thread B. Both threads can print three numbers in sequence (1-3). Let’s look at the code:
private static void demo1() { Thread A = new Thread(new Runnable() { @Override public void run() { printNumber("A"); }}); Thread B = new Thread(new Runnable() { @Override public void run() { printNumber("B"); }}); A.start(); B.start(); }Copy the code
PrintNumber (String) is used to print the numbers 1, 2, and 3 in sequence:
private static void printNumber(String threadName) { int i=0; while (i++ < 3) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(threadName + "print:" + i); }}Copy the code
Here’s what we got:
B print: 1
A print: 1
B print: 2
A print: 2
B print: 3
A print: 3
Copy the code
You can see both A and B printing numbers.
So, what if we want B to start printing after A finishes printing? We can use the thread.join() method as follows:
private static void demo2() { Thread A = new Thread(new Runnable() { @Override public void run() { printNumber("A"); }}); Thread B = new Thread(new Runnable() { @Override public void run() { System.out.println("B starts waiting for A"); try { A.join(); } catch (InterruptedException e) { e.printStackTrace(); } printNumber("B"); }}); B.start(); A.start(); }Copy the code
The results obtained are as follows:
B starts waiting for A
A print: 1
A print: 2
A print: 3
B print: 1
B print: 2
B print: 3
Copy the code
Therefore, we can see that the a.jin () method will make B wait until A finishes printing.
How do I make two threads intersect in order in a specified way?
So what if we now want B to start printing 1, 2,3 immediately after A prints 1, and then A continues printing 2,3? Obviously, we need more fine-grained locking to control the order of execution.
Here we can take advantage of the object.wait() and object.notify() methods. The code is as follows:
/** * A 1, B 1, B 2, B 3, A 2, A 3 */ private static void demo3() { Object lock = new Object(); Thread A = new Thread(new Runnable() { @Override public void run() { synchronized (lock) { System.out.println("A 1"); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("A 2"); System.out.println("A 3"); }}}); Thread B = new Thread(new Runnable() { @Override public void run() { synchronized (lock) { System.out.println("B 1"); System.out.println("B 2"); System.out.println("B 3"); lock.notify(); }}}); A.start(); B.start(); }Copy the code
The results are as follows:
A 1 A waiting... B 1 B 2 B 3 A 2 A 3Copy the code
That’s what we want.
What’s the matter? First, we create an Object lock shared by A and B: lock = new Object(); When A gets the lock, it first prints 1, then calls the lock.wait() method to put it into A wait state, and then hands over control of the lock. B will not execute until A calls lock.wait() to release control and B acquires the lock. B gets the lock, prints 1, 2, 3, and then calls the lock.notify() method to wake up waiting A. After waking up, A will continue to print the remaining 2 and 3.
I added the log to the code above to make it easier to understand.
private static void demo3() { Object lock = new Object(); Thread A = new Thread(new Runnable() { @Override public void run() { System.out.println("INFO: A is waiting for the lock"); synchronized (lock) { System.out.println("INFO: A got the lock"); System.out.println("A 1"); try { System.out.println("INFO: A is ready to enter the wait state, giving up control of the lock"); lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("INFO: B wakes up A, and A regains the lock"); System.out.println("A 2"); System.out.println("A 3"); }}}); Thread B = new Thread(new Runnable() { @Override public void run() { System.out.println("INFO: B is waiting for the lock"); synchronized (lock) { System.out.println("INFO: B got the lock"); System.out.println("B 1"); System.out.println("B 2"); System.out.println("B 3"); System.out.println("INFO: B ends printing, and calling the notify method"); lock.notify(); }}}); A.start(); B.start();Copy the code
The results are as follows:
INFO: A is waiting for the lock
INFO: A got the lock
A 1
INFO: A is ready to enter the wait state, giving up control of the lock
INFO: B is waiting for the lock
INFO: B got the lock
B 1
B 2
B 3
INFO: B ends printing, and calling the notify method
INFO: B wakes up A, and A regains the lock
A 2
A 3
Copy the code
D is executed after A, B, and C are all synchronized
The method thread.join() described earlier allows one thread to continue execution after waiting for another thread to finish. However, if we connect A, B, and C sequentially to thread D, it will cause A, B, and C to execute sequentially, and we want all three to run simultaneously.
The goal is for three threads A, B and C to start running at the same time, and for each thread to notify D when it has finished running independently. D will not run until A, B, and C have all finished running. Therefore, we use CountdownLatch to implement this type of communication. The basic usage is:
- Create a counter and set an initial value, CountdownLatch CountdownLatch = new CountdownLatch (3;
- Countdownlatch.await () calls this method in the waiting thread and enters the wait state until the count becomes zero; Otherwise, enter the wait state.
- Countdownlatch.countdown () calls this method in another thread, which reduces the count by one;
- When methods in the other countDown() threads set the count to 0, the methods in the countdownlatch.await () waiting thread will immediately exit and continue executing the following code.
The implementation code is as follows:
private static void runDAfterABC() { int worker = 3; CountDownLatch countDownLatch = new CountDownLatch(worker); new Thread(new Runnable() { @Override public void run() { System.out.println("D is waiting for other three threads"); try { countDownLatch.await(); System.out.println("All done, D starts working"); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); for (char threadName='A'; threadName <= 'C'; threadName++) { final String tN = String.valueOf(threadName); new Thread(new Runnable() { @Override public void run() { System.out.println(tN + "is working"); try { Thread.sleep(100); } catch (Exception e) { e.printStackTrace(); } System.out.println(tN + "finished"); countDownLatch.countDown(); } }).start(); }}Copy the code
The results are as follows:
D is waiting for other three threads
A is working
B is working
C is working
A finished
C finished
B finished
All done, D starts working
Copy the code
In fact, the CountDownLatch itself is a reciprocal counter, and we set the initial count to 3. When D is run, it first calls the countdownlatch.await () method to check if the counter value is 0, and if it is not, it stays in await state. . Once A, B, and C have finished running independently of each other countdownlatch.countdown (), this method will be used to decrement the countDown counter by 1. When all three are finished running, the counter is reduced to 0; Otherwise, the counter is reduced to 0. Await () will then trigger D’s methods to terminate A, B, and C, and D will begin to continue.
Therefore, CountDownLatch is suitable for situations where one thread needs to wait on multiple threads.
Three runners are ready to run
The three runners prepare to split up and then start running at the same time when everyone is ready.
This time, each of the three threads A, B, and C needs to be prepared separately and then run simultaneously when all three threads are ready. How do we do that?
On CountDownLatch it can be used to count falls, but only one await() method of one thread will get the response when the count is complete, so multiple threads cannot be triggered at the same time.
To achieve the effect of threads waiting for each other, we can use the CyclicBarrier data structure, which basically says:
- CyclicBarrier = new CyclicBarrier(3); CyclicBarrier = new CyclicBarrier(3);
- These threads begin to prepare simultaneously. Once they are ready, they need to wait for someone else to finish their preparation, so call the cyclicBarrier. Await () method to wait for someone else;
- When the cyclicBarrier.await() method is called by all the specified threads that need to wait simultaneously, which means they are ready, the threads will begin to continue executing simultaneously.
The implementation code is as follows. Imagine that there are three runners who need to start running at the same time, so they need to wait for the other runners until everyone is ready.
private static void runABCWhenAllReady() { int runner = 3; CyclicBarrier cyclicBarrier = new CyclicBarrier(runner); final Random random = new Random(); for (char runnerName='A'; runnerName <= 'C'; runnerName++) { final String rN = String.valueOf(runnerName); new Thread(new Runnable() { @Override public void run() { long prepareTime = random.nextInt(10000) + 100; System.out.println(rN + "is preparing for time:" + prepareTime); try { Thread.sleep(prepareTime); } catch (Exception e) { e.printStackTrace(); } try { System.out.println(rN + "is prepared, waiting for others"); cyclicBarrier.await(); // The current runner is ready, waiting for others to be ready } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } System.out.println(rN + "starts running"); // All the runners are ready to start running together } }).start(); }}Copy the code
The results are as follows:
A is preparing for time: 4131
B is preparing for time: 6349
C is preparing for time: 8206
A is prepared, waiting for others
B is prepared, waiting for others
C is prepared, waiting for others
C starts running
A starts running
B starts running
Copy the code
The child thread returns the result to the main thread
In real development, it is common to create child threads to perform time-consuming tasks and then pass the results back to the main thread. So how do you do that in Java?
Therefore, when a Thread is created, we normally pass a Runnable object to the Thread for execution. Runnable is defined as follows:
public interface Runnable {
public abstract void run();
}
Copy the code
You can see that the run() method does not return any results after execution. What if you want to return results? Here, you can use another similar interface class, Callable:
@FunctionalInterface public interface Callable<V> { /** * Computes a result, or throws an exception if unable to do so. * * @return computed result * @throws Exception if unable to compute a result */ V call() throws Exception; }Copy the code
As you can see, the biggest difference with Callable is that it returns generics.
So the next question is, how do I pass the results of the child thread back? Java has a class that FutureTask can use Callable with, but note that the method get uses to get results blocks the main thread.
For example, we want the child thread to calculate the sum from 1 to 100 and then return the result to the main thread.
private static void doTaskWithResultInWorker() { Callable<Integer> callable = new Callable<Integer>() { @Override public Integer call() throws Exception { System.out.println("Task starts"); Thread.sleep(1000); int result = 0; for (int i=0; i<=100; i++) { result += i; } System.out.println("Task finished and return result"); return result; }}; FutureTask<Integer> futureTask = new FutureTask<>(callable); new Thread(futureTask).start(); try { System.out.println("Before futureTask.get()"); System.out.println("Result:" + futureTask.get()); System.out.println("After futureTask.get()"); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); }}Copy the code
The results are as follows:
Before futureTask.get()
Task starts
Task finished and return result
Result: 5050
After futureTask.get()
Copy the code
As you can see, the main thread blocks when it calls the futureTask.get() method. Callable then starts executing internally and returns the result of the operation; Futuretask.get () then gets the result and the main thread resumes running.
Here, we can see that FutureTask and Callable can get the results of child threads directly in the main thread, but they block the main thread. Of course, if you don’t want to block the main thread, consider using ExecutorService to manage execution by putting FutureTask threads into a thread pool.
conclusion
Multithreading is a common feature of modern languages, interthread communication, thread synchronization and thread safety are very important topics.