那天面试,面试官让我写这么一道题,有三个线程ABC
,让我打印10
次ABC
。这题看起相当的简单,以至于我没写出来哈哈哈。而且那个狗屎在线编辑器想好好写代码是不可能的,因为我已经习惯了一套行云流水的快捷键,到在线编译器框框里面我感觉我就是原始人,无从下手。
好了,吐槽就这么多,这个题目还贴心的放了模板。
public class TestThread {
public static void main(String[] args) {
MABC mabc = new MABC();
Thread thread1 = new Thread(new ThreadAbc(mabc, 'A'));
Thread thread2 = new Thread(new ThreadAbc(mabc, 'B'));
Thread thread3 = new Thread(new ThreadAbc(mabc, 'C'));
thread1.start();
thread2.start();
thread3.start();
}
}
class MABC {
}
class ThreadAbc implements Runnable {
}
简单分析一下这个题的目,首先,是看代码规范吧,这个没得说的,其次就是考验多线程的应用,再就是这个ABC
怎么保证顺序,而且多个线程执行的是同一块代码。
话不多说,上代码吧
public class TestThread {
public static void main(String[] args) {
MABC mabc = new MABC();
Thread thread1 = new Thread(new ThreadAbc(mabc, 'A'));
Thread thread2 = new Thread(new ThreadAbc(mabc, 'B'));
Thread thread3 = new Thread(new ThreadAbc(mabc, 'C'));
thread1.start();
thread2.start();
thread3.start();
}
}
class MABC {
public int times = 10;
public int state = 1;
}
class ThreadAbc implements Runnable {
private MABC mabc;
private char c;
public ThreadAbc(MABC mabc, char c) {
this.mabc = mabc;
this.c = c;
}
@Override
public void run() {
out:
while (true) {
synchronized (mabc) {
/*循环跳出条件*/
if (mabc.times == 0) {
break out;
}
switch (c) {
case 'A': {
if (mabc.state == 1) {
System.out.print(c);
mabc.state = 2;
}
break;
}
case 'B': {
if (mabc.state == 2) {
System.out.print(c);
mabc.state = 3;
}
break;
}
case 'C': {
if (mabc.state == 3) {
System.out.print(c);
mabc.state = 1;
mabc.times--;
}
break;
}
default:
}
}
}
}
}
这样功能就实现了,注意了,这里Synchronized
代码块要是放到while循环外面会怎样?对的,会一直锁住这个线程哈。里面的赋值语句也可以这么写:
mabc.state = mabc.state%3+1;
面试官:这么写有什么问题没?我:功能都实现了能有啥问题?没问题。面试官:好的我们聊点别的。
想一想是有问题的哈,问题在于锁争抢,拿到锁了不等于万事大吉,有没有轮到你打印是另一回事呢,所以可以做些优化,如果当前线程不具备修改状态的资格,就wait()
释放锁把机会赶紧让给别人,避免白白的争抢锁,都没轮到你凑啥热闹啊你。对run()
方法做如下优化:
public void run() {
out:
while (true) {
synchronized (mabc) {
/*循环跳出条件*/
if (mabc.times == 0) {
mabc.notify();
break out;
}
try {
switch (c) {
case 'A': {
if (mabc.state == 1) {
System.out.print(c);
// mabc.state = 2;
mabc.state = mabc.state%3+1;
}
mabc.wait();
break;
}
case 'B': {
if (mabc.state == 2) {
System.out.print(c);
// mabc.state = 3;
mabc.state = mabc.state%3+1;
}
mabc.wait();
break;
}
case 'C': {
if (mabc.state == 3) {
System.out.print(c);
// mabc.state = 1;
mabc.state = mabc.state%3+1;
mabc.times--;
}
/*这里为什么写法和上面不一样?*/
mabc.notify();
break;
}
default:
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
这里肯定有些疑问的,比如循环跳出条件为什么要notify()
?为什么case 'C'
不wait()
只notify()
?
首先说循环跳出条件为什么要notify()
,因为我们线程公用了这段代码,里面有wait()
,那么就意味着某个线程wait()
之后其他线程修改状态让while
可以跳出,但是这个wait()
的线程却没办法知道了,所以需要其他人来唤醒。
为什么case 'C'
不wait()
只notify()
,因为咱们得留一个线程来唤醒别人,另外notify()
不像Reentrantlock
这样可以多条件绑定,他不能唤醒指定的线程,也就是说唤醒的线程可能还是不满足条件的,这也是为啥咱们在mabc
类里面要定义一个状态字段了。
既然已经已经提到了Reentrantlock
的多条件绑定,那么一定有方法解决锁争抢问题。解决的思路是这样,我们给每个线程加上一个条件ConditionA、B、C
,A
执行完了就赶紧去叫B
,B
完了就取叫C
,C
完了叫A
,以此循环往复。
public class TestThread {
public static ReentrantLock lock = new ReentrantLock();
public static Condition conditionA = lock.newCondition();
public static Condition conditionB = lock.newCondition();
public static Condition conditionC = lock.newCondition();
public static void main(String[] args) {
MABC mabc = new MABC();
Thread thread1 = new Thread(new ThreadAbc(mabc, 'A'));
Thread thread2 = new Thread(new ThreadAbc(mabc, 'B'));
Thread thread3 = new Thread(new ThreadAbc(mabc, 'C'));
thread1.start();
thread2.start();
thread3.start();
}
}
class MABC {
public int times = 10;
public int state = 1;
}
class ThreadAbc implements Runnable {
private MABC mabc;
private char c;
public ThreadAbc(MABC mabc, char c) {
this.mabc = mabc;
this.c = c;
}
public void run() {
out:
while (true) {
TestThread.lock.lock();
System.out.println("\nthread"+c+":"+mabc.state+":"+mabc.times);
/*循环跳出条件*/
if (mabc.times == 0) {
TestThread.conditionA.signal();
TestThread.conditionB.signal();
TestThread.conditionC.signal();
TestThread.lock.unlock();
break out;
}
try {
switch (c) {
case 'A': {
System.out.print(c);
TestThread.conditionB.signal();
TestThread.conditionA.await();
break;
}
case 'B': {
System.out.print(c);
TestThread.conditionC.signal();
TestThread.conditionB.await();
break;
}
case 'C': {
System.out.print(c);
mabc.times--;
TestThread.conditionA.signal();
TestThread.conditionC.await();
break;
}
default:
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
TestThread.lock.unlock();
}
}
}
}
这就相当的简单了,A
说完事了,老哥B
你开始吧,该我了叫我,我先休息了。B
开始了同时也给C
说,C
也这样跟A
说,就这默契的配合把活儿干完了。
最后,不知道细心的大家有没有发现这段代码:
out:
while (true) {
synchronized (mabc) {
/*循环跳出条件*/
if (mabc.times == 0) {
break out;
}
....
其实就是Java
的goto
哈,叫做标签Label
,如果你在多层的if
里面想要一步跳出去,也可以这样break : lable
来结束的。