最近看到一道线程协同的阿里面试题。
代码语言:javascript复制(JAVA)有3 个独立的线程,一个只会输出A,一个只会输出L,一个只会输出I。
在三个线程同时启动的情况下,请用合理的方式让他们按顺序打印ALIALI。
三个线程开始正常输出后,主线程若检测到用户任意的输入则停止三个打印线程的工作,整体退出。
这个题目实际上是在考察线程间协调。鉴于前面学习的线程间通信的三种方法,现在用三种方法来完成该问题。
1.synchronized 配合 wait/notify
首先用最经典的synchronized来实现。
代码语言:javascript复制import java.util.Scanner;
import java.util.concurrent.TimeUnit;
public class ThreeThreadTest {
private final static Object lock = new Object();
private static volatile int count = 0;
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
try {
while (true) {
synchronized (lock) {
lock.notifyAll();
if (count % 3 == 0) {
System.out.println(Thread.currentThread().getName() ":A");
count ;
TimeUnit.MILLISECONDS.sleep(500);
}
lock.wait();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T1");
Thread t2 = new Thread(() -> {
try {
while (true) {
synchronized (lock) {
lock.notifyAll();
if (count % 3 == 1) {
System.out.println(Thread.currentThread().getName() ":L");
count ;
TimeUnit.MILLISECONDS.sleep(500);
}
lock.wait();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T2");
Thread t3 = new Thread(() -> {
try {
while (true) {
synchronized (lock) {
lock.notifyAll();
if (count % 3 == 2) {
System.out.println(Thread.currentThread().getName() ":I");
count ;
TimeUnit.MILLISECONDS.sleep(500);
}
lock.wait();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T3");
t1.setDaemon(true);
t2.setDaemon(true);
t3.setDaemon(true);
t1.start();
t2.start();
t3.start();
Scanner scan = new Scanner(System.in);
String input;
do{
input = scan.next();
}while (input == null);
System.out.println("exit");
}
}
由于需要在输入的时候退出主线程,那么现在将其他三个线程都设置为守护线程。
代码语言:javascript复制T1:A
T2:L
T3:I
T1:A
T2:L
T3:I
T1:A
T2:L
T3:I
T1:A
T2:L
T3:I
T1:A
T2:L
T3:I
1
exit
此时输入1,导致所有线程全部退出。
2.ReentrantLock & Condation
代码语言:javascript复制public class ThreeThreadTest {
private static final ReentrantLock lock = new ReentrantLock();
private static final Condition con1 = lock.newCondition();
private static final Condition con2 = lock.newCondition();
private static final Condition con3 = lock.newCondition();
private static int count = 0;
public static void main(String[] args) throws InterruptedException{
Thread t1 = new Thread(() -> {
lock.lock();
try {
while (true) {
if((count%3) == 0) {
System.out.println(Thread.currentThread().getName() ":A");
TimeUnit.MILLISECONDS.sleep(500);
con2.signal();
count ;
}else {
con1.await();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
});
Thread t2 = new Thread(() -> {
lock.lock();
try {
while (true) {
if((count%3) == 1) {
System.out.println(Thread.currentThread().getName() ":L");
TimeUnit.MILLISECONDS.sleep(500);
con3.signal();
count ;
}else {
con2.await();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
});
Thread t3 = new Thread(() -> {
lock.lock();
try {
while (true) {
if((count%3) == 2) {
System.out.println(Thread.currentThread().getName() ":I");
TimeUnit.MILLISECONDS.sleep(500);
con1.signal();
count ;
}else {
con3.await();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
});
t1.setDaemon(true);
t2.setDaemon(true);
t3.setDaemon(true);
t1.start();
TimeUnit.MILLISECONDS.sleep(100);
t2.start();
TimeUnit.MILLISECONDS.sleep(100);
t3.start();
Scanner scan = new Scanner(System.in);
String input;
do{
input = scan.next();
}while (input == null);
System.out.println("exit");
}
}
执行结果:
代码语言:javascript复制Thread-0:A
Thread-1:L
Thread-2:I
Thread-0:A
Thread-1:L
Thread-2:I
1
exit
同理也可以解决这个问题。
3.LockSupport
代码语言:javascript复制import java.util.Scanner;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
public class ThreeThreadTest {
private static boolean state = true;
public static void main(String[] args) throws InterruptedException{
Thread t1 = new Thread(() -> {
while (true) {
System.out.println(Thread.currentThread().getName() ":A");
LockSupport.park();
}
},"T1");
Thread t2 = new Thread(() -> {
while (true) {
System.out.println(Thread.currentThread().getName() ":L");
LockSupport.park();
}
},"T2");
Thread t3 = new Thread(() -> {
while (true) {
System.out.println(Thread.currentThread().getName() ":I");
LockSupport.park();
}
},"T3");
Thread t4 = new Thread(() -> {
int i=0;
try {
while (state) {
if((i%3)==0) {
LockSupport.unpark(t1);
}else if((i%3)==1) {
LockSupport.unpark(t2);
}else {
LockSupport.unpark(t3);
}
i ;
TimeUnit.MILLISECONDS.sleep(500);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t1.setDaemon(true);
t2.setDaemon(true);
t3.setDaemon(true);
t4.setDaemon(true);
t1.start();
TimeUnit.MILLISECONDS.sleep(100);
t2.start();
TimeUnit.MILLISECONDS.sleep(100);
t3.start();
TimeUnit.MILLISECONDS.sleep(100);
t4.start();
Scanner scan = new Scanner(System.in);
String input;
do{
input = scan.next();
}while (input == null);
}
}
上述代码输出结果:
代码语言:javascript复制T1:A
T2:L
T3:I
T1:A
T2:L
T3:I
T1:A
T2:L
1
可以看到使用LockSupport,不需要使用锁进行配合。 这是最灵活的一种方式。
4.总结
对于线程同步的问题,通过前面的学习,实际上有三种方式可以实现。分别是:
- synchronized wait notify/notifyAll
- ReentrantLock Condation:await/signal
- LockSupport:park / unpark 以上三种方式都可以实现同步,我们可以根据实际需求可以选择合适的方式来实现。