等待和通知机制
可以通过锁对象调用Object类的方法:
1.wait() 让当前线程等待,直到被通知。
2.wait(long) 让线程等待,直到被通知或时间结束。
3.notify() 随机唤醒一个线程。
4.notifyAll() 唤醒所有线程。
注意:如果不是锁对象调用上面的方法会出现异常IllegalMonitorStateException。
简单的一个等待和通知案例
线程的交替输出,线程1负责输出A,线程2负责输出B,两个线程同时执行打印出ABABAB....
/**
* 线程交替输出
*/
public class ExchangeDemo1 {
public static synchronized void test1(){
while (true){
ExchangeDemo1.class.notify();
System.out.println("A");
try {
ExchangeDemo1.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static synchronized void test2(){
while (true){
ExchangeDemo1.class.notify();
System.out.println("B");
try {
ExchangeDemo1.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new Thread(()->test1()).start();
new Thread(()->test2()).start();
}
}
生产者消费者模式
是一种设计模式,用于解决两个点(线程、进程、服务器)之间数据通信的协调问题。
生产数据的点叫生产者,使用数据的点叫消费者,生产者和消费者可能存在速度不一致的情况。生产者速度过快,消费者消费速度慢,会浪费大量资源;反过来,消费者速度快,生产者速度慢,消费者浪费时间取等待。
实现步骤:
1.设置缓冲区,设定临界值。
2.生产者生产数据,存入缓冲区,如果缓冲区达到临界值,生产者就等待;否则就通知消费者进行消费。
3.消费者从缓冲区取出数据,如果缓冲区空了,消费者等待;否则就通知生产者继续生产。
作用:
1.解耦,生产者和消费者之间加入缓冲区,不需要直接调用。
2. 忙闲不均,协调生产者和消费者之间的速度。
3.节约资源,减少生产者资源的浪费,和消费者等待的时间。
编写一个简单的案例,模拟包子铺和顾客来买包子的案例
/**
* 包子的实体类
*/
public class Baozi {
private int id;
public Baozi(int id) {
this.id = id;
}
public Baozi() {
}
@Override
public String toString() {
return "--包子id" + id;
}
}
第一种方式:使用synchronized来实现
/**
* 使用synchronized实现生产者和消费者模式包子铺1.0
*/
public class BaoziStore {
//临界值
public static final int MAX_SIZE = 50;
//缓冲区
private List<Baozi> list = new ArrayList<>();
//生产包子
public synchronized void makeBaozi(){
//如果缓冲区满了,老板就等待顾客来买
if(list.size()==MAX_SIZE){
System.out.println("包子满了老板"+Thread.currentThread().getName()+"等待顾客来买");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
//通知消费者
this.notifyAll();
}
if(list.size()<MAX_SIZE){
//如果缓冲区没有满,就生产包子
Baozi baozi = new Baozi(list.size());
list.add(baozi);
System.out.println("老板" +Thread.currentThread().getName()+ "做了包子"+baozi+",总共有"+list.size()+"个包子");
}
}
//卖包子
public synchronized void takeBaozi(){
//判断如果包子铺没有包子,顾客就等待
if(list.size()==0){
System.out.println("包子空了顾客"+Thread.currentThread().getName()+"一直等");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
//通知生产者
this.notifyAll();
}
if(list.size()>0){
//如果包子铺有包子,就拿一个包子
Baozi baozi = list.remove(0);
System.out.println("顾客"+Thread.currentThread().getName()+"拿走了一个包子"+baozi+",还剩下"+list.size()+"个包子");
}
}
public static void main(String[] args) {
BaoziStore baoziStore = new BaoziStore();
new Thread(()->{
for (int i = 0; i < 100; i++) {
baoziStore.makeBaozi();
}
}).start();
for (int i = 0; i < 10; i++) {
new Thread(()->{
for (int j = 0; j < 10; j++) {
baoziStore.takeBaozi();
}
}).start();
}
}
}
第二种方式:使用同步锁的方式来实现
Lock的等待和通知
Condition 类可以提供锁对象类似的方法
创建:
Condition condition = lock.newCondition();
等待和通知:
await() 让线程等待
signal() 通知线程
signalAll() 通知所有线程
/**
* 使用同步锁实现生产者和消费者模式包子铺2.0
*/
public class BaoziStore2 {
//创建锁对象
private Lock lock = new ReentrantLock();
//获取Condition对象
private Condition condition = lock.newCondition();
//临界值
public static final int MAX_SIZE = 50;
//缓冲区
private List<Baozi> list = new ArrayList<>();
//生产包子
public void makeBaozi(){
try {
lock.lock();
//如果缓冲区满了
if (list.size() == MAX_SIZE) {
System.out.println("包子满了老板" + Thread.currentThread().getName() + "--等待顾客来买");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
//通知消费者
condition.signalAll();
}
if (list.size() < MAX_SIZE) {
//创建包子对象,存入缓存区
Baozi baozi = new Baozi(list.size());
list.add(baozi);
System.out.println("老板" + Thread.currentThread().getName() + "做了包子" + baozi + ",总共有" + list.size() + "个包子");
}
}finally {
lock.unlock();
}
}
//卖包子
public void takeBaozi(){
try {
lock.lock();
//判断如果包子铺没有包子,顾客等待
if (list.size() == 0) {
System.out.println("包子空了顾客" + Thread.currentThread().getName() + "一直等");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
//通知老板
condition.signalAll();
}
if (list.size() > 0) {
//从缓存区拿一个包子
Baozi baozi = list.remove(0);
System.out.println("顾客" + Thread.currentThread().getName() + "拿走了包子" + baozi + ",还剩下" + list.size() + "个包子");
}
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
BaoziStore2 baoziStore2 = new BaoziStore2();
new Thread(()->{
for (int i = 0; i < 100; i++) {
baoziStore2.makeBaozi();
}
}).start();
for (int i = 0; i < 10; i++) {
new Thread(()->{
for (int j = 0; j < 10; j++) {
baoziStore2.takeBaozi();
}
}).start();
}
}
}
阻塞队列
阻塞队列是一种特殊的集合,会自动阻塞对队列添加或删除数据的线程,也可以自动通知。
BlockingQueue接口
put(T) 添加数据到队尾,到临界值阻塞。
T take 从队头取出并删除数据,到空时阻塞。
常用实现类:
ArrayBlockingQueue 数组结构的阻塞队列
LinkedBlockingQueue 链表结构的阻塞队列
SynchronousQueue 同步机制的队列
使用阻塞队列来编写上述的包子铺案例
/**
* 阻塞队列的包子铺3.0
*/
public class BaoziStore3 {
//临界值
public static final int MAX_SIZE = 30;
//缓冲区
private BlockingQueue<Baozi> list = new ArrayBlockingQueue<>(MAX_SIZE);
//生产包子
public void makeBaozi(){
try {
//创建包子对象,存入缓存区
Baozi baozi = new Baozi(list.size());
list.put(baozi);
System.out.println("老板" + Thread.currentThread().getName() + "做了包子" + baozi + ",总共有" + list.size() + "个包子");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//卖包子
public void takeBaozi(){
//从缓存区拿出一个包子
try {
Baozi baozi = list.take();
System.out.println("顾客" + Thread.currentThread().getName() + "拿走包子" + baozi + ",还剩下" + list.size() + "个包子");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
BaoziStore3 baoziStore3 = new BaoziStore3();
new Thread(()->{
for (int i = 0; i < 50; i++) {
baoziStore3.makeBaozi();
}
}).start();
for (int i = 0; i < 10; i++) {
new Thread(()->{
for (int j = 0; j < 10; j++) {
baoziStore3.takeBaozi();
}
}).start();
}
}
}
ThreadLocal
线程局部变量,也是解决线程同步问题的一种方式。
锁机制 多个线程排队访问一个资源 以时间换空间。
ThreadLocal 多个线程并发访问自己独立的资源 以空间换时间。
ThreadLocal的原理
每个线程内部有Map集合ThreadLocalMap,获得数据时候返回当前线程的ThreadLocalMap,Map中有大量键值对Entry,键是当前的ThreadLocal对象,值是保存的数据。
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);
}
简单编写一个使用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());
}
}
补充:强软弱虚引用的区别?
强引用: 一般情况下创建的对象
Person person = new Person()
如果对象有引用指向,就不会被垃圾收集,没有引用指向就可能被垃圾收集。
person = null;
软引用: SoftReference
如果即将出现OOM,gc会优先回收软引用,比强引用更容易被回收。
主要用于缓存
弱引用:WeakReference
只要出现gc,都会进行回收。
可以缓存临时数据
虚引用: PhantomReference
形同虚设,不能作为对象的引用使用,主要用于跟踪垃圾收集器的活动。
强引用 > 软引用 > 弱引用 > 虚引用