利用Java内置锁与并发包多线程交替打印字符串

最近遇到一个关于多线程的面试题感觉挺有意思的,题目是这样的:三个线程A,B,C 依次交替打印ABBCCCAAAA… 直到打印到长度为200停止,分别统计每个线程打印多少次.

乍看好像并不难,但是其实涉及到的问题还是比较多的,主要就是线程间的通信。首先我们思考一下单线程的写法:

        // 单线程执行
        int sizeA = 0;
        int sizeB = 0;
        int sizeC = 0;
        StringBuffer sb = new StringBuffer();
        int index = 1;
        while (sb.length() < 200) {
            Character c = null;
            if (index % 3 == 1) {
                c = 'A';
            } else if (index % 3 == 2) {
                c = 'B';
            } else if (index % 3 == 0) {
                c = 'C';
            }
            for (int i = 0; i < index; i++) {
                sb.append(c);
            }
            index++;
        }
        System.out.println(sb);

如果是三个线程去打印,我们需要线程间的通信,要三个线程以此去获取打印的机会。是利用%3来判断是否需要获取对象的锁。关键就是在这个对象上。我们定义一个共享对象:

class ObjectLock {
    // index属性来标记当前打印的次数
    private int index;
    // isPrint用来标记是否被打印
    private boolean isPrint;

    private HashMap<Character, Long> counterMap = new HashMap<Character, Long>();

    public int getIndex() {
        return index;
    }

    public void setIndex(int index) {
        this.index = index;
    }

    public boolean isPrint() {
        return isPrint;
    }

    public void setPrint(boolean isPrint) {
        this.isPrint = isPrint;
    }

    public HashMap<Character, Long> getCounterMap() {
        return counterMap;
    }

    public void setCounterMap(HashMap<Character, Long> counterMap) {
        this.counterMap = counterMap;
    }

}

