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 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 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 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 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 task = new Callable() {
@Override
public Integer call() throws Exception {
return sum();
}
};
Future 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 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 {
@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 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 future;
public SumThread(CompletableFuture future) {
this.future = future;
}
@Override
public void run() {
future.complete(sum());
}
}
}
3.总结
本文共列举了19种方法来实现异步执行线程并得到其执行结果的需求。这都是java线程通信的常用方法,每种方法的使用大同小异。采用轮询是最不可取的方法,这样会导致主线程浪费CPU资源,增加服务器不必要的开销。
类似于茴香豆的茴有几种写法,多少种并不是关键,我们需要的是掌握这每一种线程通信方法的使用场景,在业务开发中灵活应用。