专栏首页做不甩锅的后端一道多线程协同面试题的三种解决方案

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

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

(JAVA)有3 个独立的线程,一个只会输出A,一个只会输出L,一个只会输出I。
在三个线程同时启动的情况下,请用合理的方式让他们按顺序打印ALIALI。
三个线程开始正常输出后,主线程若检测到用户任意的输入则停止三个打印线程的工作,整体退出。

这个题目实际上是在考察线程间协调。鉴于前面学习的线程间通信的三种方法,现在用三种方法来完成该问题。

1.synchronized 配合 wait/notify

首先用最经典的synchronized来实现。

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");
		
	}
}

由于需要在输入的时候退出主线程,那么现在将其他三个线程都设置为守护线程。

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

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");
	}
}

执行结果:

Thread-0:A
Thread-1:L
Thread-2:I
Thread-0:A
Thread-1:L
Thread-2:I
1
exit

同理也可以解决这个问题。

3.LockSupport

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);
	}
}

上述代码输出结果:

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 条评论
登录 后参与评论

相关文章

  • String及StringTable(五):java8的新工具类StringJoiner

    在java8中,对于字符串拼接的操作还引入了一个新的类就是StringJoiner,这个类的作用就是提供了一种快捷的字符串拼接的模板方法。

    冬天里的懒猫
  • 05 Confluent_Kafka权威指南 第五章: kafka内部实现原理

    为了在生产环境中运行kafka或者编写使用它的应用程序,并不一定要理解kafka的内部原理。然而,理解kafka的工作原理,有助于故障排查,理解kafka的工作...

    冬天里的懒猫
  • 深入理解java虚拟机学习笔记(三)-虚拟机性能监控与故障处理工具

    该命令主要与jmap搭配使用,用来分析jmap转储的转储快照。其中构建了一个微型的http/html服务器。生成dump文件的分析结果后可以通过浏览器进行查看。...

    冬天里的懒猫
  • kNN 解决鸢尾花和手写数字识别分类问题

    摘要:运用 kNN 解决鸢尾花和手写数字识别分类问题,熟悉 Sklearn 的一般套路。

    石晓文
  • 揭秘:技术非常好的程序员为什么没有女朋友?

    一程序员去面试,面试官问:"你毕业才两年,这三年工作经验是怎么来的?!"程序员答:"加班。"

    老九君
  • 正则表达式在线测试&&生成代码 转

    例如,您可能需要搜索整个网站,删除过时的材料,以及替换某些 HTML 格式标记。在这种情况下,可以使用正则表达式来确定在每个文件中是否出现该材料或该 HTML...

    wuweixiang
  • NBA球星泄密:Magic Leap产品形状酷似墨镜

    VRPinea
  • 【Rust日报】2019-08-18 - Rust Image比Python Pillow更快吗?

    作者在研究一个科学应用,有时需要在非常大的图像上面进行操作,在作者目前的Pyhton工作版本中,对于大图像的处理很慢,最后作者得出测试Rust要快很多。

    MikeLoveRust
  • JVM垃圾回收算法

    java404
  • JVM 垃圾收集算法

    本文“垃圾收集算法”节选自《深入理解Java虚拟机:JVM高级特性与最佳实践》【作者:周志明】

    smartsi

扫码关注云+社区

领取腾讯云代金券