继承Thread类创建线程
实现Runnable接口创建线程
实现Callable接口通过FutureTask包装器来创建Thread线程
至于为什么选择第3种实现方式FutureTask,当然是因为这种方式可以有返回结果啊。上述1,2两种方式中线程run()方法返回类型是void,那要它何用?溜了溜了。
package threadTest;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* @author hz
* @Description: 测试多线程并行调用两个接口数据。
* @create 2019-05-28
*/
public class FutureTaskTest {
public Map getUserInfo() throws Exception{
Map map = new HashMap();
//1.获取用户基本信息
Callable userInfoCallable=new Callable() {
@Override
public String call() throws Exception {
Thread thread0 = Thread.currentThread();
System.out.println("thread0启动:"+thread0.getName());
thread0.sleep(3000);
//模拟调用获取用户mapper接口
return "返回用户信息数据";
}
};
FutureTask userInfoFutureTask = new FutureTask(userInfoCallable);
new Thread(userInfoFutureTask).start();
//2.获取页面展示数据信息
Callable indexDataCallable=new Callable() {
@Override
public String call() throws Exception {
Thread thread1 = Thread.currentThread();
System.out.println("thread1启动:"+thread1.getName());
thread1.sleep(5000);
//模拟调用获取页面数据mapper接口
return "返回页面数据信息";
}
};
FutureTask indexDatafutureTask = new FutureTask(indexDataCallable);
new Thread(indexDatafutureTask).start();//开启线程
//3.获取对象并合并数据(调用get()方法,该线程会进入阻塞状态,直到线程返回结果才会继续执行下面代码)
String userInfo = userInfoFutureTask.get().toString();
map.put("userInfo",userInfo);
String indexData = indexDatafutureTask.get().toString();
map.put("indexData",indexData);
return map;
}
public static void main(String[] args) {
System.out.println("主线程main:"+Thread.currentThread().getName());
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
System.out.println("开始"+sdf.format(new Date()));
FutureTaskTest futureTaskTest = new FutureTaskTest();
Map userInfo=null;
try {
userInfo = futureTaskTest.getUserInfo();
if(userInfo!=null){
System.out.println(userInfo.get("userInfo"));
System.out.println(userInfo.get("indexData"));
}
System.out.println("结束"+sdf.format(new Date()));
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出结果打印:
3.1.Callable
3.2.userInfoFutureTask.get() //获取该线程执行后的返回结果 (重点)
FutureTask类中的get()方法大家可以关注一下源码,水有点小深。因为当时获取调用用户接口线程内容的这一段代码
String userInfo = userInfoFutureTask.get().toString();
我没有写在示例中那个位置,而是第二个获取页面展示数据接口Callable之前,然后发现,运行时间差永远是两个休眠时间相加。wtf?那我并发编程意义在哪?当时就蒙了,难道我玩的不是多线程?最后把这个代码放到后面去才运行正常,刚好是休眠时间最长的那个。于是翻开get源码:
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
继续点进awaitDone方法:
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false;
for (;;) {
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
int s = state;
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
else if (q == null)
q = new WaitNode();
else if (!queued)
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
q.next = waiters, q);
else if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
LockSupport.parkNanos(this, nanos);
}
else
LockSupport.park(this);
}
}
这一段源码简而言之意思就是:死循环判断该线程是否拿到结果,如果没拿到就执行LockSupport.park(),使该线程挂起,等到返回数据的线程执行完毕,通过unpark()唤醒该线程,则就可以拿到数据。
3.3 synchronized与notifyAll和wait搭配使用的机制与park机制的区别:
synchronized与notifyAll和wait:意思就是给线程加锁,只有获得对象锁才能进入,当线程调用wait()后就会释放对象锁,线程进入阻塞状态,需要等待另一个线程获得对象锁并notifyAll唤醒它才能继续执行。
而park机制就是我前面讲到过的两个方法,分别是park()挂起,unpark唤醒。
当线程使用notify()/wait()或notifyAll()/wait()进行协作时,在wait()的一方可能会错失notify()/notifyAll()的信号,而一直处于wait()中造成死锁。
那park机制优点就来了,他不会管你线程哪个先执行后执行,都会通过unpark()方法去唤醒。避免了死锁现象。
以前单线程中某一响应需要调用多个接口拿到数据,我们只能等上个接口调用完了才能执行下个接口调用(一般大多数人公司里写的小项目都是这种吧)。而多线程就可以开启多个线程分别调用接口拿到数据,例如一个页面需要用户信息即用户接口(2s),以及页面展示内容即页面数据内容接口(4s)。那我们就开启两个线程,分别调用两个接口,最后只要两个数据合在一起,一起返回就行了。然后产生的收益就是:以前单线程调用两个接口分别需要2+4 s才能执行完。现在只需要最大的接口调用时间即4s就好了。