1、线程池
1.1、简介
池化技术:
- 不直接创建具体资源,而是创建一个池,在池里面创建具体的资源
- 以前直接把任务交给具体的资源,而现在把资源交给池,池就会让空闲的资源去执行任务,任务执行完之后,资源不会销毁,而是停留在池中,等待下一个任务来执行
简单来说,就是过去将run()方法让线程去某一线程,而现在是将run()交给池中的线程去执行,把以前线程所需要的资源都交给池。
传递多线程和线程池:
-
传统多线程
-
好处
多线程各自执行,互不影响
可以设置线程优先级
-
问题
多线程运行期间,系统不断地启动和关闭新线程,会过度消耗系统资源
多线程公共抢占CPU,CPU不断切换执行线程,会有过度切换线程的危险,从而导致系统崩溃
-
-
线程池
-
优势
降低系统资源过度消耗,通过重新使用已存在的线程,降低线程创建和消耗造成的消耗
方便线程并发数的管控
提供更强大的功能,延时定时线程池
-
问题
适合生存周期较短的任务,不适合又长又大的任务
无法给线程设置优先级
-
1.2、工作原理
在线程池的编译模式下,任务都提交到线程池,而不是将任务交给某一个线程去执行,线程池在拿到任务之后,会在池内寻找空闲的线程去执行任务,如有没有空闲的线程就等待。
一个线程同时只能执行一个任务,但是可以同时向线程池提交多个任务,待池内线程空闲就去执行
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y1nMabMs-1598958603575)(D:\briup-java\笔记\QQ截图20200901163902.png)]
线程池的本质就是将要执行的任务添加到队列中,然后在线程池中寻找空闲的线程来执行队列中的任务
1.3、线程池介绍
1.3.1、工作队列
线程池都是将要执行的任务交付给队列,然后再线程池中寻找空闲的线程去执行任务。
Java中主要采取BlockingQueue来存储任务
BlockingQueue----阻塞队列
双缓冲队列,内部使用两条队列,允许两个线程同时向队列一个存储,一个取出操作。(任务存储与取出)
在保证并发安全的同时,提高了队列的存储效率
BlockingQueue继承了Queue队列,遵循了队列先进先出的原则------FIFO
队列的基本操作:
- 添加元素
- 移除元素
- 取出元素(不移除)
队列在队尾添加元素在队头移除,取出数据
BlockingQueue是一个接口,具体的实现类如下
1.3.2、ArrayBlockingQueue
采用数组实现,因为数组创建时需要定义长度,所以在创建ArrayBlockingQueue也要规定其的大小。(其中对象是按照先进先出顺序排序)
1.3.2、LinkedBlockingQueue
采用双向链表实现,大小可以不固定,当然在创建LinkedBlockingQueue时可以指定大小,如果不指定会默认是Integer.MAX_VALUE(2的31次方-1)来决定。(其中对象也是按照先进先出的顺序排序的)
1.3.3、PriorityBlockingQueue
PriorityBlockingQueue和LinkedBlockingQueue类似,区别在于其中对象的排序是按照自然排序或者是比较器排序来决定的
1.3.4、SynchronousQueue
- 是一个特殊的BlockingQueue
- 每次删除操作都要等待传入操作
- 每次插入操作都要等待删除操作
- 一个容器,一旦有了插入线程和移除线程,那么当元素插入到队列中的时候,就会马上被移除出队列,相当于这个容器就是一个渠道,让这头的任务直流到另外一头,自己本身不存储元素
- 在多任务队列中,这是最快的处理任务方式
1.4、创建和使用线程池
java中使用ThreadPoolExecutor来表示线程池,创建该类对象就是创建了一个线程池
创建
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
public class ThreadPool {
@Test
public void ThreadPoolExecutorTest() {
/*
* corePoolSize 核心线程数 核心线程在不使用的情况之下也不会消耗,会一直存在线程中,所以JVM不会关闭,要想销毁,可以销毁线程池
* maximumPoolSize 池内最多可以存在多少个线程(其他线程=最大线程数-核心线程数) 其他线程在执行完任务之后会被销毁
* keepAliveTime 其他线程在执行完任务之后,可以存活在池中的时间
* unit 时间单位
* workQueue 所用的工作队列 任务都是存在在队列中等待空闲的线程将其执行
* */
new ThreadPoolExecutor(2, 6, 2, TimeUnit.SECONDS, new SynchronousQueue<>());
}
}
使用
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
public class ThreadPool {
@Test
public void ThreadPoolExecutorTest() {
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 6, 2, TimeUnit.SECONDS, new SynchronousQueue<>());
//创建 执行任务
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
//创建 执行任务
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
//关闭线程池
executor.shutdown();
}
}
结果
//pool-1-thread-1
//pool-1-thread-2
//pool-1-thread-1
//两个核心线程可以完成任务,不需要创建其他线程
1.5、常用线程池
Java通过Executors提供了四种线程池
newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可以灵活回收空闲线程,如无可回收,则新建线程
使用的队列是SynchronousQueue
没有核心线程,只有其他线程,并且其他线程执行完任务后可以存活60秒
简单来说就是,当有任务来的时候,就创建新地线程,当这个线程执行完之前有新的任务来就创建新的线程,如果这个线程在执行完到被销毁之前有新的任务到来,就继续重复使用这个线程
缓存型池 子通常用于执行一些生存期很短的异步型任务
@Test
public void test2() throws InterruptedException {
//没有核心线程池
//重复使用线程,一旦线程空闲就不创建,而是使用上一个任务遗留的线程去执行
//能不创建就不创建,只有迫不得已才会创建 最多创建2的31次方减一
ExecutorService exe = Executors.newCachedThreadPool();
for (int i = 0; i < 20; i++) {
Thread.sleep(1000);
exe.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
}
newFixedThreadPool
创建一个定长线程池,可控制线程的最大并发数,超过线程会在队列中等待
使用的队列是LinkedBlockingQueue
这个线程池中创建的都是核心线程(不会被回收,意味着JVM不会关闭,需要销毁线程池),所以在创建定长线程池的时候要确定好核心线程池数(size)
如果线程中有空闲的线程,队列中的任务就执行,如果没有就需要等待
@Test
public void test3() throws InterruptedException {
//里面都是核心线程
//线程池一旦创建就会执行size的核心线程
//jvm 不会关闭
// System.out.println(Runtime.getRuntime().availableProcessors());
ExecutorService pool = Executors.newFixedThreadPool(5);
for (int i = 0; i < 20; i++) {
//让程序睡上两秒在执行,方便查看结果
Thread.sleep(2000);
pool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
}
newSingleThreadExecutor
创建一个单线程化的线程,它只有唯一的核心线程来执行任务,这就意味着任务需要按照执行顺序(FIFO,LIFO,优先级)来执行,
使用的队列是LinkedBlockingQueue
因为只有一个线程,任务需要一个一个来执行,所以这就说明程序运行会慢
@Test
public void test4() {
//只有一个核心线程,没有其他线程
//按照顺序执行任务,但是慢
ExecutorService executor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
executor.execute(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println(Thread.currentThread().getName());
}
});
}
}
newScheduledThreadPool
定长线程池,支持定时及周期性任务执行,
它创建的是ScheduledThreadPoolExeutor,返回的是ScheduledExeutorService对象
ScheduledThreadPoolExecutor类中的构造器表明了需要在创建的时候指定核心线程池,最大线程数为 Integer.MAX_VALUE,其他线程存活时间为0,意味着使用完就被销毁。
使用的是DelayedWorkQueue队列,这是一种特殊的队列叫做优先级队列,它会对插入的数据进行优先级排序,保证优先级越高的数据首先被获取,与数据的插入顺序无关。
public static void main(String[] args) {
//增加了延迟执行,周期性执行
ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);
//延迟执行
// pool.schedule(new Runnable() {
// @Override
// public void run() {
// System.out.println(Thread.currentThread().getName());
// }
// },3,TimeUnit.SECONDS);
//延迟一秒后执行,每个两秒执行依次
pool.scheduleAtFixedRate(new Runnable() {
@Override public void run() {
System.out.println(Thread.currentThread().getName());
}
}, 1, 2, TimeUnit.SECONDS);
}
System.out.println(Thread.currentThread().getName());
// }
// },3,TimeUnit.SECONDS);
//延迟一秒后执行,每个两秒执行依次
pool.scheduleAtFixedRate(new Runnable() {
@Override public void run() {
System.out.println(Thread.currentThread().getName());
}
}, 1, 2, TimeUnit.SECONDS);
}