然后多线程代码为:

        // 多线程执行
        final StringBuffer sb2 = new StringBuffer(); // 拼接的字符串,需要final修饰
        final ObjectLock objectLock = new ObjectLock(); // 这个是通信的锁
        objectLock.setIndex(1);

        // 启动三个线程
        new Thread(new Runnable() {

            public void run() {
                // 轮询查看是否轮到当前线程打印机会
                while (true) {
                    try {
                        Thread.currentThread().sleep(20);
                    } catch (InterruptedException e1) {
                        e1.printStackTrace();
                    }
                    if (objectLock.getIndex() % 3 == 1) {
                        // 如果当前打印的次数%3 == 1,此线程获取锁,得到打印机会
                        synchronized (objectLock) {
                            // 唤醒其他打印线程
                            objectLock.notifyAll();
                            System.out.println(Thread.currentThread().getName() + " 加锁对象");
                            try {
                                for (int i = 0; i < objectLock.getIndex(); i++) {
                                    sb2.append('A');
                                }
                                System.out.println(sb2);
                                // 设置标记,避免重复打印
                                objectLock.setPrint(true);
                                // 设置计数
                                Map<Character, Long> map = objectLock.getCounterMap();
                                Long count = map.get('A');
                                if (count == null) {
                                    count = 0l;
                                }
                                count += 1l;
                                map.put('A', count);
                                objectLock.wait(); // 释放锁
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }

                        }
                    }
                }

            }

        }, "thread--A").start();
        new Thread(new Runnable() {

            public void run() {
                while (true) {
                    try {
                        Thread.currentThread().sleep(20);
                    } catch (InterruptedException e1) {
                        e1.printStackTrace();
                    }
                    if (objectLock.getIndex() % 3 == 2) {
                        synchronized (objectLock) {
                            objectLock.notifyAll();
                            System.out.println(Thread.currentThread().getName() + " 加锁对象");
                            try {
                                Thread.currentThread().sleep(1000);
                                for (int i = 0; i < objectLock.getIndex(); i++) {
                                    sb2.append('B');
                                }
                                System.out.println(sb2);
                                objectLock.setPrint(true);

                                Map<Character, Long> map = objectLock.getCounterMap();
                                Long count = map.get('B');
                                if (count == null) {
                                    count = 0l;
                                }
                                count += 1l;
                                map.put('B', count);
                                objectLock.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }

                        }
                    }
                }
            }

        }, "thread--B").start();
        new Thread(new Runnable() {

            public void run() {
                while (true) {
                    try {
                        Thread.currentThread().sleep(20);
                    } catch (InterruptedException e1) {
                        e1.printStackTrace();
                    }
                    if (objectLock.getIndex() % 3 == 0) {
                        synchronized (objectLock) {
                            objectLock.notifyAll();
                            System.out.println(Thread.currentThread().getName() + " 加锁对象");
                            try {
                                Thread.currentThread().sleep(1000);
                                for (int i = 0; i < objectLock.getIndex(); i++) {
                                    sb2.append('C');
                                }
                                System.out.println(sb2);
                                objectLock.setPrint(true);

                                Map<Character, Long> map = objectLock.getCounterMap();
                                Long count = map.get('C');
                                if (count == null) {
                                    count = 0l;
                                }
                                count += 1l;
                                map.put('C', count);
                                objectLock.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }

                        }
                    }
                }
            }

        }, "thread--C").start();
        while (sb2.length() <= 200) {
            try {
                Thread.currentThread().sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (objectLock.isPrint()) {
                objectLock.setIndex(objectLock.getIndex() + 1);
                objectLock.setPrint(false);

                System.out.println(objectLock.getCounterMap());
            }
        }

打印结果:

thread--A 加锁对象
A
{A=1}
thread--B 加锁对象
ABB
{A=1, B=1}
thread--C 加锁对象
ABBCCC
{A=1, B=1, C=1}
thread--A 加锁对象
ABBCCCAAAA
{A=2, B=1, C=1}
thread--B 加锁对象
ABBCCCAAAABBBBB
{A=2, B=2, C=1}
thread--C 加锁对象
ABBCCCAAAABBBBBCCCCCC
{A=2, B=2, C=2}
thread--A 加锁对象
ABBCCCAAAABBBBBCCCCCCAAAAAAA
thread--B 加锁对象
{A=3, B=2, C=2}
ABBCCCAAAABBBBBCCCCCCAAAAAAABBBBBBBB
....

下面采用并发包:

class PrintChar implements Runnable {
    private StringBuffer sb = new StringBuffer();
    private ReentrantLock lock = new ReentrantLock();
    private Condition aThreadCondition = lock.newCondition(); // 重入锁条件
    private Condition bThreadCondition = lock.newCondition();
    private Condition cThreadCondition = lock.newCondition();
    private int status; // 状态0-A 线程打印, 状态1-B 线程打印, 状态2-B 线程打印
    private int index = 1;

    @Override
    public void run() {
        while (sb.length() < 200) {
            try {
                lock.lock(); 
                try {
                    Thread.currentThread().sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (status == 0) {
                    System.out.println(Thread.currentThread().getName() + " 获得打印机会 ");
                    for (int i = 0;i < index;i ++) {
                        sb.append("A");
                    }
                    System.out.println("打印完毕 : " + sb);
                    index ++;
                    bThreadCondition.signal();
                    status = 1;
                    aThreadCondition.await();
                } 

                if (status == 1) {
                    System.out.println(Thread.currentThread().getName() + " 获得打印机会 ");
                    for (int i = 0;i < index;i ++) {
                        sb.append("B");
                    }
                    System.out.println("打印完毕 : " + sb);
                    index ++;
                    cThreadCondition.signal();
                    status = 2;
                    bThreadCondition.await();
                } 

                if (status == 2) {
                    System.out.println(Thread.currentThread().getName() + " 获得打印机会 ");
                    for (int i = 0;i < index;i ++) {
                        sb.append("C");
                    }
                    System.out.println("打印完毕 : " + sb);
                    index ++;
                    aThreadCondition.signal();
                    status = 0;
                    cThreadCondition.await();
                } 

            } catch (Exception ex) {
                ex.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }

}

测试:

        PrintChar printChar = new PrintChar();
        new Thread(printChar,"A-thread").start();
        new Thread(printChar,"B-thread").start();
        new Thread(printChar,"C-thread").start();

执行结果:

B-thread 获得打印机会 
打印完毕 : A
A-thread 获得打印机会 
打印完毕 : ABB
C-thread 获得打印机会 
打印完毕 : ABBCCC
B-thread 获得打印机会 
打印完毕 : ABBCCCAAAA
A-thread 获得打印机会 
打印完毕 : ABBCCCAAAABBBBB
C-thread 获得打印机会 
打印完毕 : ABBCCCAAAABBBBBCCCCCC
B-thread 获得打印机会 
打印完毕 : ABBCCCAAAABBBBBCCCCCCAAAAAAA
A-thread 获得打印机会 
打印完毕 : ABBCCCAAAABBBBBCCCCCCAAAAAAABBBBBBBB
C-thread 获得打印机会 
打印完毕 : ABBCCCAAAABBBBBCCCCCCAAAAAAABBBBBBBBCCCCCCCCC
B-thread 获得打印机会 
打印完毕 : ABBCCCAAAABBBBBCCCCCCAAAAAAABBBBBBBBCCCCCCCCCAAAAAAAAAA
A-thread 获得打印机会 
打印完毕 : ABBCCCAAAABBBBBCCCCCCAAAAAAABBBBBBBBCCCCCCCCCAAAAAAAAAABBBBBBBBBBB
C-thread 获得打印机会 
打印完毕 : ABBCCCAAAABBBBBCCCCCCAAAAAAABBBBBBBBCCCCCCCCCAAAAAAAAAABBBBBBBBBBBCCCCCCCCCCCC
B-thread 获得打印机会 
打印完毕 : ABBCCCAAAABBBBBCCCCCCAAAAAAABBBBBBBBCCCCCCCCCAAAAAAAAAABBBBBBBBBBBCCCCCCCCCCCCAAAAAAAAAAAAA
A-thread 获得打印机会 
打印完毕 : ABBCCCAAAABBBBBCCCCCCAAAAAAABBBBBBBBCCCCCCCCCAAAAAAAAAABBBBBBBBBBBCCCCCCCCCCCCAAAAAAAAAAAAABBBBBBBBBBBBBB
C-thread 获得打印机会 
打印完毕 : ABBCCCAAAABBBBBCCCCCCAAAAAAABBBBBBBBCCCCCCCCCAAAAAAAAAABBBBBBBBBBBCCCCCCCCCCCCAAAAAAAAAAAAABBBBBBBBBBBBBBCCCCCCCCCCCCCCC
B-thread 获得打印机会 
打印完毕 : ABBCCCAAAABBBBBCCCCCCAAAAAAABBBBBBBBCCCCCCCCCAAAAAAAAAABBBBBBBBBBBCCCCCCCCCCCCAAAAAAAAAAAAABBBBBBBBBBBBBBCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAA
A-thread 获得打印机会 
打印完毕 : ABBCCCAAAABBBBBCCCCCCAAAAAAABBBBBBBBCCCCCCCCCAAAAAAAAAABBBBBBBBBBBCCCCCCCCCCCCAAAAAAAAAAAAABBBBBBBBBBBBBBCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBB
C-thread 获得打印机会 
打印完毕 : ABBCCCAAAABBBBBCCCCCCAAAAAAABBBBBBBBCCCCCCCCCAAAAAAAAAABBBBBBBBBBBCCCCCCCCCCCCAAAAAAAAAAAAABBBBBBBBBBBBBBCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCC
B-thread 获得打印机会 
打印完毕 : ABBCCCAAAABBBBBCCCCCCAAAAAAABBBBBBBBCCCCCCCCCAAAAAAAAAABBBBBBBBBBBCCCCCCCCCCCCAAAAAAAAAAAAABBBBBBBBBBBBBBCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAAAA
A-thread 获得打印机会 
打印完毕 : ABBCCCAAAABBBBBCCCCCCAAAAAAABBBBBBBBCCCCCCCCCAAAAAAAAAABBBBBBBBBBBCCCCCCCCCCCCAAAAAAAAAAAAABBBBBBBBBBBBBBCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBB
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值