1)一般线程在执行完任务之后只有等待被gc回收之后才会释放内存,此时线程会继续占据内存空间,如果不释放内存,那么线程一多就会导致占用内存过多(即内存溢出),因此线程池提供shutdown方法及时释放运行完线程任务的线程所占据的内存
2)提高线程的复用率,一般情况下当一个线程执行完线程任务后就会等待被gc回收,而线程池就会重复使用该线程执行其他线程任务.
线程池即为存放线程的容器,他可以帮助我们创建(此时线程已经在线程中创建) 销毁(用完了之后销毁) 复用线程(任务多的话让已经执行完任务的线程帮助其他线程执行未完成的任务)
1.execuator(线程池的最上端接口)
方法: void execuate(Runnable command); 执行线程任务
2.ExecutorService(1的子接口)
方法:1)void shutdown();关闭线程池
2)boolean isShutdown();获取线程是否关闭
3) Future submit(Callable task);提交任务
4)Future> submit(Runnable task);提交任务
3.ScheduledExecutorService(2的子接口)
方法:1)schedule(要执行的任务,延迟执行该任务的时间,延迟执行的时间单位);
2)scheduleFixedRate(要执行的任务,延迟执行该任务的时间,间隔时间,延迟时间单位)
对三参的备注:前一次任务开始时间 与下一次任务开始的时间的时间间隔
特殊情况:前一次任务执行时间大于时间间隔,那么下一次任务将等前一次执行完毕后紧随其后执行
3) scheduleWithFixedDelay(要执行的任务,延迟时间,间隔时间,延期时间单位)
对三参的备注:前一次任务结束时间与下一次任务开始的间隔
1)固定线程池:newFixedThreadPool();创建方式:executors.newFixedThreadPool();
2)可变线程池:newCachedThreadPool();创建方式:executors.newCachedThreadPool();
3)单例线程池:newSingleThreadExecutor();创建方式:executors.newSingleThreadExecutor();
4)调度线程池:创建方式:executors.newScheduledThreadPool(线程数量);
5)单例延迟线程池:线程池中只有一个线程,延迟一段时间后再执行
6)抢占线程池:处理较为耗时的任务
代码展示如下:
public static void main(String[] args) {
thod01();
}
public static void thod01() {
//创建固定线程池
//由于executor中没有提供可以直接创建线程池的类或者提供的实现子类参数太多(threadpoolexecutor(7参)),故提供了特殊创建线程池的类即executors
//而创建线程池的方法也就规定为类调用方法创建线程池.
//此处的ExecutorService线程名说明其下的所有方法都可以用线程池名调用
ExecutorService Pool = Executors.newFixedThreadPool(3);
//创建线程任务循环6次即创建6个线程任务
for (int i = 0; i < 4; i++) {
//提交线程任务
//需注意此时线程有3个,线程任务有6个,当再增加线程任务时,线程在执行完以前的任务后再执行新增加的任务.
Pool.submit(new Runnable() {
@Override
public void run() {
String name = Thread.currentThread().getName();
// TODO Auto-generated method stub
System.out.println(name + "线程启动");
try {
//抢着执行完输出语句后睡眠4秒
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//4s后三个线程在这继续抢占执行权,执行完毕后三个线程已经完成了三个任务,此时就会随机派一个线程出来执行完剩余的一个任务
System.out.println(name + "线程结束");
}
});
}
//所有任务完成后关闭线程池
//此为主线程,用于判断shutdown是在执行完所有任务后关闭程序
Pool.shutdown();
}
}
总结:1)该案例是将线程池创建在方法中,然后再main中调用方法来实现线程池的功能
2)由于JDK中提供的实现类(抽象类)或者创建线程对象时实现类中参数太多(threadpoolexecutor(7参))不利于创建线程对象,因此提供了特殊的用于创建线程池对象的类(executors),创建方法为:ExecutorService 线程池名= executors.new线程池类型,此时线程池名可以调用ExecutorService(子接口)下的所有方法
3)该案例创建了三个线程和4个线程任务(由for循环提供包括两个操作数据) ,因此线程任务创建在循环以内.
4)该案例执行流程细节:(三个线程同时执行任务)
4.1)三个线程在第一个操作数据前争夺执行权,然后依次执行第一个操作数据(因为没加锁)(线程启动).
4.2)执行完操作数据后休眠10s,然后再次抢夺第二个操作数据的执行权,然后依次执行第二个操作数据(也是因为没加锁)
4.3)当执行完以上流程后,4个任务已经被三个线程各执行了一次(即完成了3次任务),此时将随机派一个线程出来执行最后一次任务(由此可以看出在任务少的情况下如一个任务,线程池将随机派一个线程执行该任务),至此四个任务被3个线程全部执行完毕
4.4)执行结果如下:
pool-1-thread-2线程启动
pool-1-thread-1线程启动
pool-1-thread-3线程启动
pool-1-thread-2线程结束
pool-1-thread-1线程结束
pool-1-thread-3线程结束
pool-1-thread-3线程启动
pool-1-thread-3线程结束
5)pool.shutdown();表示 执行完所有任务后关闭线程池,不然只能等内存满了以后系统通过gc释放内存
5.1)shutdown关闭线程池,(注意此时shutdown属于主线程,停10s正常情况下主线程已经执行完毕,但结果中)分为两种情况
5.1.1)一种是在固定线程池中,shutdown将等待所有线程任务执行完毕后释放内存
5.1.2)一种是线程还没有执行完任务就直接被关闭
6)该线程任务的创建依靠线程池名调用submit(提交)方法,并在submit方法中重写run方法而创建.
代码展示如下:
public class Cachedpool {
public static void main(String[] args) {
method01();
}
public static void method01() {
//创建可变线程池
ExecutorService pool01 = Executors.newCachedThreadPool();
for (int i = 0; i < 4; i++) {
//通过submit重写run方法创建线程任务
pool01.submit(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
String name = Thread.currentThread().getName();
System.out.println(name+"线程已经启动");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(name+"线程已结束");
}
});
}
pool01.shutdown();
}
}
结合固定线程池总结可变线程池如下:
1)创建方式和固定线程池一致,区别在于对于n个任务,可变线程池会派出n个线程对象一次执行完毕
结果代码展示:
pool-1-thread-2线程已经启动
pool-1-thread-1线程已经启动
pool-1-thread-3线程已经启动
pool-1-thread-4线程已经启动
pool-1-thread-2线程已结束
pool-1-thread-1线程已结束
pool-1-thread-4线程已结束
pool-1-thread-3线程已结束
2)和固定线程池一样, shutdown会等待线程任务执行完毕后关闭程序
3)可变线程池创建的线程为前台线程
三参(只有延迟执行)代码展示:
public static void main(String[] args) {
method02();
}
public static void method02() {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
for (int i = 0; i < 2; i++) {
Runnable runnable = new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
String name01 = Thread.currentThread().getName();
long start = System.currentTimeMillis();
System.out.println(name01+"线程启动"+start);
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println(name01+"线程结束"+end);
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(end - start);
}
};
//4
pool.schedule(runnable, 10, TimeUnit.SECONDS);
}
pool.shutdown();
}
}
代码总结:
1)调度线程池的特点是通过pool.schedule(runnable, 4, TimeUnit.SECONDS)方法程序将在4后启动运行这是调度线程池相较于固定线程池 可变线程池开启线程池方式的不同之处(即它用pool.schedule开启线程池)
2)代码中第一个thread.sleep(6000)指在执行第一个操作语句后休眠6s再执行第二个操作语句,此时如果不加第二个thread.sleep(6000),则执行第二个操作语句后再操作第一个操作语句之间没有时间间隔,加了之后执行完第二个操作语句将再停6s执行第一个操作语句
3)这哥们只有延迟执行时间,而没有间隔时间
4)三参调度线程池shutdown等任务执行完毕后结束程序
5)相较于四参代码不循环执行而是执行完就结束
四参ATF(延迟+间隔)代码展示
public static void main(String[] args) {
method02();
}
public static void method02() {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
for (int i = 0; i < 2; i++) {
Runnable runnable = new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
String name01 = Thread.currentThread().getName();
long start = System.currentTimeMillis();
System.out.println(name01+"线程启动"+start);
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println(name01+"线程结束"+end);
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(end - start);
}
};
pool.scheduleAtFixedRate(runnable, 4, 10, TimeUnit.SECONDS);
}
//在调度线程池中不管任务有没有执行完,都结束任务
//pool.shutdown();
}
}
调度线程池四参案例分析总结:
1)相较于三参,四参中多了一个时间间隔(即上一次开始执行任务与下一次开始执行任务时的时间间隔)
2)shutdown不管程序有没有执行完直接结束任务
3)相较于三参不重复执行四参线程池中将不断重复执行任务
四参with代码展示
public class Schepool2 {
public static void main(String[] args) {
method02();
}
public static void method02() {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
for (int i = 0; i < 2; i++) {
Runnable runnable = new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
String name01 = Thread.currentThread().getName();
long start = System.currentTimeMillis();
System.out.println(name01+"线程启动"+start);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println(name01+"线程结束"+end);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(end - start);
}
};
pool.scheduleWithFixedDelay(runnable, 3, 8, TimeUnit.SECONDS);
}
//在调度线程池中不管任务有没有执行完,都结束任务
//pool.shutdown();
}
}
案例分析总结:
1)四参shutdown不等程序执行完直接结束程序
2)四参with将重复执行程序
3)四参with间隔时间指前一次任务的结束时间和后一次任务的开始时间
案例引入
public class SumMultiply {
public static void main(String[] args) {
test();
test01();
}
public static void test() {
ExecutorService pool01 = Executors.newCachedThreadPool();
pool01.submit(new Runnable() {
int sum = 0;
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 100; i++) {
sum+=i ;
}
System.out.println(sum);
}
});
}
public static void test01() {
ExecutorService pool02 = Executors.newCachedThreadPool();
pool02.submit(new Runnable() {
int multiply = 1 ;
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 1; i < 10; i++) {
multiply*=i ;
}
System.out.println(multiply);
}
});
}
}
总结:此时线程任务没有返回值因此不能将所得和以及阶乘值进行运算,因此需要用到有返回值的Callable
代码展示:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class SumMultiply2 {
public static void main(String[] args) {
ExecutorService p = Executors.newCachedThreadPool();
/*这里有三步
1)先用submit重写call方法 Ctrl+1;
2)再给泛型中写入返回值类型的包装类 Ctrl+1;
3)在封号后Ctrl+1*/
Future f1 = p.submit(new Callable() {
int sum = 0 ;
@Override
public Integer call() throws Exception {
// TODO Auto-generated method stub
for (int i = 1; i < 101; i++) {
sum+=i ;
}
return sum;
}
});
//p.shutdown();
Future f2 = p.submit(new Callable() {
int mul = 1 ;
@Override
public Integer call() throws Exception {
// TODO Auto-generated method stub
for (int i = 1; i < 11; i++) {
mul*=i ;
}
return mul;
}
});
try {
//此时获取值时用Ctrl+1
Integer sum1 = f1.get();
Integer mul1 = f2.get();
//在此在try catch中直接计算二者的和
System.out.println(sum1+mul1);
} catch (InterruptedException | ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
p.shutdown();
}
}
案例分析总结:
1)线程任务的优化Callable与Runnable的区别是一个有返回值一个没有
2)本案例直接在main方法中创建可变线程池,并用同一个线程池对象调用submit重写有返回值的Callable
3)在用返回调用get()返回值时会报错此时用Ctrl+1快捷进行try catch,同时在一个try catch中输出和阶乘相加的结果
4)get()会等任务结束后获取计算的结果值