线程的等待和通知
Oject类中包含的方法
- wait() 让当前线程进入等待状态,直到被通知为止
- wait(long) 让当前线程进入等待状态,同时设置时间,直到被通知或时间结束
- notify() 随机通知一个等待线程
- notifyAll() 通知所有的等待线程
注意:调用等待和通知方法的对象必须是锁对象
wait和sleep方法的区别
- 调用对象不同
wait方法由锁对象调用
sleep方法由线程调用
- 锁使用不同
执行wait方法后,自动释放锁
执行sleep方法后,不会释放锁
- 唤醒机制不同
执行wait方法后,可以被通知唤醒
执行sleep后,只能等待时间结束后,自动唤醒
生产者消费者设计模式
- 生产者
某些程序/进程/线程负责生产数据就属于生产者
- 消费者
某些程序/进程/线程负责使用数据就属于消费者
生产者与消费者间的问题
- 耦合性高,生产者与消费者之间联系紧密,不利于系统的维护与扩展
- 并发性能低,同时能处理的请求量少
- 忙闲不均,生产者和消费者的(生产/消费)速度不一致,会带来系统资源的浪费
实现过程:
- 通过添加缓冲区,设置上限
- 生产者生产数据,向缓冲区存放,如果满了,生产者进入等待,直到缓冲区有空的位置再通知生产者生产
- 消费者取出数据进行消费,如果缓冲区空了,消费者进入等待,直到缓冲区有数据了再通知消费者消费
解决了忙闲不均的问题,提高了并发性和降低了能耦合性
class Baozi{
private int id;
public Baozi(int id) {
this.id = id;
}
@Override
public String toString() {
return "包子" + id;
}
}
public static final int MAX_COUNT = 100;
private List<Baozi> baozis = new ArrayList<>();
/**
* 做包子
*/
public synchronized void product() throws InterruptedException {
if (baozis.size() == MAX_COUNT){
System.out.println("包子太多了");
this.wait();
}else {
this.notifyAll();
}
Baozi baozi = new Baozi(baozis.size() + 1);
System.out.println(Thread.currentThread().getName() + "做了" + baozi);
baozis.add(baozi);
}
/**
* 吃包子
*/
public synchronized void eat() throws InterruptedException {
if (baozis.size() == 0){
System.out.println("包子没了,等待中");
this.wait();
}else {
this.notifyAll();
}
if (baozis.size() > 0){
Baozi baozi = baozis.remove(0);
System.out.println(Thread.currentThread().getName() + "吃了" + baozi);
}
}
public static void main(String[] args) {
BaoziShop baoziShop = new BaoziShop();
//1个生产者
Thread productor = new Thread(() -> {
for (int i = 0; i < 100; i++) {
try {
baoziShop.product();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//10个消费者吃10个包子
for (int i = 0; i < 10; i++) {
Thread consumer = new Thread(() ->{
try {
for (int j = 0; j < 10; j++) {
baoziShop.eat();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
consumer.start();
}
productor.start();
}
阻塞队列
应用了生产者消费者设计模式的集合,能根据数据的情况,自动对线程执行等待和通知命令
BlockingQueue接口
- put 添加数据,达到上限会自动让线程等待
- take 取,并删除数据,数据空了会自动让线程等待
实现类
ArrayBlockingQueue类 数据结构为数组
LinkedBlockingQueue类 数据结构为链表
static class Baozi{
private int id;
public Baozi(int id) {
this.id = id;
}
@Override
public String toString() {
return "包子" + id;
}
}
public static void main(String[] args) {
//阻塞队列
ArrayBlockingQueue<Baozi> baozis = new ArrayBlockingQueue<>(100);
//生产者线程
new Thread(() -> {
for (int i = 0; i < 200; i++) {
Baozi baozi = new Baozi(baozis.size() + 1);
try {
baozis.put(baozi);
System.out.println(Thread.currentThread().getName() + "生产了" + baozi);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
//取包子
for (int j = 0; j < 40; j++) {
try {
Baozi take = baozis.take();
System.out.println(Thread.currentThread().getName() +"消费了" + take);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
线程池
作用:回收线程资源
线程是一种宝贵的胸资源,执行完任务后会死亡,如果有大量任务需要处理,频繁的创建和销毁线程,会过度消耗系统资源,造成系统性能降低
线程池会保存一定量的线程,线程执行完任务后,会回到线程池中,等待执行下一个任务,节省系统资源,提升性能
线程池的使用
继承Executor
添加线程管理方法,如:shutdown()、shutdownNow()
Executors
用于创建线程池的工具类
主要方法
方法名 | 说明 |
---|---|
newCachedThreadPool() | 创建长度不限的线程池 |
newFixedThreadPool(int) | 创建长度固定的线程池 |
newSingleThreadExecutor() | 创建长度为一的线程 |
newScheduledThreadPool(int) | 创建固定长度的可以定时,周期性地执行任务的线程 |
public static void cachedThreadPool() throws InterruptedException {
//创建不限长度的线程池
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 1000; i++) {
int n = i;
//使用线程池启动线程
executorService.execute(() -> {
System.out.println(Thread.currentThread().getName() + "执行了任务" + n);
});
Thread.sleep(1);
}
executorService.shutdown();
}
public static void fixedThreadPool() throws InterruptedException {
//创建长度固定的线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
int n = i;
//使用线程池启动线程
executorService.execute(() -> {
System.out.println(Thread.currentThread().getName() + "执行了任务" + n);
});
Thread.sleep(1);
}
executorService.shutdown();
}
public static void singleThreadPool() throws InterruptedException {
//创建单一长度的线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 100; i++) {
int n = i;
//使用线程池启动线程
executorService.execute(() -> {
System.out.println(Thread.currentThread().getName() + "执行了任务" + n);
});
Thread.sleep(1);
}
executorService.shutdown();
}
public static void scheduledThreadPool() throws InterruptedException {
//获得可调度的线程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
//执行可调度任务
System.out.println("------------");
scheduledExecutorService.scheduleAtFixedRate(()->{
System.out.println(Thread.currentThread().getName()+"---->"+ LocalDateTime.now());
},1,3, TimeUnit.SECONDS);
}
public static void main(String[] args) throws InterruptedException {
// cachedThreadPool();
// fixedThreadPool();
// singleThreadPool();
scheduledThreadPool();
}
线程池的优化配置
线程池的实现类:ThreadPoolExecutor
线程池的构造方法参数:
-
corePoolsize 核心线程数,创建线程池时创建的,不会销毁
-
maximumPoolSize 最大线程数
-
keepAliveTime 线程存活时间,非核心线程空闲状态的时间,超过后销毁
-
timeUnit 时间单位
-
blockingQueue 阻塞队列,存放任务的集合
-
threadFactory 线程池创建线程使用的工厂
-
handler 线程池对拒绝任务的处理
优化配置
- 核心线程数应该与CPU内核数量相关,保证每个CPU都有执行1个或多个线程 CPU内核数*N(N和任务执行需要的时间和并发量相关)
- 最大线程数可以和核心线程数一样,避免频繁创建和小回县城
- 如果存在非核心线程,设置大一点,避免频繁创建和销毁线程
- 阻塞队列使用LinkedBlockingQueue,插入和删除任务效率更高
线程池的实现原理
思考:线程池是如何对线程进行回收利用的
所有的线程都保存在set集合中
当调用execute方法后,会判断当前线程数是否小于核心线程数,如果小于就会创建核心线程(执行addWorker方法),否则将任务添加到阻塞队列workQueue,如果阻塞队列满了,就创建非核心线程执行任务,如果线程创建失败(在addWorker方法中创建非核心线程时,如果当前线程大于最大线程数就返回false)就执行设置的拒绝方法
addWorker()方法会创建Worker对象存入HashSet集合,然后执行线程(runWorker)
在runWorker()会循环调用getTask()方法从阻塞队列中读取任务,只要getTask方法不返回null(即队列不为空),该线程就不会退出