线程的等待和通知
等待和通知是Object类下的方法,但只有锁对象才能调用,如:
wait():让当前线程进入等待状态,直到被通知才能执行;
wait(long):让当前线程进入等待状态,同时给他设置一个等待的时间,直到被通知或者时间结束;
notify():随机通知一个正在等待的线程,让其开始执行;
notifyAll():通知所有正在等待的线程
注意:必须是锁对象,否则会抛出IllegalMonitorStateException
/**
*
*通过锁对象将线程等待,5秒后通知该线程执行
*/
public class WaitDemo {
public synchronized void print() throws InterruptedException {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "--->" +i);
if(i == 50){
//让当前线程等待
this.wait();
}
}
}
public synchronized void notifyTest(){
//让等待的线程执行
this.notifyAll();
}
public static void main(String[] args) {
WaitDemo waitDemo = new WaitDemo();
new Thread(()->{
try {
waitDemo.print();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
waitDemo.notifyTest();
}
}
wait和sleep的区别
通过之前的学习我们都知道让线程进入阻塞还可以让其sleep,那么wait和他的区别是什么?
1.调用对象不同
wait()由锁对象来调用
sleep()由线程来调用
2.锁的使用不同
执行wait后,会自动释放锁
执行sleep后不会自动释放
3.唤醒机制不同
wait可以被通知方法唤醒
sleep只能等待时间结束,自动唤醒
线程池
我们可以从它是什么?有什么作用?来学习。
什么是线程?作用是什么?
我们都知道线程是一种宝贵的系统资源,执行完任务后会死亡,如果有大量的任务需要处理,那么就需要频繁的创建和销毁线程(线程的生命周期),造成系统性能降低。而线程池则会保存一定量的线程,不用频繁的创建和销毁,当线程池执行完任务后它会回到线程池等待下一个任务,不仅节约资源,还提升了性能;它的 作用就是将线程回收利用。
如何使用线程池?
线程池的API
Executor接口
ExecutorService接口
AbstractExecutorService抽象类
ThreadPoolExecutor线程池类
Executors工具类
(顶层接口是Executor,可以调用executor(Runnable)来启动一个线程执行任务)
ExecutorServcie
它是继承自Executor,可以添加线程池管理方法,如:shutdown()、shutdownNow()
Executors工具类
主要用于创建线程池的工具类,主要方法有:
方法名 | 说明 |
---|---|
newCachedThreadPool() | 返回线程数量无限的线程池 |
newFixedThreadPool(int ) | 返回固定长度的线程池,可以控制并发量 |
newSingleThreadExecutor() | 返回单一线程的线程池 |
newScheduledThreadPool(int) | 返回可调度的线程 |
我用几行代码展示一下用法:
public class ThreadPoolDemo {
public static void useCachedThreadPool(){
//创建长度不限的线程池
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
int n = i;
//使用线程池启动线程
executorService.execute(()->{
System.out.println(Thread.currentThread().getName()+"执行了任务"+n);
});
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
executorService.shutdown();
}
public static void useFixedThreadPool(){
//创建长度固定的线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
int n = i;
//使用线程池启动线程
executorService.execute(()->{
System.out.println(Thread.currentThread().getName()+"执行了任务"+n);
});
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
executorService.shutdown();
}
public static void useSingleThreadPool(){
//创建单一长度的线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
int n = i;
//使用线程池启动线程
executorService.execute(()->{
System.out.println(Thread.currentThread().getName()+"执行了任务"+n);
});
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
executorService.shutdown();
}
public static void useScheduledThreadPool(){
//创建可调度的线程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
System.out.println("----------");
scheduledExecutorService.scheduleAtFixedRate(()->{
System.out.println(Thread.currentThread().getName()+"---->"+ LocalDateTime.now());
}, 1,3, TimeUnit.SECONDS);
}
public static void main(String[] args) {
// useCachedThreadPool();
// useFixedThreadPool();
// useSingleThreadPool();
useScheduledThreadPool();
}
}
自定义线程池的配置
线程池的实现类:ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long KeepAliveTime,
TimeUnit unit,
BlockingQueue workQueue)
参数解释:
corePoolSize:核心线程数;
maximumPoolSize:最大线程数;
keepALiveTime:临时的线程能够存活的时间
TimeUnit :时间单位;
BlockingQueue :用于保存任务的阻塞队列
优化配置
1.核心线程数:应该和CPU内核数量相关(大于或等于),可以通过:Runtime.getRuntime().availableProcessors()来获取。
2.最大线程数可以和核心线程数相等,应避免频繁的创建和销毁线程
3.如果存在非核心线程,设置大一点,应避免频繁的创建和销毁线程
4.阻塞队列使用LinkedBlockingQueue的性能比较高
//cpu内核数
public static final int CPU_NUM = Runtime.getRuntime().availableProcessors();
/**
* 自定义配置线程池
*/
public static void testThreadPool(){
//配置线程池
ExecutorService pool = new ThreadPoolExecutor(CPU_NUM,CPU_NUM * 4 + 20,
0,TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>());
for(int i = 0;i < 100;i++){
final int n = i;
//使用线程池启动线程
pool.execute(()->{
System.out.println(Thread.currentThread().getName()+"执行了! i --> "+n);
});
}
//关闭线程池
pool.shutdown();
}
线程池的实现原理
所有的线程保存在HashSet中
生产者和消费者模式
在线程世界里,也有消费者和生产者,生产数据的线程是生产者,消费数据的线程是消费者;这是一种设计模式。
实现过程:
通过添加缓冲区,设置上限;生产者生产数据,向缓冲区存放,如果满了,生产者进入等待,直到缓冲区有空的位置通知生产者生产;消费者从缓冲区取数据进行消费,如果空了,消费者进入等待,直到缓冲区有数据再通知消费者消费。
解决问题:降低耦合性,提高了并发性能,解决忙线不均
package com.cn.wait;
import java.util.ArrayList;
import java.util.List;
public class BaoziShop {
/**
* 包子
*/
class Baozi{
private int id;
public Baozi(int id) {
this.id = id;
}
@Override
public String toString() {
return "香喷喷的包子" + id;
}
}
private int MAX = 100;
private List<Baozi> baozis = new ArrayList<>();
public synchronized void makeBaozi(){
//判断缓冲区是否满了
if (baozis.size()==100){
try {
System.out.println("缓冲区满了,还请等待");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
this.notifyAll();
}
//创建包子
Baozi baozi = new Baozi(baozis.size()+1);
System.out.println(Thread.currentThread().getName()+"做了"+baozi);
//保存到缓冲区
baozis.add(baozi);
}
public synchronized void takeBaozi(){
if(baozis.size()==0){
try {
System.out.println("缓冲区空了,还请等待");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}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();
Thread product = new Thread(() -> {
for (int i = 0; i < 100; i++) {
try {
baoziShop.makeBaozi();
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
for (int i = 0; i < 10; i++) {
Thread consumer = new Thread(()->{
for (int j = 0; j < 10; j++) {
baoziShop.takeBaozi();
}
});
consumer.start();
}
product.start();
}
}
上面的生产者消费者模式除了使用锁的等待和通知方式外,还可以使用阻塞队列,自动完成等待和通知功能。
阻塞队列
应用了生产者和消费者模式的集合,根据数据满或空,自动对线程执行等待和通知
BlockingQueue 接口:
put 添加数据,达到上限会自动让线程等待
take 取并删除数据,数据空了会自动让线程等待
实现类:
ArrayBlockingQueue 类 数据结构为数组
LinkedBlockingQueue类 链表结构
还是以包子铺为例
/**
* 包子铺
*/
public class BaoziShop2 {
/**
* 包子
*/
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) {
//阻塞队列
BlockingQueue<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 baozi = baozis.take();
System.out.println(Thread.currentThread().getName()+"消费了"+baozi);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
}