最近看了咕泡学视频教程,这里算是做一个记录。
一、JDK1.5之前时代
创建线程的方式
继承thread
package study.java5;
/**
* @Auther: zhw
* @Date: 2018/10/28 11:16
* @Description: 1.5多线程方式1
*/
public class ThreadDemo {
public static void main(String[] args) {
Demo1 demo1 = new Demo1();
demo1.start();
}
}
class Demo1 extends Thread
{
@Override
public void run() {
System.out.println("线程开始执行");
}
}
实现Runnable接口
package study.java5;
/**
* @Auther: zhw
* @Date: 2018/10/28 12:29
* @Description: //TODO
*/
public class RunnableDemo {
public static void main(String[] args) {
Demo2 demo2 =new Demo2();
Thread thread = new Thread(demo2);
thread.start();
}
}
class Demo2 implements Runnable{
@Override
public void run() {
System.out.println("线程开始执行");
}
}
1.5总结:
1、缺少线程池的管理
2、想获取线程执行的结果非常麻烦
二、JDK1.5时代
2.1、Executors
这个可以算是线程的管理,其中包含各种线程服务
ExecutorService:最常见的执行者。生命周期
ScheduledExecutorService:带有任务调度的执行者
ThreadFactory:线程工厂,用于创建新线程
2.2、ExecutorService线程服务
public class ExecutorsExecutorService {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
Future<String> future = executorService.submit(new Callable<String>(){
@Override
public String call() throws Exception {
return "first executorService";
}
});
//do other thing
String result = "";
try {
result = future.get();
}catch (Exception e) {
e.printStackTrace();
}
System.out.println(result);
}
}
如上面例子,创建了一个数量为2的线程池,返回了ExecutorServie,ExecutorServie调用了submit方法产生了一个Future结果.
区分Callable和Future
Callable:是源头,是任务,是产生结果的。
Future:Future是用于获取结果的。由于Callable的调用是异步的,也就是说结果不知道什么时候产生,也就是这个单词的意思“未来”,这是个未来结果。
详解Future
- get:这个方法会一直等待结果的完成,然后获取结果,这个方法还支持等待的最大时间,超过这个时间就不继续等待。
- isDone:判断结果是否完成
- cancel:取消任务,未开始或已完成返回false,参数表示是否中断执行中的线程 更详细的描述
- isCancelled:是否取消了。
除了submit还有invokeAll(也就是批量)、invokeAny(这个有点用)、execute,看jdk源码或者名字都很好理解
其他服务就不介绍了。
在1.5中已经出现了线程池、线程管理、获取线程的执行结果,已经很方便了,所以jdk1.5是个经典。
缺点:
多个线程之间交互需要提高(但是还是不影响1.5是经典)
三、JDK1.7
ForkJoinPool
背景:ForkJoinPool的优势在于,可以充分利用多cpu,多核cpu的优势,把一个任务拆分成多个“小任务”,把多个“小任务”放到多个处理器核心上并行执行;当多个“小任务”执行完成之后,再将这些执行结果合并起来即可。这种思想值得学习。
这里特别推荐一篇文章,写的非常好=====>精彩博文
主要有两个类:RecursiveAction和RecursiveTask
3.1 RecursiveAction:递归没有返回结果的任务
下面演示一个打印任务
public static void main(String[] args) throws Exception{
PrintTask task = new PrintTask(0, 100);
//创建实例,并执行分割任务
ForkJoinPool pool = new ForkJoinPool();
pool.submit(task);
//线程阻塞,等待所有任务完成
pool.shutdown();
pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
System.out.println("线程池已经关闭");
}
class PrintTask extends RecursiveAction{
private static final int THRESHOLD = 10; //最多只能打印50个数
private int start;
private int end;
public PrintTask(int start, int end) {
super();
this.start = start;
this.end = end;
}
@Override
protected void compute() {
if(end - start < THRESHOLD){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i=start;i<end;i++){
System.out.println(Thread.currentThread().getName()+"的i值:"+i);
}
}else {
int middle =(start+end)/2;
PrintTask left = new PrintTask(start, middle);
PrintTask right = new PrintTask(middle, end);
//并行执行两个“小任务”
left.fork();
right.fork();
}
}
}
3.2 RecursiveTask:递归有返回结果的任务
public static void main(String[] args) throws Exception {
int[] arr = new int[100];
Random random = new Random();
int total =0;
//方式一、初始化100个数组元素,并且通过单线程计算出总和
for(int i=0,len = arr.length;i<len;i++){
int temp = random.nextInt(20);
//对数组元素赋值,并将数组元素的值添加到sum总和中
total += (arr[i]=temp);
}
System.out.println("初始化数组总和:"+total);
//方式二、通过递归多线程计算
SumTask task = new SumTask(arr, 0, arr.length);
ForkJoinPool pool = ForkJoinPool.commonPool();
Future<Integer> future = pool.submit(task); //提交分解的SumTask 任务
System.out.println("多线程执行结果:"+future.get());//这里会阻塞线程,等待所有子线程结束。
pool.shutdown(); //关闭线程池
}
class SumTask extends RecursiveTask<Integer>{
private static final int THRESHOLD = 20; //每个小任务 最多只累加20个数
private int arry[];
private int start;
private int end;
/**
* Creates a new instance of SumTask.
* 累加从start到end的arry数组
* @param arry
* @param start
* @param end
*/
public SumTask(int[] arry, int start, int end) {
super();
this.arry = arry;
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
int sum =0;
//当end与start之间的差小于threshold时,开始进行实际的累加
if(end - start <THRESHOLD){
System.out.println(Thread.currentThread().getName()+"start:"+start+",end:"+end);
for(int i= start;i<end;i++){
sum += arry[i];
}
return sum;
}else {//当end与start之间的差大于threshold,即要累加的数超过20个时候,将大任务分解成小任务
int middle = (start+ end)/2;
SumTask left = new SumTask(arry, start, middle);
SumTask right = new SumTask(arry, middle, end);
//并行执行两个 小任务
left.fork();
right.fork();
//把两个小任务累加的结果合并起来
return left.join()+right.join();
}
}
}
对于累加等操作jdk1.8提供了其他的类:LongAccumulator.
解释几个概念:
3.3 shutdown
中文解释: 开始一个有序的关闭,在关闭过程中以前提交的任务还是会被执行,
但是不会 接受新的提交的任务。
英文解释:
/**
* Possibly initiates an orderly shutdown in which previously
* submitted tasks are executed, but no new tasks will be
* accepted. Invocation has no effect on execution state if this
* is the {@link #commonPool()}, and no additional effect if
* already shut down. Tasks that are in the process of being
* submitted concurrently during the course of this method may or
* may not be rejected.
*/
3.4 shutdownNow
大致和shutdown相同,但是它会取消现有的和未执行的任务,非常符合方法名字的定义
英文解释:
/**
* Possibly attempts to cancel and/or stop all tasks, and reject
* all subsequently submitted tasks.
3.5 awaitTermination
它会阻塞当前线程,除非发生下面任何一种情况
1、在发出shutdown请求后,所有的任务都完成
2、出现timeout的情况
3、当前线程被中断。
所以在ForkJoinPool中经常看到这样的代码
//发出关闭线程请求,但是已提交的不会终止
pool.shutdown();
//等待子任务执行完成,如果子任务执行完成,线程会关闭
pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
或者
pool.shutdown();
while (!pool.awaitTermination(1, TimeUnit.SECONDS)) {
System.out.println("子线程还没有执行完成");
}
System.out.println("子线程池执行完成,线程池也关闭了");