zoukankan      html  css  js  c++  java
  • java多线程编程之连续打印abc的几种解法

    一道编程题如下:

    实例化三个线程,一个线程打印a,一个线程打印b,一个线程打印c,三个线程同时执行,要求打印出10个连着的abc。

    题目分析:

    通过题意我们可以得出,本题需要我们使用三个线程,三个线程分别会打印6次字符,关键是如何保证顺序一定是abc...呢。所以此题需要同步机制来解决问题!

    令打印字符A的线程为ThreadA,打印B的ThreadB,打印C的为ThreadC。问题为三线程间的同步唤醒操作,主要的目的就是使程序按ThreadA->ThreadB->ThreadC->ThreadA循环执行三个线程,因此本人整理出了三种方式来解决此问题。

    一、通过两个锁(不推荐,可读性和安全性比较差)

    package com.demo.test;
    
    /**
     * 基于两个lock实现连续打印abcabc....
     * @author lixiaoxi
     *
     */
    public class TwoLockPrinter implements Runnable {
    
        // 打印次数
        private static final int PRINT_COUNT = 10;
        // 前一个线程的打印锁
        private final Object fontLock;
        // 本线程的打印锁
        private final Object thisLock;
        // 打印字符
        private final char printChar;
    
        public TwoLockPrinter(Object fontLock, Object thisLock, char printChar) {
            this.fontLock = fontLock;
            this.thisLock = thisLock;
            this.printChar = printChar;
        }
    
        @Override
        public void run() {
            // 连续打印PRINT_COUNT次
            for (int i = 0; i < PRINT_COUNT; i++) {
                // 获取前一个线程的打印锁
                synchronized (fontLock) {
                    // 获取本线程的打印锁
                    synchronized (thisLock) {
                        //打印字符
                        System.out.print(printChar);
                        // 通过本线程的打印锁唤醒后面的线程 
                        // notify和notifyall均可,因为同一时刻只有一个线程在等待
                        thisLock.notify();
                    }
                    // 不是最后一次则通过fontLock等待被唤醒
                    // 必须要加判断,不然虽然能够打印10次,但10次后就会直接死锁
                    if(i < PRINT_COUNT - 1){
                        try {
                            // 通过fontLock等待被唤醒
                            fontLock.wait();
                            
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    
                }    
            }    
        }
    
        public static void main(String[] args) throws InterruptedException {
            // 打印A线程的锁
            Object lockA = new Object();
            // 打印B线程的锁
            Object lockB = new Object();
            // 打印C线程的锁
            Object lockC = new Object();
            
            // 打印a的线程
            Thread threadA = new Thread(new TwoLockPrinter(lockC, lockA, 'A'));
            // 打印b的线程
            Thread threadB = new Thread(new TwoLockPrinter(lockA, lockB, 'B'));
            // 打印c的线程
            Thread threadC = new Thread(new TwoLockPrinter(lockB, lockC, 'C'));
    
            // 依次开启a b c线程
            threadA.start();
            Thread.sleep(100); // 确保按顺序A、B、C执行
            threadB.start();
            Thread.sleep(100);
            threadC.start();
            Thread.sleep(100);
        }
    
    }

    打印结果:

    ABCABCABCABCABCABCABCABCABCABC

    分析:

        此解法为了为了确定唤醒、等待的顺序,每一个线程必须同时持有两个对象锁,才能继续执行。一个对象锁是fontLock,就是前一个线程所持有的对象锁,还有一个就是自身对象锁thisLock。主要的思想就是,为了控制执行的顺序,必须要先持有fontLock锁,也就是前一个线程要释放掉前一个线程自身的对象锁,当前线程再去申请自身对象锁,两者兼备时打印,之后首先调用thisLock.notify()释放自身对象锁,唤醒下一个等待线程,再调用fontLock.wait()释放prev对象锁,暂停当前线程,等待再次被唤醒后进入循环。运行上述代码,可以发现三个线程循环打印ABC,共10次。程序运行的主要过程就是A线程最先运行,持有C,A对象锁,后释放A锁,唤醒B。线程B等待A锁,再申请B锁,后打印B,再释放B锁,唤醒C,线程C等待B锁,再申请C锁,后打印C,再释放C锁,唤醒A。看起来似乎没什么问题,但如果你仔细想一下,就会发现有问题,就是初始条件,三个线程按照A,B,C的顺序来启动,按照前面的思考,A唤醒B,B唤醒C,C再唤醒A。但是这种假设依赖于JVM中线程调度、执行的顺序,所以需要手动控制他们三个的启动顺序,即Thread.Sleep(100)。

    二、通过一个ReentrantLock和三个conditon实现(推荐,安全性,性能和可读性较高)

    package com.demo.test;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * 基于一个ReentrantLock和三个conditon实现连续打印abcabc...
     * @author lixiaoxi
     *
     */
    public class RcSyncPrinter implements Runnable{
    
        // 打印次数
        private static final int PRINT_COUNT = 10;
        // 打印锁
        private final ReentrantLock reentrantLock;
        // 本线程打印所需的condition
        private final Condition thisCondtion;
        // 下一个线程打印所需要的condition
        private final Condition nextCondtion;
        // 打印字符
        private final char printChar;
    
        public RcSyncPrinter(ReentrantLock reentrantLock, Condition thisCondtion, Condition nextCondition, 
                char printChar) {
            this.reentrantLock = reentrantLock;
            this.nextCondtion = nextCondition;
            this.thisCondtion = thisCondtion;
            this.printChar = printChar;
        }
    
        @Override
        public void run() {
            // 获取打印锁 进入临界区
            reentrantLock.lock();
            try {
                // 连续打印PRINT_COUNT次
                for (int i = 0; i < PRINT_COUNT; i++) {
                    //打印字符
                    System.out.print(printChar);
                    // 使用nextCondition唤醒下一个线程
                    // 因为只有一个线程在等待,所以signal或者signalAll都可以
                    nextCondtion.signal();
                    // 不是最后一次则通过thisCondtion等待被唤醒
                    // 必须要加判断,不然虽然能够打印10次,但10次后就会直接死锁
                    if (i < PRINT_COUNT - 1) {
                        try {
                            // 本线程让出锁并等待唤醒
                            thisCondtion.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
    
                }
            } finally {
                // 释放打印锁
                reentrantLock.unlock();
            }
        }
        
        public static void main(String[] args) throws InterruptedException {
            // 写锁
            ReentrantLock lock = new ReentrantLock();
            // 打印a线程的condition
            Condition conditionA = lock.newCondition();
            // 打印b线程的condition
            Condition conditionB = lock.newCondition();
            // 打印c线程的condition
            Condition conditionC = lock.newCondition();
            // 实例化A线程
            Thread printerA = new Thread(new RcSyncPrinter(lock, conditionA, conditionB, 'A'));
            // 实例化B线程
            Thread printerB = new Thread(new RcSyncPrinter(lock, conditionB, conditionC, 'B'));
            // 实例化C线程
            Thread printerC = new Thread(new RcSyncPrinter(lock, conditionC, conditionA, 'C'));
            // 依次开始A B C线程
            printerA.start();
            Thread.sleep(100);
            printerB.start();
            Thread.sleep(100);
            printerC.start();
        }
    }

    打印结果:

    ABCABCABCABCABCABCABCABCABCABC

    分析:

        仔细想想本问题,既然同一时刻只能有一个线程打印字符,那我们为什么不使用一个同步锁ReentrantLock?线程之间的唤醒操作可以通过Condition实现,且Condition可以有多个,每个condition.await阻塞只能通过该condition的signal/signalall来唤醒!这是synchronized关键字所达不到的,那我们就可以给每个打印线程一个自身的condition和下一个线程的condition,每次打印字符后,调用下一个线程的condition.signal来唤醒下一个线程,然后自身再通过自己的condition.await来释放锁并等待唤醒。

    三、通过一个锁和一个状态变量来实现(推荐)

    package com.demo.test;
    
    /**
     * 基于一个锁和一个状态变量实现连续打印abcabc...
     * @author lixiaoxi
     *
     */
    public class StateLockPrinter {
        //状态变量
        private volatile int state=0;
        
        // 打印线程
        private class Printer implements Runnable {
            //打印次数
            private static final int PRINT_COUNT=10;
            //打印锁
            private final Object printLock;
            //打印标志位 和state变量相关
            private final int printFlag;
            //后继线程的线程的打印标志位,state变量相关
            private final int nextPrintFlag;
            //该线程的打印字符
            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() {
                //获取打印锁 进入临界区
                synchronized (printLock) {
                    //连续打印PRINT_COUNT次
                    for(int i=0;i<PRINT_COUNT;i++){
                        //循环检验标志位 每次都阻塞然后等待唤醒
                        while (state!=printFlag) {
                            try {
                                printLock.wait();
                            } catch (InterruptedException e) {
                                return;
                            }
                        }
                        //打印字符
                        System.out.print(printChar);
                        //设置状态变量为下一个线程的标志位
                        state=nextPrintFlag;
                        //注意要notifyall,不然会死锁,因为notify只通知一个,
                        //但是同时等待的是两个,如果唤醒的不是正确那个就会没人唤醒,死锁了
                        printLock.notifyAll();
                    }
                }
            }
            
        }
    
        public void test() throws InterruptedException{
            //
            Object lock=new Object();
            //打印A的线程
            Thread threadA=new Thread(new Printer(lock, 0,1, 'A'));
            //打印B的线程
            Thread threadB=new Thread(new Printer(lock, 1,2, 'B'));
            //打印C的线程
            Thread threadC=new Thread(new Printer(lock, 2,0, 'C'));
            //一次启动A B C线程
            threadA.start();
            Thread.sleep(1000);
            threadB.start();
            Thread.sleep(1000);
            threadC.start();
        }
        
        public static void main(String[] args) throws InterruptedException {
            
            StateLockPrinter print = new StateLockPrinter();
            print.test();
        }
       
    }

    打印结果:

    ABCABCABCABCABCABCABCABCABCABC

    分析:

        状态变量是一个volatile的整型变量,0代表打印a,1代表打印b,2代表打印c,三个线程都循环检验标志位,通过阻塞前和阻塞后两次判断可以确保当前打印的正确顺序,随后线程打印字符,然后设置下一个状态字符,唤醒其它线程,然后重新进入循环。 

    补充题

    三个Java多线程循环打印递增的数字,每个线程打印5个数值,打印周期1-75,同样的解法:

    package com.demo.test;
    
    import java.util.concurrent.atomic.AtomicInteger;
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * 数字打印,三个线程同时打印数字,第一个线程打印12345,第二个线程打印678910 .........
     * @author lixiaoxi
     *
     */
    public class NumberPrinter {
    
        //打印计数器
        private final AtomicInteger counter=new AtomicInteger(0);
        
        private class Printer implements Runnable{
            //总共需要打印TOTAL_PRINT_COUNT次
            private static final int TOTAL_PRINT_COUNT = 5;
            //每次打印PER_PRINT_COUNT次
            private static final int PER_PRINT_COUNT = 5;
            //打印锁
            private final ReentrantLock reentrantLock;
            //前一个线程的condition
            private final Condition afterCondition;
            //本线程的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() {
                //进入临界区
                reentrantLock.lock();
                try {
                    //循环打印TOTAL_PRINT_COUNT次
                    for(int i=0;i<TOTAL_PRINT_COUNT;i++){
                        //打印操作
                        for(int j=0;j<PER_PRINT_COUNT;j++){
                            //以原子方式将当前值加 1。
                            //incrementAndGet返回的是新值(即加1后的值)
                            System.out.println(counter.incrementAndGet());
                        }
                        //通过afterCondition通知后面线程
                        afterCondition.signalAll();
                        if(i < TOTAL_PRINT_COUNT - 1){
                            try {
                                //本线程释放锁并等待唤醒
                                thisCondtion.await();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                } finally {
                    reentrantLock.unlock();
                }
            }
        }
        
        public void test() throws InterruptedException {
            //打印锁
            ReentrantLock reentrantLock=new ReentrantLock();
            //打印A线程的Condition
            Condition conditionA=reentrantLock.newCondition();
            //打印B线程的Condition
            Condition conditionB=reentrantLock.newCondition();
            //打印C线程的Condition
            Condition conditionC=reentrantLock.newCondition();
    
            //打印线程A
            Thread threadA=new Thread(new Printer(reentrantLock,conditionA, conditionB));
            //打印线程B
            Thread threadB=new Thread(new Printer(reentrantLock, conditionB, conditionC));
            //打印线程C
            Thread threadC=new Thread(new Printer(reentrantLock, conditionC, conditionA));
            // 依次开启a b c线程
            threadA.start();
            Thread.sleep(100);
            threadB.start();
            Thread.sleep(100);
            threadC.start();
        }
        
        public static void main(String[] args) throws InterruptedException {
            NumberPrinter print = new NumberPrinter();
            print.test();
        }
    }

    运行结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
  • 相关阅读:
    POJ 2230 Watchcow 欧拉回路的DFS解法(模板题)
    POJ 2188 Cow Laundry
    ACM一年记,总结报告(希望自己可以走得很远)
    [USACO1.5]回文质数 Prime Palindromes
    POJ-1276-Cash Machine(多重背包)
    POJ-1322 Chocolate(概率DP)
    POJ-1179 Polygon (动态规划)
    HDU-1520 Anniversary party(树形DP)
    HDU-1054 Strategic Game(树形DP)
    POJ-1157 LITTLE SHOP OF FLOWERS(动态规划)
  • 原文地址:https://www.cnblogs.com/xiaoxi/p/8035725.html
Copyright © 2011-2022 走看看