前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一个多线程异步执行面试题的多种解决方法

一个多线程异步执行面试题的多种解决方法

作者头像
冬天里的懒猫
发布2021-08-31 16:00:48
7400
发布2021-08-31 16:00:48
举报

文章目录

1.问题

题目如下: 在 main 函数启动一个新线程,运行一个方法,拿到这个方法的返回值后,退出主线程?

public class Homework03 {

	public static void main(String[] args) {
		long start = System.currentTimeMillis();

		// 在这里创建一个线程或线程池,
		// 异步执行 下面方法
		int result = sum();

		System.out.println("异步计算结果:"+result);
		System.out.println("计算耗时:"+(System.currentTimeMillis() - start) +"  ms");
		
	}
	
	private static int sum() {
		return fibo(36);
	}
	
	private static int fibo(int a) {
		if(a<2){
			return 1;
		}
		return fibo(a-1) + fibo(a-2);
	}
	
}

题目的实际要求是,在main线程中,启动一个线程或者线程池,来执行一个斐波那契数列的求和运算,之后在计算完毕之后,将计算结果返回到主线程。 对于这个问题,实际上就是两个线程,main线程和计算线程之间的通讯问题。主线程在启动计算线程之后,需要进入等待或者阻塞状态,直到等待的变量状态改变,或者被阻塞的任务执行完毕,之后再运行获取结果的方法,拿到计算结果。 大致可以分为 共享内存、join、同步工具等多种解决方案。

2.解决方法

2.1 线程的Join方法

线程的join方法本身就是jvm实现的,让当前线程进行阻塞,等待被执行线程结束之后再执行的方法。代码如下:

public class AsyncRun02 {

	private static int sum() {
		return fibo(36);
	}

	private static int fibo(int a) {
		if (a < 2) {
			return 1;
		}
		return fibo(a - 1) + fibo(a - 2);
	}

	public static void main(String[] args) throws Exception {
		long start = System.currentTimeMillis();
		SumThread sumThread = new SumThread();

		sumThread.start();
		sumThread.join();
		int result = sumThread.getResult();
		System.out.println("异步计算结果:" + result);
		System.out.println("计算耗时:" + (System.currentTimeMillis() - start) + "  ms");
	}

	static class SumThread extends Thread {

		private Integer result;

		public Integer getResult() {
			return result;
		}

		@Override
		public void run() {
			result = sum();
		}
	}

}

2.2 共享volatile变量

可以让两个线程共享一个volatile的变量,计算线程在计算完成之后,更新这个volatile变量的状态为ture,那么main线程只需要在计算线程启动之后,不断轮询监控该变量的状态即可。代码如下:

public class AsyncRun03 {

	private static int sum() {
		return fibo(36);
	}

	private static int fibo(int a) {
		if (a < 2) {
			return 1;
		}
		return fibo(a - 1) + fibo(a - 2);
	}

	public static void main(String[] args) throws Exception {
		long start = System.currentTimeMillis();
		SumThread sumThread = new SumThread();

		sumThread.start();

		while (!sumThread.isSuccess()) {
			TimeUnit.MILLISECONDS.sleep(1);
		}

		int result = sumThread.getResult();
		System.out.println("异步计算结果:" + result);
		System.out.println("计算耗时:" + (System.currentTimeMillis() - start) + "  ms");
	}

	static class SumThread extends Thread {

		private Integer result;
		private volatile boolean success = false;

		public boolean isSuccess() {
			return success;
		}

		public Integer getResult() {
			return result;
		}

		@Override
		public void run() {
			result = sum();
			success = true;
		}
	}

}

2.3 synchronized锁

synchronized可以实现这个需求,但是需要一个前提,就是让计算线程先拿到锁,这之后main线程被synchronized阻塞。直到计算线程执行完毕,代码如下:

public class AsyncRun04 {

	private static final Object lock = new Object();

	private static int sum() {
		return fibo(36);
	}

