The title description is as follows:

Write A program, open three threads, the ID of the three threads are respectively A, B and C, each thread to print their OWN ID on the screen 10 times, requiring the output results must be displayed in ABC order, such as ABCABCABC… In turn, recursive

This is a classic multithreaded programming interview question, first of all, the demand of this question is very strange, first open multithreaded, and then serial print ABC, this is not full of support? But since it’s an interview question, I’ll leave it at that. The purpose is to test your multithreaded programming background. This one, if you can’t write down three or four solutions, you’re embarrassed to say you learned multithreading. Ha ha joke, below for you to introduce several solutions to this problem.

1. The simplest way — use LockSupport

LockSupport is Java. The util. Concurrent. The locks under the package of tools, its static methods unpark park () () and can block the current thread respectively and awaken the effect of the specified thread, so use it to solve this problem is a piece of cake, the code is as follows:

public class PrintABC {
  
    static Thread threadA, threadB, threadC;
  
  	public static void main(String[] args) {
        threadA = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
              	// Prints the current thread name
                System.out.print(Thread.currentThread().getName());
              	// Wake up the next thread
                LockSupport.unpark(threadB);
              	// The current thread is blockedLockSupport.park(); }},"A");
        threadB = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
              	// block and wait to wake up
                LockSupport.park();
                System.out.print(Thread.currentThread().getName());
              	// Wake up the next threadLockSupport.unpark(threadC); }},"B");
        threadC = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
              	// block and wait to wake up
                LockSupport.park();
                System.out.print(Thread.currentThread().getName());
              	// Wake up the next threadLockSupport.unpark(threadA); }},"C"); threadA.start(); threadB.start(); threadC.start(); }}Copy the code

The result is as follows:

ABCABCABCABCABCABCABCABCABCABC
Process finished with exit code 0
Copy the code

2. The most traditional approach — use synchronized locks

In this method, Java synchronized keyword is directly used, and Object wait() and notifyAll() methods are used to realize the effect of alternating printing threads. However, this method is complicated and has a large amount of code. Since neither notify() nor notifyAll() can wake up the specified thread, three Boolean variables are required to control the order of thread execution. Another thing to note is that i++ in the for loop needs to be executed after the thread prints, otherwise i++ will be executed every time we wake up, whether it’s the current thread’s turn to print or not, which is obviously not what we want. The code is as follows (generally, the execution logic of THREAD B, C and thread A is similar, and only detailed comments are made in the code of thread A) :

public class PrintABC {
    // Use Boolean variables to control the printing order. True indicates that the current thread is printing
    private static boolean startA = true;
    private static boolean startB = false;
    private static boolean startC = false;

    public static void main(String[] args) {
      	// as a lock object
        final Object o = new Object();
      	/ / A thread
        new Thread(() -> {
            synchronized (o) {
                for (int i = 0; i < 10;) {if (startA) {
                      	// Indicates that it is the current thread's turn to print
                        System.out.print(Thread.currentThread().getName());
                      	// B will print next, so set startB to true and others to false
                        startA = false;
                        startB = true;
                        startC = false;
                      	// Wake up other threads
                        o.notifyAll();
                      	// Add I here
                        i++;
                    } else {
                      	// The current thread does not print, so continue to wait
                        try {
                            o.wait();
                        } catch(InterruptedException e) { e.printStackTrace(); }}}}},"A").start();
      	/ / thread B
        new Thread(() -> {
            synchronized (o) {
                for (int i = 0; i < 10;) {if (startB) {
                        System.out.print(Thread.currentThread().getName());
                        startA = false;
                        startB = false;
                        startC = true;
                        o.notifyAll();
                        i++;
                    } else {
                        try {
                            o.wait();
                        } catch(InterruptedException e) { e.printStackTrace(); }}}}},"B").start();
      	/ / C thread
        new Thread(() -> {
            synchronized (o) {
                for (int i = 0; i < 10;) {if (startC) {
                        System.out.print(Thread.currentThread().getName());
                        startA = true;
                        startB = false;
                        startC = false;
                        o.notifyAll();
                        i++;
                    } else {
                        try {
                            o.wait();
                        } catch(InterruptedException e) { e.printStackTrace(); }}}}},"C").start(); }}Copy the code

The result is as follows:

ABCABCABCABCABCABCABCABCABCABC
Process finished with exit code 0
Copy the code

3, use Lock and Condition implementation

Synchronized locking is a bit complicated to write, so why not ReentrantLock? This is a Java. Util. Concurrent. The locks lock under the package implementation class, it has more flexible API, can achieve finer control of multithreaded execution process, especially in the case of matching Condition using, can follow one’s inclinationsly control multiple threads of execution order, Let’s see how this combination is used in this example:

public class PrintABC {
  public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
    	Create three conditions using ReentrantLock's newCondition() method
    	// Thread A, thread B, thread C
        Condition conditionA = lock.newCondition();
        Condition conditionB = lock.newCondition();
        Condition conditionC = lock.newCondition();

    		/ / A thread
        new Thread(() -> {
            try {
                lock.lock();
                for (int i = 0; i < 10; i++) {
                    System.out.print(Thread.currentThread().getName());
                    // Wake up thread B
                    conditionB.signal();
                    // This thread is blocked
                    conditionA.await();
                }
              	// There is a pit, remember to call signal() after the loop, otherwise the thread may stay in
              	// In wait state, the program cannot finish
                conditionB.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
              	// Call the unlock method in the finally blocklock.unlock(); }},"A").start();
    	/ / thread B
        new Thread(() -> {
            try {
                lock.lock();
                for (int i = 0; i < 10; i++) {
                    System.out.print(Thread.currentThread().getName());
                    conditionC.signal();
                    conditionB.await();
                }
                conditionC.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally{ lock.unlock(); }},"B").start();
    	/ / C thread
        new Thread(() -> {
            try {
                lock.lock();
                for (int i = 0; i < 10; i++) {
                    System.out.print(Thread.currentThread().getName());
                    conditionA.signal();
                    conditionC.await();
                }
                conditionA.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally{ lock.unlock(); }},"C").start(); }}Copy the code

The result is as follows:

ABCABCABCABCABCABCABCABCABCABC
Process finished with exit code 0
Copy the code

4, use Semaphore implementation

Semaphore (Chinese for semaphore) is an operating system concept, but there is also a semaphore class in JUC that can be used to control the number of concurrent threads. Permitting int permits Semaphore constructor with permitting int permits Semaphore constructor with permitting int permits

public Semaphore(int permits) {... }Copy the code

Permits refers to the number of permits that can be allocated to the Semaphore object. A Semaphore object in a thread can call acquire() method to enable the thread to obtain permission and continue to run. At the same time, the number of permits of the object is reduced by one. The Semaphore object calls the Release () method to release the permissions, incremented by one. Talk is cheap, show me the code!

public class PrintABC {
  
  	public static void main(String[] args) {
      	// If the number of initialization permissions is 1, thread A can execute first
        Semaphore semaphoreA  = new Semaphore(1);
      	// The number of initialization permissions is 0, and thread B is blocked
        Semaphore semaphoreB  = new Semaphore(0);
      	// The number of initialization permissions is 0, the C thread is blocked
        Semaphore semaphoreC  = new Semaphore(0);

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    // A thread is granted permission and semaphoreA is reduced to 0 for the next loop
                    // thread A will block until another thread executes semaphorea.release ();
                    semaphoreA.acquire();
                    // Prints the current thread name
                    System.out.print(Thread.currentThread().getName());
                    // semaphoreB license number + 1
                    semaphoreB.release();
                } catch(InterruptedException e) { e.printStackTrace(); }}},"A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    semaphoreB.acquire();
                    System.out.print(Thread.currentThread().getName());
                    semaphoreC.release();
                } catch(InterruptedException e) { e.printStackTrace(); }}},"B").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    semaphoreC.acquire();
                    System.out.print(Thread.currentThread().getName());
                    semaphoreA.release();
                } catch(InterruptedException e) { e.printStackTrace(); }}},"C").start(); }}Copy the code

The result is as follows:

ABCABCABCABCABCABCABCABCABCABC
Process finished with exit code 0
Copy the code

5, summary

This article introduces a total of four three thread alternate printing methods, the first method is the most simple to understand, but more can examine the multithreaded programming skills should be the second and third method, in the interview is also more points. Want to master these a few kinds of methods only and understand thoroughly, encounter this kind of question model later need not panic.