其他分享
首页 > 其他分享> > 一个多线程异步执行面试题的多种解决方法

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

作者:互联网

文章目录

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资源,增加服务器不必要的开销。
类似于茴香豆的茴有几种写法,多少种并不是关键,我们需要的是掌握这每一种线程通信方法的使用场景,在业务开发中灵活应用。

标签:异步,面试题,int,System,fibo,static,result,多线程,public
来源: https://blog.csdn.net/dhaibo1986/article/details/119910622