	private static int fibo(int a) {
		if (a < 2) {
			return 1;
		}
		return fibo(a - 1) + fibo(a - 2);
	}

	public static void main(String[] args) throws Exception {
		long start = System.currentTimeMillis();
		SumThread sumThread = new SumThread();
		sumThread.start();
		
		TimeUnit.MILLISECONDS.sleep(1);
		synchronized (lock) {

		}
		int result = sumThread.getResult();
		System.out.println("异步计算结果:" + result);
		System.out.println("计算耗时:" + (System.currentTimeMillis() - start) + "  ms");
	}

	static class SumThread extends Thread {

		private Integer result;

		public Integer getResult() {
			return result;
		}

		@Override
		public void run() {
			synchronized (lock) {
				result = sum();
			}
		}
	}
}

2.4 wait+notify/notifyAll

可以利用object对象的wait+notify/notifyAll方法来实现。在启动完计算线程之后将主线程wait,之后在计算线程中,执行完毕之后,调用notify/notifyAll方法来唤醒主线程继续执行。 代码如下:

public class AsyncRun05 {

	private static final Object lock = new Object();

	private static int sum() {
		return fibo(36);
	}

	private static int fibo(int a) {
		if (a < 2) {
			return 1;
		}
		return fibo(a - 1) + fibo(a - 2);
	}

	public static void main(String[] args) throws Exception {
		long start = System.currentTimeMillis();
		SumThread sumThread = new SumThread();

		sumThread.start();
		synchronized (lock) {
			lock.wait();
		}

		int result = sumThread.getResult();
		System.out.println("异步计算结果:" + result);
		System.out.println("计算耗时:" + (System.currentTimeMillis() - start) + "  ms");
	}

	static class SumThread extends Thread {

		private Integer result;

		public Integer getResult() {
			return result;
		}

		@Override
		public void run() {
			synchronized (lock) {
				result = sum();
				lock.notifyAll();
			}
		}
	}
}

2.5 park/unpark

同理,也可以利用同步工具中的lockSupport的park/unpark方法来实现。在主线程启动计算线程之后执行park,之后再在计算线程执行完毕之后,调用主线程的unpark方法。diamagnetic如下:

public class AsyncRun07 {
	
	private static int sum() {
		return fibo(36);
	}

	private static int fibo(int a) {
		if (a < 2) {
			return 1;
		}
		return fibo(a - 1) + fibo(a - 2);
	}

	public static void main(String[] args) throws Exception {
		long start = System.currentTimeMillis();
		SumThread sumThread = new SumThread();
		sumThread.setMainThread(Thread.currentThread());
		sumThread.start();
		LockSupport.park();

		int result = sumThread.getResult();
		System.out.println("异步计算结果:" + result);
		System.out.println("计算耗时:" + (System.currentTimeMillis() - start) + "  ms");
	}

	static class SumThread extends Thread {

		private Thread mainThread;

		private Integer result;

		public Integer getResult() {
			return result;
		}

		public Thread getMainThread() {
			return mainThread;
		}

		public void setMainThread(Thread mainThread) {
			this.mainThread = mainThread;
		}

		@Override
		public void run() {
			result = sum();
			LockSupport.unpark(getMainThread());
		}
	}

}

2.6 ReentrantLock可重入锁

同样,参考synchronized,也可以通过可重入锁ReentrantLock,但是需要先让计算线程抢到锁,之后main线程会被阻塞直到计算线程执行完毕。 代码如下:

public class AsyncRun08 {

	private static final ReentrantLock lock = new ReentrantLock(true);

	private static int sum() {
		return fibo(36);
	}

	private static int fibo(int a) {
		if (a < 2) {
			return 1;
		}
		return fibo(a - 1) + fibo(a - 2);
	}

