多线程(4)- 线程间通信
前言
同步阻塞与异步非阻塞、单线程通信,多线程通信扩展:
概念
- JVM: Java虚拟机。
1、同步阻塞与异步非阻塞
- 同步阻塞:每个请求Event过来同步调用方法,执行一系列业务操作,最后返回结果。
- 客户等待时间长
- 系统吞吐量低
- 一个线程处理一个业务,线程频繁的创建开启与销毁,消耗资源多
- 业务峰值,大量的业务处理线程会导致频繁的CPU上下文切换,从而降低性能
- 异步非阻塞:每个请求过来,放入Event队列,主线程创建工单号,另外开启线程去处理工单号对应的业务,返回工单号。
- 等待时间短
- 提升系统吞吐量和并发量
- 服务端线程数量控制在一定范围,不会导致太多的CPU切换从而带来开销
- 服务端线程可以重复利用,减少了不断创建线程带来的资源浪费
- 异步缺陷:需要再调用一次获取结果(异步回调接口-比如支付业务)
2、单线程间通信
- 异步非阻塞如何知道Event队列有数据
- 笨方法就是不断去轮询查,有数据就处理,没有数据就等待后再轮询
- 通知机制,Event进队列就通知工作线程处理,没有就等待让工作线程休息
public class EventQueueMain {
static class Event {
}
/**
* 最大处理数 队列最大容量 10
*/
public int max = 10;
private final LinkedList<Event> eventQueue = Lists.newLinkedList();
public EventQueueMain(int max) {
this.max = max;
}
/***
* event请求进来
*/
public void offer(Event event) {
synchronized (eventQueue) {
if (eventQueue.size() > max) {
console(" queue already full");
try {
eventQueue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
console(" the new event is submitted");
eventQueue.addLast(event);
//当队列为空,请求一个Event,此时通知可以消费了
this.eventQueue.notify();
}
}
/**
* 处理event请求
*/
public void take() {
synchronized (eventQueue) {
if (CollectionUtils.isEmpty(eventQueue)) {
try {
console(" deal queue is empty");
eventQueue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Event event = eventQueue.removeFirst();
//当队列已满,处理一个,此时通知可以继续生产
this.eventQueue.notify();
console("the event " + event + " is handled");
}
}
public void console(String message) {
System.out.printf(" %s : %s \n", Thread.currentThread().getName(), message);
}
public static void main(String[] args) {
EventQueueMain eventQueueMain = new EventQueueMain(10);
new Thread(()->{
for (;;){
eventQueueMain.offer(new EventQueueMain.Event());
}
},"producer").start();
new Thread(()->{
for (;;){
eventQueueMain.take();
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"consumer").start();
}
}
producer : the new event is submitted
……
producer : the new event is submitted
producer : queue already full
consumer : the event com.today.roc.go.understand.thread.four.asyncNonBlocking.EventQueueMain$Event@3b66d775 is handled
producer : the new event is submitted
producer : queue already full
consumer : the event com.today.roc.go.understand.thread.four.asyncNonBlocking.EventQueueMain$Event@7f43d966 is handled
producer : the new event is submitted
producer : queue already full
2.1 Object.wait
public final void wait() throws InterruptedException {wait(0);}
//0代表着永不超时
public final native void wait(long timeout) throws InterruptedException;
//阻塞时间到了timeout自动唤醒
public final void wait(long timeout, int nanos) throws InterruptedException
wait方法必须拥有对象的monitor,必须在同步方法中使用
当前线程执行了该对象的wait方法后,会释放该对象的monitor,并进入到与该对象关联的wait set中,此时其它线程可以竞争对象的monitor所有权
notify之后,wait继续向下执行,应该内部逻辑从新拿到monitor所有权
2.2 Object.notify
唤醒单个执行该对象wait方法的线程
线程如果有执行该对象的wait方法,则会被唤醒,没有则会忽略
被唤醒线程必须重新获取对象所关联monitor的lock才能继续执行
2.3 wait、notify注意事项
- wait是可中断方法,interrupt会打断抛出InterruptedException,同时interrupt标识会被擦除
- 线程执行了某个同步对象的wait,会加入到对象的monitor关联的wait set中
- 当线程进入wait set之后,notify会将其唤醒,从wait set中弹出
- 同步方法中使用wait 和 notify方法,执行wait和notify的前提条件是必须持有同步方法monitor的所有权
- 同步代码的monitor必须与执行wait notify方法的对象一致,哪个对象的同步就用哪个对象的wait notify
2.4 wait sleep区别
- 都会产生阻塞,且均是可中断方法,中断后都可接收到InterruptedException
- wait 是 Object方法, sleep是Thread方法
- wait必须在同步方法中,sleep没有这个限制
- wait会释放monitor所有权,sleep不会释放
- sleep是一定时间后退出阻塞,wait可永久阻塞,可配合notify方法唤醒或中断退出阻塞
3、多线程间通信
- 多线程通信使用notifyAll,处于wait再满足时,多个生产者消费者都参与竞争
- 使用if只判断一次,就继续生产或消费,限制使用while循环,每次notifyAll之后,继续判断是否已经为空或超出边界。
public class MultiEventQueueMain {
static class Event {
}
/**
* 最大处理数 队列最大容量 10
*/
public int max = 10;
private final LinkedList<Event> eventQueue = Lists.newLinkedList();
public MultiEventQueueMain(int max) {
this.max = max;
}
/***
* event请求进来
*/
public void offer(Event event) {
console("come 0");
synchronized (eventQueue) {
console("sync 1");
while (eventQueue.size() > max) {
console(" queue already full");
try {
eventQueue.wait();
console("wait after 2");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
console(" the new event is submitted");
eventQueue.addLast(event);
//当队列为空,请求一个Event,此时通知可以消费了
this.eventQueue.notifyAll();
}
}
/**
* 处理event请求
*/
public void take() {
synchronized (eventQueue) {
while (CollectionUtils.isEmpty(eventQueue)) {
try {
console(" deal queue is empty");
eventQueue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Event event = eventQueue.removeFirst();
//当队列已满,处理一个,此时通知可以继续生产
this.eventQueue.notifyAll();
console("the event " + event + " is handled");
}
}
public void console(String message) {
System.out.printf(" %s : %s \n", Thread.currentThread().getName(), message);
}
public static void main(String[] args) {
MultiEventQueueMain eventQueueMain = new MultiEventQueueMain(10);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
for (; ; ) {
eventQueueMain.offer(new MultiEventQueueMain.Event());
}
}, "producer"+i).start();
new Thread(() -> {
for (; ; ) {
eventQueueMain.take();
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "consumer"+i).start();
}
}
}
4、自定义显示锁
多个线程竞争锁
public class BooleanLockTest {
private final Lock lock = new BooleanLock();
public void syncMethod() {
//使用try finally 保证lock被回收
try {
lock.lock();
int nextInt = current().nextInt(10);
System.out.println(currentThread() + " get lock " + nextInt);
TimeUnit.SECONDS.sleep(nextInt);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public List<Thread> getBlockThreads(){
return lock.getBlockThreads();
}
public static void main(String[] args) throws InterruptedException{
BooleanLockTest blt = new BooleanLockTest();
//multiLock();
interruptAble(blt);
getBlockThreads(blt);
}
/**
* interrupt able 可中断特性
*/
private static void interruptAble(BooleanLockTest blt) throws InterruptedException {
new Thread(()->{
blt.syncMethod();
},"T1").start();
TimeUnit.MILLISECONDS.sleep(2);
Thread t2 = new Thread(() -> {
blt.syncMethod();
}, "T2");
t2.start();
TimeUnit.MILLISECONDS.sleep(10);
t2.interrupt();
}
/**
* 多个线程争抢锁资源
*/
private static void multiLock(BooleanLockTest blt) {
IntStream.range(0, 10).mapToObj(v -> new Thread(blt::syncMethod)).forEach(Thread::start);
}
private static void getBlockThreads(BooleanLockTest blt) {
Thread thread = new Thread(() -> {
while (true) {
List<Thread> threads = blt.getBlockThreads();
String str = StringUtils.join(threads, ",");
System.out.printf(" block thread : %s \n", str);
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.setDaemon(true);
thread.start();
}
}
锁中断能力
线程被中断时,需要从阻塞队列中移除,避免内存泄漏
/**
* interrupt able 可中断特性
*/
private static void interruptAble(BooleanLockTest blt) throws InterruptedException {
new Thread(()->{
blt.syncMethod();
},"T1").start();
TimeUnit.MILLISECONDS.sleep(2);
Thread t2 = new Thread(() -> {
blt.syncMethod();
}, "T2");
t2.start();
TimeUnit.MILLISECONDS.sleep(10);
t2.interrupt();
}
block thread :
T1 :2021-02-28 15:35:44.516 : try lock success
T2 :2021-02-28 15:35:44.518 : try lock wait
Thread[T1,5,main] get lock 7
java.lang.InterruptedException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at com.today.roc.go.understand.thread.four.booleanlock.BooleanLock.lock(BooleanLock.java:48)
at com.today.roc.go.understand.thread.four.booleanlock.BooleanLockTest.syncMethod(BooleanLockTest.java:29)
at com.today.roc.go.understand.thread.four.booleanlock.BooleanLockTest.lambda$interruptAble$1(BooleanLockTest.java:60)
at java.lang.Thread.run(Thread.java:748)
block thread : Thread[T2,5,]
T1 :2021-02-28 15:35:51.524 : release the lock
获取锁超时
private static void getLockTimeout(BooleanLockTest blt) throws InterruptedException {
new Thread(()->{
blt.syncMethod(0);
},"T1").start();
TimeUnit.MILLISECONDS.sleep(2);
Thread t2 = new Thread(() -> {
blt.syncMethod(1000);
}, "T2");
t2.start();
}
block thread :
T1 :2021-02-28 15:56:04.566 : try lock success
Thread[T1,5,main] get lock 8
T2 :2021-02-28 15:56:05.570 : try lock wait remainderMills :-3
java.util.concurrent.TimeoutException: can not get lock during 1000 ms
at com.today.roc.go.understand.thread.four.booleanlock.BooleanLock.lock(BooleanLock.java:86)
at com.today.roc.go.understand.thread.four.booleanlock.BooleanLockTest.syncMethod(BooleanLockTest.java:34)
at com.today.roc.go.understand.thread.four.booleanlock.BooleanLockTest.lambda$getLockTimeout$1(BooleanLockTest.java:66)
at java.lang.Thread.run(Thread.java:748)
block thread :
T1 :2021-02-28 15:56:12.571 : release the lock