1.线程池的用处
线程的诞生是为了解决进程的创建/销毁太重了,比较慢的问题。但是如果提高创建/销毁线程的频率,线程的开销也难以忽视。这时候我们可以使用线程池。
在使用第一个线程时,提前把第二个线程,第三个线程……给创建好,后续如果想要重新创建,就能直接从池子中拿来用了。
那么为什么从池子中取比直接创建一个线程要快呢?
从池子中取是用户态的操作;
创建新线程需要用户和内核态的相互配合;
操作系统是由内核和配套的应用程序构成的;2.java标准库中线程池的具体实现
2.Java标准库中线程池的具体实现
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo2_ExecutorService {
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
}
}
注意,线程池不是直接new的,而是通过一个专门的方法,反回了一个线程池对象。事实上。这其实是工厂模式。
通常创建对象,使用new,new关键字就会触发类的构造方法,但是有时候构造方法村在一定的局限性。
//考虑有这么个类
class Point {
public Point(double x, double y) { };//极坐标系
public Point(double x, double y) { };//直角坐标系
}//受重载规则制约,编译会出错
于是就需要工厂模式。使用工厂模式可以代替构造方法完成初始化工作,普通方法就可以使用方法的名字来区分了,也就不再受到重载的规则制约了。
实践中,一般单独搞一个类,给这个类弄一些静态方法,由这样的静态方法,负责构造出对象。
//大概率是这样的
class PointFactory {
public static Point makePointByXY(double x, double y) {
Point p = new Point();
p.setX(x);
p.setY(y);
return p;
}
public static Point makePointByRA(double r, double a);
}
//创建
Point p = PointFactory.makePointByXY(10,20);
创建线程的几种不同方法
1.ExecutorService service = Executors.newCachedThreadPool();
其中,executors是工厂类,newCachedThreadPool是工厂方法
cache缓存,用过之后不急着释放,先留着下次使用
此时构造出的线程池对象有一个基本的特点,即线程数目是能够动态适应的,随着往线程池里添加任务,这个线程池会根据需要自动地被创建出来。创建出来以后也不急着销毁,会在池子中保留一定的时间,以备随时再使用,
2.ExecutorService service = Executors.newFixedThreadPool(10);
该式表示创建好线程池后,池子中就有10个线程
至于在实际中究竟要设置多少个线程,要视情况而定
CPU核心数是N
1.CPU密集型,设置为N;
2.IO密集型,可以超过N。
但是我们无法知道一个代码具体多少内容是CPU密集型,多少是IO密集型。
正确做法:使用实验的方式,对程序进行性能测试,以此来获取那个值。
3.ExecutorService service = Executors.newSingleThreadExecutor();
只有一个线程的线程池
4.ExecutorService service = Executors.newScheduledThreadPool()
相当于定时器,但不是一个扫描线程负责执行任务了,而是有多个线程执行时间到的任务。
上述这几个工厂刚刚生成的线程池,本质上都是对一个类进行的封装,即ThreadPoolExecutor。
3.ThreadPoolExecutor的运用
有两个核心方法:
1.构造
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,BLockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
1.corePoolSize:核心线程数 ;maximumPoolSize:最大线程数;
这个线程池中的线程数量介于corePoolSize和maximumPoolSize之间
2.keepAliveTime:数值;unit:单位;
表示允许实习生摸鱼的最大时间
3.BLockingQueue<Runnable> workQueue:阻塞对列,用来存放线程池中的任务
可以根据需要,灵活设置这里的队列是什么。
4.RejectedExecutionHandler handler:线程池的拒绝策略
4.RejectedExecutionHandler handler:线程池的拒绝策略
一个线程池能够容纳的任务数量是由上限的,当持续地往线程池中添加任务时,一旦达到上限了,继续添加,会发生什么?
几种拒绝策略
1.ThreadPoolExecutor.AbortPolicy//直接抛异常
2.ThreadPoolExecutor.CallerRunsPolicy//新添加的任务,由添加任务的线程负责执行
3.ThreadPoolExecutor.Discard0ldestPolicy//丢弃任务队列中最老的任务
4.ThreadPoolExecutor.DiscardPolicy//丢弃当前新加的任务
2.注册任务(添加任务)
public void submit(Runnable runnable) {
}
4.模拟实现一个简单的线程池
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.BlockingQueue;
class MyThreadPool{
//任务队列
private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);
//通过这个方法,把任务添加到队列中
public void submit(Runnable runnable) throws InterruptedException {
queue.put(runnable);
}
public MyThreadPool(int n) {
Thread t = new Thread(()-> {
//让这个线程从队列中消费任务,并执行
Runnable runnable = null;
try {
runnable = queue.take();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
runnable.run();
});
t.start();
}
}
public class Demo3_MyThreadPool {
}