	public static void main(String[] args) throws Exception {
		long start = System.currentTimeMillis();
		SumThread sumThread = new SumThread();
		sumThread.start();

		TimeUnit.MILLISECONDS.sleep(1);
		lock.lock();
		try {
			System.out.println("***");
		} finally {
			lock.unlock();
		}

		int result = sumThread.getResult();
		System.out.println("异步计算结果:" + result);
		System.out.println("计算耗时:" + (System.currentTimeMillis() - start) + "  ms");
	}

	static class SumThread extends Thread {


		private Integer result;

		public Integer getResult() {
			return result;
		}

		@Override
		public void run() {
			lock.lock();
			try {
				result = sum();
			} finally {
				lock.unlock();
			}
		}
	}

}

2.7 ReentrantLock配合Condation

当然,既然使用了ReentrantLock,那么还有一种与wait/notify等价的方法,就是结合Condation,通过Condation的await和signalAll/signal方法。 在启用了计算线程之后,通过Condation的await方法阻塞,待计算线程执行完毕再执行signal方法。 代码如下:

public class AsyncRun09 {

	private static final ReentrantLock lock = new ReentrantLock(true);
	private static final Condition c1 = lock.newCondition();

	private static int sum() {
		return fibo(36);
	}

	private static int fibo(int a) {
		if (a < 2) {
			return 1;
		}
		return fibo(a - 1) + fibo(a - 2);
	}

	public static void main(String[] args) throws Exception {
		long start = System.currentTimeMillis();
		SumThread sumThread = new SumThread();
		sumThread.start();

		lock.lock();
		try {
			c1.await();
		} finally {
			lock.unlock();
		}

		int result = sumThread.getResult();
		System.out.println("异步计算结果:" + result);
		System.out.println("计算耗时:" + (System.currentTimeMillis() - start) + "  ms");
	}

	static class SumThread extends Thread {


		private Integer result;

		public Integer getResult() {
			return result;
		}

		@Override
		public void run() {
			lock.lock();
			try {
				result = sum();
				c1.signalAll();
			} finally {
				lock.unlock();
			}
		}
	}
}

2.8 CountDownLatch

在java的并发包中,有很多同步的工具可以来实现这个场景,定义一个CountDownLatch,需要倒计时的线程为1,当main线程启动线程之后,让CountDownLatch执行await方法,计算线程在计算完毕之后,执行countdown方法。mian线程则会继续执行。 代码如下:

public class AsyncRun06 {

	private static final CountDownLatch latch = new CountDownLatch(1);

	private static int sum() {
		return fibo(36);
	}

	private static int fibo(int a) {
		if (a < 2) {
			return 1;
		}
		return fibo(a - 1) + fibo(a - 2);
	}

	public static void main(String[] args) throws Exception {
		long start = System.currentTimeMillis();
		SumThread sumThread = new SumThread();

		sumThread.start();
		latch.await();

		int result = sumThread.getResult();
		System.out.println("异步计算结果:" + result);
		System.out.println("计算耗时:" + (System.currentTimeMillis() - start) + "  ms");
	}

	static class SumThread extends Thread {

		private Integer result;

		public Integer getResult() {
			return result;
		}

		@Override
		public void run() {
			result = sum();
			latch.countDown();
		}
	}

}

2.9 CyclicBarrier

同理,CyclicBarrier也可以在这个场景使用。主线程启动计算线程之后,执行await,之后计算线程执行完之后,也执行await,这个内存屏障设为2,则正好解除屏障,继续执行。 代码如下:

public class AsyncRun10 {

	private static final CyclicBarrier barrier = new CyclicBarrier(2);

	private static int sum() {
		return fibo(36);
	}

	private static int fibo(int a) {
		if (a < 2) {
			return 1;
		}
		return fibo(a - 1) + fibo(a - 2);
	}

	public static void main(String[] args) throws Exception {
		long start = System.currentTimeMillis();
		SumThread sumThread = new SumThread();
		sumThread.start();
		barrier.await();

		int result = sumThread.getResult();
		System.out.println("异步计算结果:" + result);
		System.out.println("计算耗时:" + (System.currentTimeMillis() - start) + "  ms");
	}

