线程的等待和通知
-
wait() 让当前线程进入等待状态,直到被通知为止
-
wait(long) 让当前线程进入等待状态,同时设置时间;直到被通知为止或时间结束
-
notify() 随机通知一个等待线程
-
notifyAll() 通知所有的等待线程
等待再通知的实现
1.同步方法实现
public class WaitDemo {
public synchronized void print(){
for (int i = 0; i <100 ; i++) {
System.out.println(Thread.currentThread().getName()+"---"+i);
if (i==50) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public synchronized void notifyTest(){
this.notify();
}
public static void main(String[] args) {
WaitDemo waitDemo = new WaitDemo();
new Thread(()->{
waitDemo.print();
}).start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
waitDemo.notifyTest();
}
}
2.同步代码块实现
public class WaitDemo2 {
public void print(){
synchronized (this) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
if (i == 50) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public void notifyTest(){
synchronized (this) {
this.notify();
}
}
public static void main(String[] args) {
WaitDemo2 waitDemo = new WaitDemo2();
new Thread(()->{
waitDemo.print();
}).start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
waitDemo.notifyTest();
}
}
3.静态实现
public class WaitDemo3 {
public static void print(){
synchronized (WaitDemo3.class) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
if (i == 50) {
try {
WaitDemo3.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public static void notifyTest(){
synchronized (WaitDemo3.class) {
WaitDemo3.class.notify();
}
}
public static void main(String[] args) {
new Thread(()->{
print();
}).start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
notifyTest();
}
}
注意:等待和通知方法调用必须是锁对象,否则会抛illegalMonitorException
重点 :wait()和sleep()的区别
-
调用对象不同
wait() 由锁对象调用,sleep() 由线程调用
-
锁使用不同
执行wait后,自动释放锁,执行sleep后,不会释放锁
-
唤醒机制不同
执行wait后,可以被通知唤醒,执行sleep后,只能等待时间结束后,自动唤醒
生产者消费者模式
设计模式,不属于GOF23
-
生产者
某些程序/进程/线程负责生产数据就属于生产者,类似于地球中的植物,负责生产能量
-
消费者
某些程序/进程/线程负责使用数据就属于消费者,类似于地球中的动物,负责消耗掉能量
生产者与消费者之间存在的问题
耦合性高:生产者和消费者之间的联系过于精密,不适合代码的维护,一旦修改要改的地方过多
并发性能低:同时能处理的的请求量少
忙闲不均:有时会出现生产者多余消费者,会出现多余情况,有时会消费者过多,会出现不够的情况,会给系统资源带来浪费
解决方法:
-
通过添加缓冲区,设置上限
-
生产者生产数据,向缓冲区存放,如果满了,生产者进入等待,直到缓冲区有空的位置通知生产者生产;
-
消费者从缓冲区取数据进行消费,如果空了,消费者进入等待,直到缓冲区有数据再通知消费者消费。
public class BaoziShop {
/**
* 包子
*/
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 makeBaozi() throws InterruptedException {
//判断缓冲区是否满了
if(baozis.size() == MAX_COUNT){
System.out.printf("缓冲区满了,%s等待%n",Thread.currentThread().getName());
//让生产者线程等待
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 takeBaozi() throws InterruptedException {
//判断缓冲区是否空了
if(baozis.size() == 0){
System.out.printf("缓冲区空了,%s等待%n", Thread.currentThread().getName());
//让消费者等待
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();
//一个生产者
Thread productor = new Thread(() -> {
for (int i = 0; i < 100; i++) {
try {
baoziShop.makeBaozi();
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
productor.start();
//10个消费者吃10个包子
for (int i = 0; i < 10; i++) {
Thread consumer = new Thread(() ->{
try {
for (int j = 0; j < 10; j++) {
baoziShop.takeBaozi();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
consumer.start();
}
}
}
阻塞队列
应用了生产者消费者模式的集合,能够根据数据满或空的情况,自动对线程执行等待和通知
BlockingQueue 接口
-
put 添加数据,达到上限会自动让线程等待
-
take 取并删除数据,数据空了会自动让线程等待
实现类
ArrayBlockingQueue 类 数据结构为数组 ,适合于多读少改的情况
LinkedBlockingQueue类 链表结构,适合于少读多改的情况
public class BaoZiShop {
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);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"生产了"+baozi);
}
}).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();
}
}
}
线程池
作用:回收利用线程资源
线程是一种非常宝贵的资源,执行完任务后就会死亡,而有时候有大量任务需要执行,频繁的生成和销毁线程会对资源产生压力,造成巨大的浪费,造成系统性能的降低,线程池可以保存一定量的线程,将他们进行回收利用,这样可以对系统资源进行更高效的利用,线程执行完任务后,会回到线程池中,等待下一个任务,节省系统资源,提升性能。
线程池使用
顶层为一个接口:Executor
-
execute(Runnable) 启动线程执行一个任务
ExecuterService,继承了Executer,添加了线程管理的方法如 :shutdown()、shutdownNow()
Executors:用于创建线程池的工具类
主要方法
方法名 | 作用 |
newCachedThreadPool() | 创建长度不限的线程池 |
newFixedThreadPool(int ) | 创建固定长度的线程池 |
newSingleThreadExecutor() | 创建单一个数的线程池 |
newScheduledThreadPool(int) | 创建可以调度的线程池 |
线程池的优化配置
线程池的实现类
ThreadPoolExecutor
线程池的构造方法参数:
-
corePoolSize 核心线程数,创建线程池后自带线程,不会进行销毁
-
maximumPoolSize 最大线程数
-
keepAliveTime 存活时间,非核心线程能够闲置的时间,超过后被销毁
-
timeUnit 时间单位
-
blockingQueue 阻塞队列 存放任务(Runnable)的集合
优化
-
核心线程数 应该和CPU内核数量相关 CPU内核数 * N (N和任务执行需要时间和并发量相关)
-
最大线程数可以和核心线程数一样,避免频繁创建和销毁线程
-
如果存在非核心线程,设置大一点,避免频繁创建和销毁线程
-
阻塞队列使用LinkedBlockingQueue,插入和删除任务效率更高