一道多线程协同面试题的三种解决方案

2020-09-16 11:19:41 浏览数 (1)

最近看到一道线程协同的阿里面试题。

代码语言: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 以上三种方式都可以实现同步,我们可以根据实际需求可以选择合适的方式来实现。

0 人点赞