	static class SumThread extends Thread {
		
		private Integer result;

		public Integer getResult() {
			return result;
		}

		@Override
		public void run() {
			try {
				result = sum();
				barrier.await();
			} catch (InterruptedException e) {
				e.printStackTrace();
			} catch (BrokenBarrierException e) {
				e.printStackTrace();
			}
		}
	}
}

2.10 Semaphore

Semaphore也能很好的实现,Semaphore初始为0,在主线程中执行acquire,自然会被阻塞,等到计算线程执行完毕,执行release。 代码如下:

public class AsyncRun11 {

	private static final Semaphore semaphore = new Semaphore(0);

	private static int sum() {
		return fibo(36);
	}

	private static int fibo(int a) {
		if (a < 2) {
			return 1;
		}
		return fibo(a - 1) + fibo(a - 2);
	}

	public static void main(String[] args) throws Exception {
		long start = System.currentTimeMillis();
		SumThread sumThread = new SumThread();
		sumThread.start();
		semaphore.acquire();

		int result = sumThread.getResult();
		System.out.println("异步计算结果:" + result);
		System.out.println("计算耗时:" + (System.currentTimeMillis() - start) + "  ms");
	}

	static class SumThread extends Thread {
		
		private Integer result;

		public Integer getResult() {
			return result;
		}

