一. 为什么需要多线程并行编程设计
多线程学习策略:
二.多线程场景示例
package com.hong.concurrent;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* Created by John on 2019/1/24.
* 场景:一个app首页需要展示用户的基本信息,用户积分
* 且这二部份信息需要调用二个不同的服务获取,相互之间没有依赖关系
* 假设获取用户基本信息耗时:2s,用户积分3s
* 如果使用传统的串行获取,则总耗时会超过5s
* 采取并行获取,则总耗时3s多些
*/
public class Forkjoin {
/**
* 获取首页信息
* @return
*/
public String getIndexPageInfo() throws ExecutionException, InterruptedException {
long begin = System.currentTimeMillis();
/**
* Callable Future机制
* Runnable 与 Callable的区别:
* 1.Runnable无返回值,Callable有返回值
* 2.Runnable的run()方法不会抛出异常,Callable的call()方法会throws Exception
* 两者之间的联系:Callable的call()实际运行在Runnable的run()方法里面
*/
// 1.利用Callable包装业务代码
Callable<String> userInfoCallable = () -> {
long start = System.currentTimeMillis();
String userInfo = "用户基本信息";
//模拟接口调用耗时
Thread.sleep(2000);
System.out.println(Thread.currentThread() +"->获取用户信息耗时:" + (System.currentTimeMillis() - start) + "ms");
return userInfo;
};
Callable<String> integralCallable = () -> {
long start = System.currentTimeMillis();
String userInfo = "用户积分";
//模拟接口调用耗时
Thread.sleep(3000);
System.out.println(Thread.currentThread() + "->获取用户积分耗时:" + (System.currentTimeMillis() - start) + "ms");
return userInfo;
};
//多线程异步运行
FutureTask<String> userInfoTask = new FutureTask<>(userInfoCallable);
FutureTask<String> integralTask = new FutureTask<>(integralCallable);
/* MyFutureTask<String> userInfoTask = new MyFutureTask<>(userInfoCallable);
MyFutureTask<String> integralTask = new MyFutureTask<>(integralCallable);*/
new Thread(userInfoTask).start();
new Thread(integralTask).start();
// 合并结果
String result = userInfoTask.get() + "||" + integralTask.get();
System.out.println(Thread.currentThread() + "->总耗时:" + (System.currentTimeMillis() - begin) + "ms");
return result;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
Forkjoin forkjoin = new Forkjoin();
System.out.println(forkjoin.getIndexPageInfo());
}
}
三. 实现自己的FutureTask
package com.hong.concurrent;
import java.util.concurrent.Callable;
/**
* Created by John on 2019/1/24.
* 自己实现一个FutureTask
* 关键步骤:
* 1.泛型
* 2.构造方法传入Callable
* 3.实现Runnable接口
* 4.get()方法返回执行结果
* 5.任务未执行结束的话,get()方法有阻塞的效果,等待任务执行完成
*/
public class MyFutureTask<T> implements Runnable {
/**
* 执行状态标识
*/
private String state = "NEW";
/**
* 包装业务代码
*/
private Callable<T> callable;
/**
* 执行结果
*/
private T result;
public MyFutureTask(Callable<T> callable) {
this.callable = callable;
}
public T get() {
if ("END".equals(state)) {
return result;
}
// 阻塞等待任务执行完成
try {
synchronized (this) {
System.out.println(Thread.currentThread() + "进入等待");
this.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return result;
}
@Override
public void run() {
try {
result = callable.call();
} catch (Exception e) {
e.printStackTrace();
//捕获异常
state = "EX";
} finally {
state = "END";
}
/**
* wait...notify/notifyAll一定要放在synchronized中
*/
synchronized (this) {
System.out.println(Thread.currentThread() + "唤醒等待的线程");
this.notifyAll();
}
}
}
自定义的MyFutureTask的实现显然没有JDK的FutureTask那么严谨,这里只是为了演示FutureTask内部的工作原理。
有一点需要说明:MyFutureTask为了实现get()的阻塞等待获取执行结果采用了Object的wait…notifyAll机制,而在FutureTask中,使用的是 LockSupport.park()和LockSupport.unpark(),这里是有原因的。因为Object的wait…notifyAll机制存在类似活锁的问题,当需要被唤醒的子线程执行时间过长,而主调线程先一步执行了notifyAll方法
那么子线程将永远处于等待中而无法被唤醒。
package com.hong.concurrent;
/**
* Created by John on 2019/1/24.
* wait...notify/notifyAll机制存在类似死锁的问题。
* 当需要被唤醒的子线程执行时间过长,而主调线程先一步执行了notifyAll方法
* 那么子线程将永远处于等待中而无法被唤醒。(先后顺序不同)
* 而LockSupport.park()和LockSupport.unpark()可以实现顺序无关性,即使主调线程先一步执行了park()方法,
* 子线程还是可以被唤醒继续后面的流程。
*/
public class WaitNotifyTest {
private static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
try {
Thread.sleep(5000L); // 1
synchronized (lock) {
System.out.println(Thread.currentThread() + "等待");
lock.wait();
System.out.println(Thread.currentThread() + "等待结束,继续执行");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
//在主线程中,3s之后唤醒
Thread.sleep(3000L);
synchronized (lock) {
lock.notifyAll();
System.out.println(Thread.currentThread() + "唤醒等待者");
}
Thread.sleep(10000000L);
}
}
注释 /1处的代码。则可以顺利结束
使用LockSupport.park()和LockSupport.unpark()则可以解决此问题。
package com.hong.concurrent;
import java.util.concurrent.locks.LockSupport;
/**
* Created by John on 2019/1/24.
* wait...notify/notifyAll机制存在类似死锁的问题。
* 当需要被唤醒的子线程执行时间过长,而主调线程先一步执行了notifyAll方法
* 那么子线程将永远处于等待中而无法被唤醒。(先后顺序不同)
* 而LockSupport.park()和LockSupport.unpark()可以实现顺序无关性,即使主调线程先一步执行了park()方法,
* 子线程还是可以被唤醒继续后面的流程。
*/
public class LockParkTest {
private static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
try {
Thread.sleep(5000L);
System.out.println(Thread.currentThread() + "等待");
LockSupport.park();
System.out.println(Thread.currentThread() + "等待结束,继续执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t.start();
//在主线程中,3s之后唤醒
Thread.sleep(3000L);
LockSupport.unpark(t);
System.out.println(Thread.currentThread() + "唤醒等待者");
Thread.sleep(10000000L);
}
}