Several Solutions to the Continuous Printing abc of java Multithread Programming Problem

Keywords: Java Programming jvm

A programming problem is as follows:

Instantiate three threads, one thread prints a, one prints b, one prints c, three threads execute at the same time, and require six connected ABCs to be printed.

Topic analysis:

From the meaning of the question, we can conclude that we need to use three threads, three threads will print six characters, the key is how to ensure that the order must be abc.. So this problem needs synchronization mechanism to solve the problem!

Let the thread of printing character A be ThreadA, ThreadB of printing B and ThreadC of printing C. The problem is the synchronous wake-up operation among threads. The main purpose is to make the program press ThreadA - > ThreadB - > ThreadC.-

> ThreadA loop executes three threads, so I sorted out three ways to solve this problem.

1. Through two locks (not recommended, poor readability and security)

/**
 * Continuous printing of abcabc is realized based on two lock s.
 * @author fhr
 * @since 2017/09/04
 */
public class TwoLockPrinter {

    @Test
    public void test() throws InterruptedException {
        // Printing A Thread lock
        Object lockA = new Object();
        // Printing B Thread lock
        Object lockB = new Object();
        // Printing C Thread lock
        Object lockC = new Object();
        ThreadGroup group = new ThreadGroup("xx");
        // Printing a Threads
        Thread threadA = new Thread(group, new Printer(lockC, lockA, 'A'));
        // Printing b Threads
        Thread threadB = new Thread(group, new Printer(lockA, lockB, 'B'));
        // Printing c Threads
        Thread threadC = new Thread(group, new Printer(lockB, lockC, 'C'));
        // Turn on a b c thread
        threadA.start();
        Thread.sleep(100);
        threadB.start();
        Thread.sleep(100);
        threadC.start();
        // Main Thread Loop Delivery cpu Usufruct
        while (group.activeCount() > 0) {
            Thread.yield();
        }
    }

    // Print thread
    private class Printer implements Runnable {
        // Printing times
        private static final int PRINT_COUNT = 6;
        // Print locks for the previous thread
        private final Object fontLock;
        // Print lock for this thread
        private final Object thisLock;
        // Printing character
        private final char printChar;

