最近遇到一个关于多线程的面试题感觉挺有意思的,题目是这样的:三个线程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