线程池
线程池是什么
进程本身已经能够做到并发编程,但由于进程太"重量"了,创建和销毁的成本比较高(需要申请和释放资源),线程可以看作是轻量级的进程(共用一组系统资源)。虽然如此,但是在更频繁的创建和释放的情况下,线程也抗不住了,因此要做进一步的优化,其中有两种方式:线程池和协程(纤程)
协程:轻量级线程
线程池:就是把线程创建好之后,放到池子里,需要使用线程,就直接从池子里取,而不是通过系统来创建,当线程用完了,就将线程还到池子里,而不是通过系统来销毁,通过线程池的方法来进一步提高效率
【为什么将线程放到池子里,从池子里取线程就要比从系统创建线程更高效呢?】
从池子里取线程,属于纯用户态操作,通过系统来创建线程,设及内核态操作,通常认为,牵扯到内核态的操作,就要比纯用户态的操作更低效
【为什么用户态操作比内核态操作更高效呢?】
线程池存在的意义:减少用户态和内核态之间的交互,把更多的工作放到用户态去完成,通过这种方式提高程序效率
协程:是一种轻量级的用户态线程。简单来说,线程的调度是由操作系统负责,线程的睡眠、等待、唤醒的时机是由操作系统控制,开发者无法决定。使用协程,开发者可以自行控制程序切换的时机,可以在一个函数执行到一半的时候中断执行,让出cpu,在需要的时候再回到中断点继续执行。因为切换的时机是由开发者来决定的,就可以结合业务的需求来实现一些高级的特性。
标准库中的线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class threadpool {
public static void main(String[] args) {
ExecutorService pool= Executors.newFixedThreadPool(10);
for(int i=0;i<100;i++){
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello pool");
}
});
}
}
}
执行结果:
虽然我们创建的线程池中只有10个线程,而我们线程池中submit了100个任务 因为线程池中的线程执行完一个任务后,可以接着执行下一个任务
Executors 创建线程池的几种方式
- newFixedThreadPool: 创建固定线程数的线程池
- newCachedThreadPool: 创建线程数目动态增长的线程池.
- newSingleThreadExecutor: 创建只包含单个线程的线程池.
- newScheduledThreadPool: 设定 延迟时间后执行命令,或者定期执行命令. 是进阶版的 Timer.
Executors里面的各种工厂方法,其实都是针对ThreadPoolExecutor这个类的进行了new,并且传入了不同风格的参数,来达到构造不同种类线程池的目的
工厂方法及工厂模式
newFixedThreadPool是Executors类的静态方法,借助静态方法来创建实例,像这样的方法,称为"工厂方法",对应的设计模式就叫做"工厂模式".
通常情况下,创建对象借助new关键字,调用构造方法来实现的,但是在C++/Java中的构造方法,有很多的限制,在很多时候不方便使用,因此就需要给构造方法在包装一层,外面起到包装作用的方法就是工厂方法
构造方法受到的限制,比如构造方法的名字必须和类名相同,如果想要实现不同版本的构造,就需要重载,而重载又要受到限制(参数类型,个数,顺序不同)
通过工厂模式解决上述问题:
//表示一个点
class Point{
double x;
double y;
double r;
double a;
public void setX(double x) {
this.x = x;
}
public void setY(double y) {
this.y = y;
}
public void setR(double r) {
this.r = r;
}
public void setA(double a) {
this.a = a;
}
public static Point makePointByXY(double x, double y){
Point point=new Point();
point.setX(x);
point.setY(y);
return point;
}
public static Point makePointByRA(double r,double a){
Point point=new Point();
point.setR(r);
point.setA(a);
return point;
}
}
实现线程池
- 简单实现成可指定线程个数的线程池
- 核心操作为 submit, 将任务加入线程池中
- 使用 Runnable 描述一个任务
- 使用一个 BlockingQueue 组织所有的任务
- 每个 worker 线程要做的事情: 不停的从 BlockingQueue 中取任务并执行.
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class MyThreadPool {
private BlockingQueue<Runnable> queue=new LinkedBlockingQueue<>();
public void submit(Runnable runnable) throws InterruptedException {
queue.put(runnable);
}
//在构造方法中创建线程,让这些线程来执行任务
public MyThreadPool(int n){
for(int i=0;i<n;i++){
Thread worker=new Thread(()->{
while(!Thread.currentThread().isInterrupted()){
try {
Runnable runnable = queue.take();
runnable.run();
} catch (InterruptedException e) {
e.printstacktrace();
break;
}
}
});
worker.start();
}
}
public static void main(String[] args) throws InterruptedException {
MyThreadPool pool=new MyThreadPool(10);
for (int i = 0; i < 30; i++) {
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello MyPool");
}
});
}
}
}
执行结果:
【补充】:
线程池和字符串常量池都是广义的概念
Java里面JVM中实现了字符串常量池,我们在自己写的Java业务代码中,也能实现自己版本的字符串常量池
线程池在Java标准库中提供里线程池的实现,我们自己也可以写自己版本的实现