        public Printer(Object fontLock, Object thisLock, char printChar) {
            super();
            this.fontLock = fontLock;
            this.thisLock = thisLock;
            this.printChar = printChar;
        }
        @Override
        public void run() {
            // Continuous printing PRINT_COUNT second
            for (int i = 0; i < PRINT_COUNT; i++) {
                // Get the print lock of the previous thread
                synchronized (fontLock) {
                    // Get the print lock for this thread
                    synchronized (thisLock) {
                        // Printing character
                        System.out.print(printChar);
                        // Wake up the following thread through the print lock of this thread 
                        // notify and notifyall All possible,Because there is only one thread waiting at the same time.
                        thisLock.notify();
                        // Not for the last time fontLock Waiting to be awakened
                        // It must be judged, or it can be printed six times, but after six times it will be deadlocked directly.
                        if (i < PRINT_COUNT - 1) {
                            try {
                                // adopt fontLock Waiting to be awakened
                                fontLock.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }
    }
}

In order to determine the order of wake-up and wait, each thread must hold two object locks at the same time in order to continue execution. One object lock is fontLock, which is the object lock held by the previous thread, and the other is itself.

Object lock thisLock. The main idea is that in order to control the order of execution, fontLock locks must be held first, that is, the former thread should release the object locks of the former thread itself, and the current thread should apply for its own object locks.

After that, it first calls thisLock.notify() to release its own object lock, wake up the next waiting thread, and then calls fontLock.wait() to release the prev object lock, pause the current thread, and wait to be waked up again before entering the loop. transport

Line the above code, you can find three threads printing ABC circularly, a total of six times. The main process of program running is that thread A runs first, holds C and A object locks, then releases lock A and wakes up B. Thread B waits for lock A, applies for lock B, and then types

Print B, release B lock, wake up C, thread C waits for B lock, then apply for C lock, print C, then release C lock, wake up A. It doesn't seem to be a problem, but if you think about it carefully, you'll find that the problem is the initial bar.

Three threads start in the order of A, B and C. According to the previous thinking, A wakes B, B wakes C, C wakes A again. But this assumption depends on the order in which threads are scheduled and executed in the JVM, so they need to be controlled manually.

Start order, Thread.Sleep(100)

2. Through a ReentrantLock and three conditon s (recommendation, security, performance and readability are high)

/**
 * Continuous printing of abcabc based on one ReentrantLock and three conditon s is realized.
 * @author fhr
 * @since 2017/09/04
 */
public class RcSyncPrinter {

    @Test
    public void test() throws InterruptedException {
        ThreadGroup group = new ThreadGroup("xx");
        // Write lock
        ReentrantLock lock = new ReentrantLock();
        // Printing a Thread condition
        Condition conditionA = lock.newCondition();
        // Printing b Thread condition
        Condition conditionB = lock.newCondition();
        // Printing c Thread condition
        Condition conditionC = lock.newCondition();
        // instantiation A thread
        Thread printerA = new Thread(group, new Printer(lock, conditionA, conditionB, 'A'));
        // instantiation B thread
        Thread printerB = new Thread(group, new Printer(lock, conditionB, conditionC, 'B'));
        // instantiation C thread
        Thread printerC = new Thread(group, new Printer(lock, conditionC, conditionA, 'C'));
        // Start in turn A B C thread
        printerA.start();
        Thread.sleep(100);
        printerB.start();
        Thread.sleep(100);
        printerC.start();
        // Main Thread Loop Delivery CPU Usufruct
        while (group.activeCount() > 0) {
            Thread.yield();
        }
    }

    // Print thread
    private class Printer implements Runnable {
        // Printing times
        private static final int PRINT_COUNT = 6;
        // Print lock
        private final ReentrantLock reentrantLock;
        // What this thread needs to print condition
        private final Condition thisCondtion;
        // Next thread printing required condition
        private final Condition nextCondtion;
        // Printing character
        private final char printChar;

        public Printer(ReentrantLock reentrantLock, Condition thisCondtion, Condition nextCondition, char printChar) {
            this.reentrantLock = reentrantLock;
            this.nextCondtion = nextCondition;
            this.thisCondtion = thisCondtion;
            this.printChar = printChar;
        }

        @Override
        public void run() {
            // Get Print Lock into Critical Zone
            reentrantLock.lock();
            try {
                // Continuous printing PRINT_COUNT second
                for (int i = 0; i < PRINT_COUNT; i++) {
                    System.out.print(printChar);
                    // Use nextCondition Wake up the next thread
                    // Because there is only one thread waiting, so signal perhaps signalAll Fine
                    nextCondtion.signal();
                    // Not for the last time thisCondtion Waiting to be awakened
                    // It must be judged, or it can be printed six times, but after six times it will be deadlocked directly.
                    if (i < PRINT_COUNT - 1) {
                        try {
                            // This thread releases the lock and waits for wake-up
                            thisCondtion.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }

                }
            } finally {
                // Release Print Lock
                reentrantLock.unlock();
            }
        }
    }
}

Consider this question carefully. Since only one thread can print characters at the same time, why don't we use a synchronous lock ReentrantLock? Wake-up operations between threads can be implemented through Conditions, and Conditions can have

Multiple, each condition.await block can only be awakened by the condition's signal/signalall! This is not achieved by the synchronized keyword, so we can give each print thread its own condition and the following

Each time a character is printed, the condition.signal of the next thread is called to wake up the next thread, and then it releases the lock itself through its condition.await and waits to wake up.

3. Implement by a lock and a state variable (recommended)

/**
 * Achieve continuous printing of abcabc based on a lock and a state variable.
 * @author fhr
 * @since 2017/09/04
 */
public class StateLockPrinter {
    //state variable
    private volatile int state=0;
    @Test
    public void test() throws InterruptedException {
        //lock
        Object lock=new Object();
        ThreadGroup group=new ThreadGroup("xx");
        //Printing A Threads
        Thread threadA=new Thread(group,new Printer(lock, 0,1, 'A'));
        //Printing B Threads
        Thread threadB=new Thread(group,new Printer(lock, 1,2, 'B'));
        //Printing C Threads
        Thread threadC=new Thread(group,new Printer(lock, 2,0, 'C'));
        //One start A B C thread
        threadA.start();
        Thread.sleep(1000);
        threadB.start();
        Thread.sleep(1000);
        threadC.start();
        //Loop checks the number of live threads in a thread combination
        while (group.activeCount()>0) {
            //Give up CPU Usufruct
            Thread.yield();
        }
    }
    //Print thread
    private class Printer implements Runnable{
        //Printing times
        private static final int PRINT_COUNT=6;
        //Print lock
        private final Object printLock;
        //Print label and state Variable correlation
        private final int printFlag;
        //Print flags for threads of successor threads, state Variable correlation
        private final int nextPrintFlag;
        //Print characters for this thread
        private final char printChar;
        public Printer(Object printLock, int printFlag,int nextPrintFlag, char printChar) {
            super();
            this.printLock = printLock;
            this.printFlag=printFlag;
            this.nextPrintFlag=nextPrintFlag;
            this.printChar = printChar;
        }

        @Override
        public void run() {
            //Get Print Lock into Critical Zone
            synchronized (printLock) {
                    //Continuous printing PRINT_COUNT second
                    for(int i=0;i<PRINT_COUNT;i++){
                        //Cyclic checkpoint blocks every time and waits for wake-up
                        while (state!=printFlag) {
                            try {
                                printLock.wait();
                            } catch (InterruptedException e) {
                                return;
                            }
                        }
                        //Printing character
                        System.out.print(printChar);
                        //Set the status variable as the flag bit for the next thread
                        state=nextPrintFlag;
                        //Pay attention to notifyall,Otherwise it will be deadlocked because notify Notify only one.
                        //But there are two waiting at the same time.,If the wake-up is not correct, no one will wake up, deadlock.
                        printLock.notifyAll();
                    }
            }
        }
    }
}

The state variable is an integral variable of volatile, 0 represents print a,1 represents print b,2 represents print c. All three threads check the flag bit circularly. By judging before and after blocking, the correct order of current printing can be ensured, and then the threads.

Print the character, then set the next status character, wake up other threads, and re-enter the loop.  

Supplementary questions

Three Java multithreaded loops print incremental numbers, each thread prints five values, printing cycle 1-75, the same solution:

/**
 * Digital printing. Three threads print numbers at the same time.
 * The first thread prints 12345, and the second thread prints 678910...
 * @author fhr
 * @since 2017/09/04
 */
public class NumberPrinter {
    //print counter
    private final AtomicInteger counter=new AtomicInteger(0);
    
    @Test
    public void test() throws InterruptedException {
        //Print lock
        ReentrantLock reentrantLock=new ReentrantLock();
        //Printing A Thread Condition
        Condition conditionA=reentrantLock.newCondition();
        //Printing B Thread Condition
        Condition conditionB=reentrantLock.newCondition();
        //Printing C Thread Condition
        Condition conditionC=reentrantLock.newCondition();
        ThreadGroup group=new ThreadGroup("xx");
        //Print thread A
        Thread threadA=new Thread(group,new Printer(reentrantLock,conditionA, conditionB));
        //Print thread B
        Thread threadB=new Thread(group,new Printer(reentrantLock, conditionB, conditionC));
        //Print thread C
        Thread threadC=new Thread(group,new Printer(reentrantLock, conditionC, conditionA));
        // Turn on a b c thread
        threadA.start();
        Thread.sleep(100);
        threadB.start();
        Thread.sleep(100);
        threadC.start();
        while (group.activeCount()>0) {
            Thread.yield();
        }
    }
    
    private class Printer implements Runnable{
        //Total Printing Requirements TOTAL_PRINT_COUNT second
        private static final int TOTAL_PRINT_COUNT=5;
        //Each time printing PER_PRINT_COUNT second
        private static final int PER_PRINT_COUNT=5;
        //Print lock
        private final ReentrantLock reentrantLock;
        //Of the previous thread condition
        private final Condition afterCondition;
        //This thread condition
        private final Condition thisCondtion;
        
        public Printer(ReentrantLock reentrantLock, Condition thisCondtion,Condition afterCondition) {
            super();
            this.reentrantLock = reentrantLock;
            this.afterCondition = afterCondition;
            this.thisCondtion = thisCondtion;
        }

        @Override
        public void run() {
            //Enter the critical zone
            reentrantLock.lock();
            try {
                //Loop printing TOTAL_PRINT_COUNT second
                for(int i=0;i<TOTAL_PRINT_COUNT;i++){
                    //Print operation
                    for(int j=0;j<PER_PRINT_COUNT;j++){
                        System.out.println(counter.incrementAndGet());
                    }
                    //adopt afterCondition Notify the subsequent thread
                    afterCondition.signalAll();
                    if(i<=TOTAL_PRINT_COUNT-1){
                        try {
                            //This thread releases the lock and waits for wake-up
                            thisCondtion.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            } finally {
                reentrantLock.unlock();
            }
        }
    }
}

Posted by renob on Mon, 27 May 2019 13:08:14 -0700