前一段时间写了socket和多线程的俩篇文章,由于工作繁忙最近一致没有跟新,有好多开发爱好者私信小编,让把多线程和socket这块进行补充完毕,今天抽时间将多线程高级进阶篇进行详细的补充一下,这里主要涉及到是callable 、ExecutorService以及线程池的相关技术,其中会涉及大量的示例,如有看不明白或者讲解不透彻的部分,大家可以将示例在本地运行进行观察数据输出的情况,可以更进一步加深多线程的了解。
一: 在进行线程池方面的讲解之前,首先我们来回顾一下java 多线程的三种实现方式:
1. 第一种是通过Thread类实现多线程
示例如下:
public class TestThread {
static class Personextends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public static void main(String[] args) {
Person person =new Person();
Person person1 =new Person();
Person person2 =new Person();
person.run();
person1.run();
person2.run();
}
}
2. 第二种是通过实现Runnable接口实现多线程
public class TestRunnable {
static class Personimplements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public static void main(String[] args) {
Person person =new Person();
Person person1 =new Person();
Person person2 =new Person();
person.run();
person1.run();
person2.run();
}
3. 第三种种通过实现Callable接口实现多线程
Callable接口需要结合ExecutorService 和Future使用
二: 多线程种run()方法与start()调用线程的区别:
1. run()方法是按照顺序执行的,在main线程种的被当作一种普通的方法调用,所有多线程启动后调用run()方法他的线程名换是main线程,线程的执行过程种和普通方法调用一样,都是按照顺序执行的,严格意义上来说他不是多线程。
2. start()方式是真正意义上的多线程,在调用start()方法启动后,除main线程外,会有单独的线程启动,每一个线程的名称和调用顺序都是不同的。
示例如下:
run()方法和普通方法调用一样,输出的线程都是main线程。
start()方法是真正意义上的多线程。
三: 多线程lamda方式与传统方式实现区别:
看一下是不是使用lamda表达式的时候,程序更加简单了,一行代码就搞定。
public class TestThread {
public static void main(String[] args) {
Runnable runnable = (() -> System.out.println(Thread.currentThread().getName()));
runnable.run();
}
}
前面我们穿针引线给大家做一个简单的回顾,下面我们开始讲解正餐,在之前的多线程种,一致存在种种问题,让程序员诟病,诸如无法预知线程执行接口,执行状态,以及多线程管理的麻烦问题,所有Java的开发团队也一致在寻求线程的改进,在后续函数式接口注解@FunctionalInterface、java.util.concurrent框架的加入都是为了解决多线程遗留的相关问题。在实际应用种,我们可能会经常遇到以下问题:
1. 一个线程执行完毕后,如何获取线程执行的结果?
2. 线程执行过程中,如果获取线程执行状态已经取消正在执行的线程?
3. 俩个线程独立同时执行,但一个线程又依赖另一个线程执行的结果?
4. 如果获取一个集合中所有线程的执行完后后的结果?
5. 多个不同的线程通过不同的方式计算一个数学问题时,如果获取最先计算完毕线程的结果(通常计算最先完成的算法也是最优的)?
6. 一个线程执行完毕后,可以异步回调进行通知其他线程,不需要通过阻塞的方式?
7. 如和通过手工设置异步操作完成一个Future的执行?
针对上面的问题,在最新的concurrent框架下,我们可以找到答案。
在详细讲解之前,我们先看下面的这张思维导图(此图非本人制作,如有冒犯请联系博主),这张图可以说是非常适合大家理清concurrent框架的下的五大功能点:
atomic(原子变量相关)、tools(工具类)、locks(锁相关的内容)、executor(线程池相关)、coolections相关。
在详细讲解concurrent框架时,先以Callable接口为切入点,然后涉及Future分装返回结果,以及异步调用过程中线程状态监控,结果合并、结果依赖等相关的CompletableFuture超级功能类,最后衍生到locks和atomic等相关功能,因涉及的功能非常多,内容非常广泛,所有本博文主要详细介绍executor功能块。其他的在后续章节中会进行详细的描述。其实大家别被他这么庞大的类和接口吓到,我们抽丝剥茧把主要的几个核心的类和方法调用摸透了,其他的就用起来如鱼得水。在介绍的过程中,我会采用一个方法一个例子或者多个方法一个例子的方式,让看的明白。
Future类
首先我们介绍一下Future类,Future类是java1.5版本后新的类,主要方法用于检查计算是否完成,等待其完成,并检索计算结果。只有当计算完成时,才能使用方法get检索结果,必要时阻塞,直到准备好为止。取消由cancel方法执行。还提供了其他方法来确定任务是否正常完成或取消。一旦计算完成,就不能取消计算。
下面我们来看一下Future类的五个方法,首先我们将五个方法详细逻辑并介绍一下相关功能,后续我们会对每一个方法都有通过详细的例子在运行,让大家彻底明白他的真实意图。
1. boolean cancel(boolean mayInterruptIfRunning);
官方文档:试图取消当前任务的执行。如果任务已经完成、已经取消或由于其他原因无法取消,则取消失败,返回fase。并且在调用cancel时该任务还没有启动,则该任务应该永远不会运行。如果任务已经启动,那么mayInterruptIfRunning参数确定执行该任务的线程是否应该在试图停止该任务时被中断。
2. boolean isCancelled()
如果此任务在正常完成之前被取消,则返回true,如果没有取消则返回true
3. boolean isDone()
如果该任务完成,返回true。完成可能是由于正常终止、异常或取消——在所有这些情况下,该方法将返回true。
4. V get()
获取异步计算的结果,
5. V get(long timeout,TimeUnit unit)
获取异步计算的结果,并且设置最长等待时间,其中第一个参数是最长等待时间,第二个参数为时间单位。从这里我们可以看到设置线程等待时间TimeUnit 类也是Java官方的推荐的类,他比Thread.sleep();类对时间概念更加直观。
上面详细介绍了Future类的的五个方法,下面我们通过示例演示这五个方法的真正用途。
import java.util.concurrent.*;
public class TestThread {
public static void main(String[] args)throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
Future future = executorService.submit(()->{
return"thread starting。。。";
});
System.out.println(future.get());
}
}
get()方法用于获取异步计算后的结果,这个方法是阻塞的,如果结果没有返回,则线程会一致阻塞在哪里,下面我们分析一下get()方法的实现,get()的实现在Future 接口的实现类FutureTask中。首先在类中有一个用volatile关键字修饰的线程状态state字段。线程在启动后,通过FutureTask类的构造函数将state的初始值设置为NEW(也就是默认是0).
private volatile int state;
private static final int NEW =0;
private static final int COMPLETING =1;
private static final int NORMAL =2;
private static final int EXCEPTIONAL =3;
private static final int CANCELLED =4;
private static final int INTERRUPTING =5;
private static final int INTERRUPTED =6;
通过构造函数对state进行赋值操作,初始默认值为0.
public FutureTask(Callable callable) {
if (callable ==null)
throw new NullPointerException();
this.callable = callable;
this.state =NEW; // ensure visibility of callable
}
get()方法的具体实现,通过判断starte是否是完成状态,如果state的值小于1,则调用awaitDone进行等待,其中awaitDone()方法有俩个值,第一个参数是否使用等待时间,如果设置false,线程没有执行完毕,则一致阻塞在哪里。第二个参数是设置等待的时间,与第一个结合使用。
public V get()throws InterruptedException, ExecutionException {
int s =state;
if (s <=COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
然后我们在看awaitDone()方法的具体实现。我们可以看到,首先通过一个无限循化是判断线程是否被中断,如果没有被中断,则s的值赋值为2.
然后继续与COMPLETING进行比较,如果大于COMPLETING,并且WaitNode 为mull则直接返回state的当前值。
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);
}
}
接下来我们看isDone()方法,该方法主要是获取当前线程的运行状态结果,如果该线程运行结束则返回true,其中线程的结束有多种方式,包括线程的取消、异常或者正常结束都是线程的运行结束状态,在这里有好多初学者认为时只有线程正常运行结束后,才返回true,这是不正常的,尤其涉及线程运行结束获取结果进行判断时,一定要注意,下面我们通过多个例子进行演示获取当前线程运行结果状态。
public class TestIsDone {
public static void main(String[] args)throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
Future future = executorService.submit(()->{
System.out.println("thread starting ....");
});
Boolean flag =false;
do {
flag = future.isDone();
System.out.println(flag);
}while (!flag);
}
}
首先我们通过Executors类创建个线程池,Executors后期我们会讲解,他采用的是工厂方法模式。然后将线程提交到线程池,并将结果返回到Future中,在线程中我们只做个一个输出“thread starting。。。”。接下来我们获取当前线程运行的结果状态,在这里我为了方便显示不同情况下获取线程运行状态结果,通过一个循环获取每次isDone()执行的结果,通过控制台我们可以看到,线程在运行过程中,第一次获取false,这是由于异步执行过程中,第一次循序时,线程没有结束,所有获取的结果为false,第二次循环中,线程已经执行完毕,所以获取的结果为true。在实际运行中,不同电脑可能循环的次数不同。
在上面我们提到过,获取线程结束状态,他包含的不单是正常结束的线程返回true,取消的线程,异常终止的线程也会返回true,总之一句话,就是线程结束了,那么他就会返回true,具体是怎么结束的,这不是他关注的,下面我们通过修改上面的程序,在线程运行过程中进行取消,然后获取线程的状态。
package concurrent.d;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
public class TestIsDone_2 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
Future future = executorService.submit(()->{
for(int i=0;i<100;i++){
try {
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
});
System.out.println("获取当前线程运行结果状态 "+future.isDone());
future.cancel(true);
System.out.println("线程取消后获取当前线程运行结果状态 "+ future.isDone());
}
}
上面的程序我提交一个线程到线程池。为了在线程执行过程中我们能很直观的看到线程执行状态,我在循环打印1到100数据的时候,没次间隔1秒,然后我们获取当前线程是否处于完成状态,在第一次执行中,我们可以看到通过isDone方法返回false,表示当前线程正常执行,接着我们运行cancel()方法取消当前的线程,然后在通过isDone()方法获取当前线程的运行状态,结果返回为true,通过上述示例我们可以看到isDone()方法返回当前线程运行的结果状态,在返回ture的时候,他不但包含了线程的正常结束情况,也包括线程的取消或者异常情况。
最后一个方法我们来讨论一下isCancelled() 方法,该方法表示在实际业务中用来判断一个线程或者一个任务是否在完成前进行取消。
讲在最后:在接下来的几个章节中,我会用简单的例子把常用的函数功能进行详细解析透彻,随着进一步的深入,我们将实际业务中的示例带入进来,让大家更加明了。