		@Override
		public void run() {
			try {
				result = sum();
				semaphore.release();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}

2.11 Phaser

Phaser如果要实现这个场景,则设置Phaser为2,在主线程和计算线程中都执行arriveAndAwaitAdvance。 代码如下:

public class AsyncRun12 {

	private static final Phaser phaser = new Phaser(2);

	private static int sum() {
		return fibo(36);
	}

	private static int fibo(int a) {
		if (a < 2) {
			return 1;
		}
		return fibo(a - 1) + fibo(a - 2);
	}

	public static void main(String[] args) throws Exception {
		long start = System.currentTimeMillis();
		SumThread sumThread = new SumThread();
		sumThread.start();
		phaser.arriveAndAwaitAdvance();

		int result = sumThread.getResult();
		System.out.println("异步计算结果:" + result);
		System.out.println("计算耗时:" + (System.currentTimeMillis() - start) + "  ms");
	}

	static class SumThread extends Thread {


		private Integer result;

		public Integer getResult() {
			return result;
		}

		@Override
		public void run() {
			try {
				result = sum();
				phaser.arriveAndAwaitAdvance();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}

2.12 Exchanger

Exchanger是最适合两个线程通信的工具。尤其是交替执行的两个线程。主线程和计算线程都通过exchange方法,同时被阻塞住,然后交换数据。 代码如下:

public class AsyncRun13 {

	private static final Exchanger<Integer> exchanger = new Exchanger<>();

	private static int sum() {
		return fibo(36);
	}

	private static int fibo(int a) {
		if (a < 2) {
			return 1;
		}
		return fibo(a - 1) + fibo(a - 2);
	}

	public static void main(String[] args) throws Exception {
		long start = System.currentTimeMillis();
		SumThread sumThread = new SumThread();
		sumThread.start();
		Integer result = exchanger.exchange(0);

		System.out.println("异步计算结果:" + result);
		System.out.println("计算耗时:" + (System.currentTimeMillis() - start) + "  ms");
	}

	static class SumThread extends Thread {


		@Override
		public void run() {
			try {
				int result = sum();
				exchanger.exchange(result);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

}

2.13 BlockingQueue

BlockingQueue的take方法,也是有阻塞效果的,那么对于这一类的Queue,或者List,可以在主线程启动起算线程之后,通过take方法进行阻塞。而一旦计算线程执行完毕,将数据加入到queue,则阻塞被解除。采用ArrayBlockingQueue实现。 代码如下:

public class AsyncRun14 {

	private static final ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(1);

	private static int sum() {
		return fibo(36);
	}

	private static int fibo(int a) {
		if (a < 2) {
			return 1;
		}
		return fibo(a - 1) + fibo(a - 2);
	}

	public static void main(String[] args) throws Exception {
		long start = System.currentTimeMillis();
		SumThread sumThread = new SumThread();
		sumThread.start();
		Integer result = queue.take();

		System.out.println("异步计算结果:" + result);
		System.out.println("计算耗时:" + (System.currentTimeMillis() - start) + "  ms");
	}

	static class SumThread extends Thread {


		@Override
		public void run() {
			try {
				int result = sum();
				queue.add(result);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}

2.14 SynchronousQueue

SynchronousQueue是一个特殊的Queue,只能允许1个长度,本质上与BlockingQueue的原理一致。

public class AsyncRun15 {

	private static final SynchronousQueue<Integer> queue = new SynchronousQueue<>();

	private static int sum() {
		return fibo(36);
	}

	private static int fibo(int a) {
		if (a < 2) {
			return 1;
		}
		return fibo(a - 1) + fibo(a - 2);
	}

	public static void main(String[] args) throws Exception {
		long start = System.currentTimeMillis();
		SumThread sumThread = new SumThread();
		sumThread.start();
		Integer result = queue.take();

		System.out.println("异步计算结果:" + result);
		System.out.println("计算耗时:" + (System.currentTimeMillis() - start) + "  ms");
	}

	static class SumThread extends Thread {


		@Override
		public void run() {
			try {
				int result = sum();
				queue.add(result);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

}

2.15 List/Set非阻塞集合

在使用了阻塞的集合实现之后,同样,非阻塞的集合也能实现,这个与通过volatile共享变量类似。需要主线程轮询集合的状态,是否isEmpty。如下采用ArrayList实现。 代码如下:

public class AsyncRun16 {

	private static final ArrayList<Integer> list = new ArrayList<>();

	private static int sum() {
		return fibo(36);
	}

	private static int fibo(int a) {
		if (a < 2) {
			return 1;
		}
		return fibo(a - 1) + fibo(a - 2);
	}

	public static void main(String[] args) throws Exception {
		long start = System.currentTimeMillis();
		SumThread sumThread = new SumThread();
		sumThread.start();
		while (list.isEmpty()) {
			TimeUnit.MILLISECONDS.sleep(1);
		}
		Integer result = list.get(0);
		System.out.println("异步计算结果:" + result);
		System.out.println("计算耗时:" + (System.currentTimeMillis() - start) + "  ms");
	}

	static class SumThread extends Thread {
		
		@Override
		public void run() {
			try {
				int result = sum();
				list.add(result);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}

2.16 Thread.isAlive

由于本题的特殊性,也可以不用第三变量,直接判断计算线程的状态,isAlive方法,在isAlive方法中轮询sleep,待计算线程执行完毕。 代码如下:

public class AsyncRun19 {

	private static int sum() {
		return fibo(36);
	}

	private static int fibo(int a) {
		if (a < 2) {
			return 1;
		}
		return fibo(a - 1) + fibo(a - 2);
	}

	public static void main(String[] args) throws Exception {
		long start = System.currentTimeMillis();
		SumThread sumThread = new SumThread();
		sumThread.start();
		do {
			TimeUnit.MILLISECONDS.sleep(1);
		} while (sumThread.isAlive());

		int result = sumThread.getResult();
		System.out.println("异步计算结果:" + result);
		System.out.println("计算耗时:" + (System.currentTimeMillis() - start) + "  ms");
	}

	static class SumThread extends Thread {

		private Integer result;

		public Integer getResult() {
			return result;
		}

		@Override
		public void run() {
			result = sum();
		}
	}
}

2.17 Callable

由于Runnable是没有返回值的,那么java再解决这个问题的时候引入了Callable,我们也可以利用Callable来实现。 代码如下:

public class AsyncRun01 {

	public static void main(String[] args) throws Exception {
		long start = System.currentTimeMillis();

		ExecutorService executorService = Executors.newSingleThreadExecutor();

		Callable<Integer> task = new Callable<Integer>() {
			@Override
			public Integer call() throws Exception {
				return sum();
			}
		};
		Future<Integer> future = executorService.submit(task);
		System.out.println("异步计算结果:" + future.get());
		System.out.println("计算耗时:" + (System.currentTimeMillis() - start) + "  ms");
		executorService.shutdown();

	}

	private static int sum() {
		return fibo(36);
	}

	private static int fibo(int a) {
		if (a < 2) {
			return 1;
		}
		return fibo(a - 1) + fibo(a - 2);
	}
}

2.18 FutureTask

当然,还可以通过FutureTask来拿到线程异步执行的返回结果。 代码如下:

public class AsyncRun17 {


	private static int sum() {
		return fibo(36);
	}

	private static int fibo(int a) {
		if (a < 2) {
			return 1;
		}
		return fibo(a - 1) + fibo(a - 2);
	}

	public static void main(String[] args) throws Exception {
		long start = System.currentTimeMillis();
		ExecutorService service = Executors.newCachedThreadPool();

		SumThread sumThread = new SumThread();
		FutureTask<Integer> futureTask = new FutureTask<>(sumThread);
		service.submit(futureTask);
		service.shutdown();
		System.out.println("异步计算结果:" + futureTask.get());
		System.out.println("计算耗时:" + (System.currentTimeMillis() - start) + "  ms");
	}

	static class SumThread implements Callable<Integer> {

		@Override
		public Integer call() throws Exception {
			return sum();
		}
	}
}

2.19 CompleteFuture

jvm1.8中引入了CompleteFuture,也能在这个场景中来使用。 代码如下:

public class AsyncRun18 {
	
	private static int sum() {
		return fibo(36);
	}

	private static int fibo(int a) {
		if (a < 2) {
			return 1;
		}
		return fibo(a - 1) + fibo(a - 2);
	}

	public static void main(String[] args) throws Exception {
		long start = System.currentTimeMillis();
		ExecutorService service = Executors.newCachedThreadPool();

		CompletableFuture<Integer> future = new CompletableFuture<>();
		SumThread sumThread = new SumThread(future);
		service.submit(sumThread);
		service.shutdown();

		System.out.println("异步计算结果:" + future.get());
		System.out.println("计算耗时:" + (System.currentTimeMillis() - start) + "  ms");
	}

	static class SumThread implements Runnable {

		private CompletableFuture<Integer> future;

		public SumThread(CompletableFuture<Integer> future) {
			this.future = future;
		}

		@Override
		public void run() {
			future.complete(sum());
		}
	}
}

3.总结

本文共列举了19种方法来实现异步执行线程并得到其执行结果的需求。这都是java线程通信的常用方法,每种方法的使用大同小异。采用轮询是最不可取的方法,这样会导致主线程浪费CPU资源,增加服务器不必要的开销。 类似于茴香豆的茴有几种写法,多少种并不是关键,我们需要的是掌握这每一种线程通信方法的使用场景,在业务开发中灵活应用。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-08-25 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 1.问题
  • 2.解决方法
    • 2.1 线程的Join方法
      • 2.2 共享volatile变量
        • 2.3 synchronized锁
          • 2.4 wait+notify/notifyAll
            • 2.5 park/unpark
              • 2.6 ReentrantLock可重入锁
                • 2.7 ReentrantLock配合Condation
                  • 2.8 CountDownLatch
                    • 2.9 CyclicBarrier
                      • 2.10 Semaphore
                        • 2.11 Phaser
                          • 2.12 Exchanger
                            • 2.13 BlockingQueue
                              • 2.14 SynchronousQueue
                                • 2.15 List/Set非阻塞集合
                                  • 2.16 Thread.isAlive
                                    • 2.17 Callable
                                      • 2.18 FutureTask
                                        • 2.19 CompleteFuture
                                        • 3.总结
                                        领券
                                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档