生产者消费者模式
是一种设计模式,用于解决两个点(线程、进程、服务器)之间数据通信的协调问题。
生产数据的点叫生产者,使用数据的点叫消费者,生产者和消费者可能存在速度不一致的情况。生产者速度过快,消费者消费速度慢,会浪费大量资源;反过来,消费者速度快,生产者速度慢,消费者浪费时间取等待。
过程:
- 设置缓冲区,设定临界值
- 生产者生产数据,存入缓冲区,如果缓冲区达到临界值,生产者就等待;否则就通知消费者进行消费
- 消费者从缓冲区取出数据,如果缓冲区空了,消费者等待;否则就通知生产者继续生产
解决的问题:
1. 解耦,生产者和消费者之间加入缓冲区,不需要直接调用
2. 忙闲不均,协调生产者和消费者之间的速度
3. 节约资源,减少生产者资源的浪费,和消费者等待的时间
等待和通知机制
锁对象:
- 同步方法
-
- 静态方法 : 类.class
- 实例方法: this
同步代码块
写在synchronized括号中的对象
可以通过锁对象调用Object类的方法:
- wait() 让当前线程等待,直到被通知
- wait(long) 让线程等待,直到被通知或时间结束
- notify() 随机唤醒一个线程
- notifyAll() 唤醒所有线程
注意:如果不是锁对象调用上面的方法会出现异常IllegalMonitorStateException
/**
* 等待和通知案例
*/
public class WaitDemo {
//测试等待
public synchronized void testWait(){
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"执行了"+i);
if(i == 5){
System.out.println(Thread.currentThread().getName()+"进行等待");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//测试通知
public synchronized void testNotify(){
this.notifyAll();
}
public static void main(String[] args) {
WaitDemo demo = new WaitDemo();
//启动线程测试等待
for(int i = 0;i < 5;i++) {
new Thread(() -> {
demo.testWait();
}).start();
}
//5秒后主线程通知
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
demo.testNotify();
}
}
使用Lock实现
Lock的等待和通知
Condition 类可以提供锁对象类似的方法
创建:
Condition condition = lock.newCondition();
等待和通知:
await() 让线程等待
signal() 通知线程
signalAll() 通知所有线程
/**
* 包子铺
*/
public class BaoziStore2 {
//创建锁对象
private Lock lock = new ReentrantLock();
//获得Condition对象
private Condition condition = lock.newCondition();
//临界值
public static final int MAX_SIZE = 50;
//缓冲区
private List<Baozi> baozis = new ArrayList<>();
//做包子
public void makeBaozi(){
try {
//上锁
lock.lock();
//如果缓冲区满了,生产者等待
if (baozis.size() == MAX_SIZE) {
System.out.println("包子铺满了,生产者" + Thread.currentThread().getName() + "等待");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
//通知所有消费者
condition.signalAll();
}
if (baozis.size() < MAX_SIZE) {
//创建包子对象,存入缓冲区
Baozi baozi = new Baozi(baozis.size());
baozis.add(baozi);
System.out.println("生产者" + Thread.currentThread().getName() + "做了" + baozi + ",总数:" + baozis.size());
}
}finally {
//释放锁
lock.unlock();
}
}
//拿包子
public void takeBaozi(){
try {
lock.lock();
//如果缓冲区空了,消费者等待
if (baozis.size() == 0) {
System.out.println("包子铺空了,消费者" + Thread.currentThread().getName() + "等待");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
//通知生产者生产
condition.signalAll();
}
if (baozis.size() > 0) {
//从缓冲区拿出一个包子
Baozi baozi = baozis.remove(0);
System.out.println("消费者" + Thread.currentThread().getName() + "拿了" + baozi + ",总数:" + baozis.size());
}
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
BaoziStore2 store = new BaoziStore2();
new Thread(()->{
for (int i = 0; i < 100; i++) {
store.makeBaozi();
}
}).start();
for (int i = 0; i < 10; i++) {
new Thread(()->{
for (int j = 0; j < 10; j++) {
store.takeBaozi();
}
}).start();
}
}
}
阻塞队列
是一种特殊的集合,会自动阻塞对队列添加或删除数据的线程,也可以自动通知
BlockingQueue接口
put(T) 添加数据到队尾,到临界值阻塞
T take 从队头取出并删除数据,到空时阻塞
常用实现类:
ArrayBlockingQueue 数组结构的阻塞队列
LinkedBlockingQueue 链表结构的阻塞队列
SynchronousQueue 同步机制的队列
//创建阻塞队列 临界值为5
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(5);
new Thread(()->{
for(int i = 0;i < 100;i++){
System.out.println(Thread.currentThread().getName()+"添加"+i+",size:" + queue.size());
try {
queue.put(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(()->{
for(int i = 0;i < 100;i++){
try {
Integer data = queue.take();
System.out.println(Thread.currentThread().getName()+"取出"+data+",size:" + queue.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
使用阻塞队列实现
/**
* 包子铺
*/
public class BaoziStore3 {
//临界值
public static final int MAX_SIZE = 50;
//缓冲区 使用阻塞队列实现
private BlockingQueue<Baozi> baozis = new ArrayBlockingQueue<>(MAX_SIZE);
//做包子
public void makeBaozi(){
//创建包子对象,存入缓冲区
try {
Baozi baozi = new Baozi(baozis.size());
baozis.put(baozi);
System.out.println("生产者" + Thread.currentThread().getName() + "做了" + baozi + ",总数:" + baozis.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//拿包子
public void takeBaozi(){
//从缓冲区拿出一个包子
try {
Baozi baozi = baozis.take();
System.out.println("消费者" + Thread.currentThread().getName() + "拿了" + baozi + ",总数:" + baozis.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
BaoziStore3 store = new BaoziStore3();
new Thread(()->{
for (int i = 0; i < 100; i++) {
store.makeBaozi();
}
}).start();
for (int i = 0; i < 10; i++) {
new Thread(()->{
for (int j = 0; j < 10; j++) {
store.takeBaozi();
}
}).start();
}
}
}
ThreadLocal
线程局部变量,也是解决线程同步问题的一种方式
锁机制 多个线程排队访问一个资源 以时间换空间
ThreadLocal 多个线程并发访问自己独立的资源 以空间换时间
使用ThreadLocal
public class ThreadLocalDemo {
//线程共享的变量
static volatile int value = 1;
public static void main(String[] args) {
//线程本地变量
ThreadLocal<Integer> local = new ThreadLocal<Integer>(){
//设置初始值
@Override
protected Integer initialValue() {
return 1;
}
};
for(int j = 0;j < 3;j++) {
new Thread(() -> {
for (int i = 0; i < 10; i++) {
value++;
local.set(local.get() + 1);
}
System.out.println(Thread.currentThread().getName()+" value=" + value);
System.out.println(Thread.currentThread().getName()+" local=" + local.get());
}).start();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" value="+value);
System.out.println(Thread.currentThread().getName()+" local="+local.get());
}
}
ThreadLocal的原理
每个线程内部有Map集合ThreadLocalMap,获得数据时候返回当前线程的Map集合,局部变量是保存到当前线程的Map中的
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}