同步阻塞与异步阻塞
单线程间通信
- 轮询机制:不断地轮询,如果有数据则读取数据并处理,如果没有则等待若干时间再次轮询
- 通知机制:如果队列中有Event,则通知工作的线程开始工作,没有Event,则工作线程休息并等待通知
wait 和 notify
简单的生产者消费者
package com.example.demo.thread;
import java.util.LinkedList;
public class EventQueue {
private final int max;
static class Event {
}
private final LinkedList<Event> eventQueue = new LinkedList<>();
private final static int DEFAULT_MAX_EVENT = 10;
public EventQueue() {
this(DEFAULT_MAX_EVENT);
}
public EventQueue(int max) {
this.max = max;
}
public void offer(Event event) {
synchronized (eventQueue) {
if (eventQueue.size() >= max) {
try {
console("the queue is full.");
eventQueue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
console("the new event is submitted.");
eventQueue.addLast(event);
eventQueue.notify();
}
}
public Event take() {
synchronized (eventQueue) {
if (eventQueue.isEmpty()) {
try {
console("the queue is empty.");
eventQueue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Event event = eventQueue.removeFirst();
this.eventQueue.notify();
console("the event "+ event + " is handled.");
return event;
}
}
private void console(String msg) {
System.out.printf("%s:%s\n",Thread.currentThread().getName(), msg);
}
}
package com.example.demo.thread;
import java.util.concurrent.TimeUnit;
public class EventClient {
public static void main(String[] args) {
final EventQueue eventQueue = new EventQueue();
new Thread(()-> {
for (; ;) {
eventQueue.offer(new EventQueue.Event());
}
}, "Producer").start();
new Thread(()->{
for (; ; ) {
eventQueue.take();
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "Consumer").start();
}
}
//result
Consumer:the queue is empty.
Producer:the new event is submitted.
Producer:the new event is submitted.
Producer:the new event is submitted.
Producer:the new event is submitted.
Producer:the new event is submitted.
Producer:the new event is submitted.
Producer:the new event is submitted.
Producer:the new event is submitted.
Producer:the new event is submitted.
Producer:the new event is submitted.
Producer:the queue is full.
Consumer:the event com.example.demo.thread.EventQueue$Event@49068e0c is handled.
Producer:the new event is submitted.
Producer:the queue is full.
Consumer:the event com.example.demo.thread.EventQueue$Event@7eaac23c is handled.
Producer:the new event is submitted.
Producer:the queue is full.
Consumer:the event com.example.demo.thread.EventQueue$Event@2ae281ba is handled.
Producer:the new event is submitted.
Producer:the queue is full.
Consumer:the event com.example.demo.thread.EventQueue$Event@14b5cfee is handled.
Producer:the new event is submitted.
wait 和 notify方法详解
public final void wait() throws InterruptedException
public final native void wait(long timeout) throws InterruptedException
public final void wait(long timeout, int nanos) throws InterruptedException
wait
方法的三个重载方法都将调用wait(long timeout)
这个方法,wait()
方法等价于wait(0)
,0 代表着永不超时Object
的wait(long timeout)
方法会导致当前线程进入阻塞,直到有其他线程调用了Object
的notify
或者notifyAll
方法才能将其唤醒,或者阻塞时间到达了timeout而自动唤醒wait
方法必须拥有该对象的monitor
,也就是wait
方法必须在同步方法中使用- 当前线程执行了该对象的
wait
方法之后,将会放弃对该monitor
的所有权并且进入与该对象关联的wait set
中,也就是说一旦线程执行了某个Object
的wait
方法之后,它将会释放对该对象monitor
的所有权,其他线程也会继续争抢该monitor
的所有权
public final native void notify();
- 唤醒单个正在执行对象
wait
方法的线程 - 如果有某个线程由于执行该对象的
wait
方法而进入阻塞则会被唤醒,如果没有则会忽略 - 被唤醒的线程需要重新获取对该对象所关联
monitor
的lock
才能继续执行
wait 和 notify的注意事项
wait
方法是可中断方法,这也就意味着,当前线程一旦调用了wait
方法进入阻塞状态,其他线程是可以使用interrupt
方法将其打断的;可中断方法被打断后会收到中断异常InterruptedException
,同时interrupt
标识也会被擦除。- 线程执行了某个对象的
wait
方法以后,会加入与之对应的wait set
中,每一个对象的monitor
都有一个与之关联的wait set
- 当线程进入
wait set
之后,notify
方法可以将其唤醒,也就是从wait set
中弹出,同时中断wait
中的线程也会将其唤醒 - 必须在同步方法中使用
wait
和notify
方法,因为执行wait
和notify
的前提条件是必须持有同步方法的monitor
的所有权,否则会抛出IllegalMonitorStateException
异常 - 同步代码的
monitor
必须与执行wait
和notify
方法的对象一致,简单地说就是用哪个对象的monitor
进行同步,就只能用哪个对象进行wait
和notify
操作,否则也会抛出IllegalMonitorStateException
异常
wait 和 sleep
从表面上看,wait
和sleep
方法都可以使当前线程进入阻塞状态,但是两者之间存在着本质的区别。
相似之处:
wait
和sleep
方法都可以使线程进入阻塞状态wait
和sleep
方法均是可中断方法,被中断后都会收到中断异常
区别:wait
是Object
的方法,而sleep
是Thread
特有的方法wait
方法的执行必须在同步方法中进行,而sleep
则不需要- 线程在同步方法中执行
sleep
方法时,并不会释放monitor
的锁,而wait
方法则会释放monitor
的锁 sleep
方法短暂休眠之后会主动退出阻塞,而wait
方法(没有指定wait
时间)则需要被其他线程中断后才能退出阻塞
多线程间通信
notifyAll方法
notify
方法每次只能唤醒其中的一个线程,而notifyAll
可以同时唤醒全部的阻塞线程。
被唤醒的线程仍需要继续争夺monitor
的锁
线程休息室 wait set
线程调用了某个对象的wait
方法之后都会被加入与该对象monitor
关联的wait set
中,并且释放monitor
的所有权
另一个线程调用notify
方法之后,其中一个线程会从wait set
中弹出
另一个线程调用notifyAll
方法之后,其中所有线程都会从wait set
中弹出
自定义显式锁 BooleanLock
synchronized关键字的缺陷
synchronized关键字提供了一种排他式的数据同步机制,某个线程在获取monitor lock的时候会被阻塞,而这种阻塞有两个明显的缺陷:
第一:无法控制阻塞时长
第二:阻塞不可被中断
package com.example.demo.thread;
import java.util.List;
import java.util.concurrent.TimeoutException;
public interface Lock {
/**
* 永远阻塞,除非获取到了锁,可被中断,抛出 InterruptedException
*/
void lock() throws InterruptedException;
/**
* 除了可中断之外,增加对应的超时功能
* @param mills 超时时间
* @throws InterruptedException 中断异常
* @throws TimeoutException 超时异常
*/
void lock(long mills) throws InterruptedException, TimeoutException;
/**
* 进行锁的释放
*/
void unlock();
/**
* 获取当前有哪些线程被阻塞
* @return 当前被阻塞的线程列表
*/
List<Thread> getBlockedThreads();
}
package com.example.demo.thread;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeoutException;
public class BooleanLock implements Lock {
/**
* 当前拥有锁的线程
*/
private Thread currentThread; //当前拥有锁的线程
/**
* boolean 开关
* {@code false} 代表当前锁没有被任何线程获得或者已经释放
* {@code true} 代表该锁已被某个线程获得,该线程就是currentThread
*/
private boolean locked = false;
/**
* 存储哪些线程在获取当前线程时进入了阻塞状态
*/
private final List<Thread> blockedList = new ArrayList<>();
/**
* 永远阻塞,除非获取到了锁
* @throws InterruptedException 中断异常
*/
@Override
public void lock() throws InterruptedException {
synchronized (this) {
Thread thread = Thread.currentThread();
while (locked) {
blockedList.add(thread);
this.wait();
}
blockedList.remove(thread);
this.locked = true;
this.currentThread = thread;
}
}
/**
* 除了可中断之外,增加对应的超时功能
* @param mills 超时时间
* @throws InterruptedException 中断异常
* @throws TimeoutException 超时异常
*/
@Override
public void lock(long mills) throws InterruptedException, TimeoutException {
synchronized (this) {
Thread thread = Thread.currentThread();
if (mills <= 0) {
this.lock();
} else {
long remainingMills = mills;
long endMills = System.currentTimeMillis() + remainingMills;
while (locked) {
if (remainingMills <= 0)
throw new TimeoutException("can not get the lock during " + mills);
if (!blockedList.contains(thread)) {
blockedList.add(thread);
}
this.wait(remainingMills);
remainingMills = endMills - System.currentTimeMillis();
}
blockedList.remove(thread);
this.locked = true;
this.currentThread = thread;
}
}
}
/**
* 进行锁的释放
*/
@Override
public void unlock() {
synchronized (this) {
if (currentThread == Thread.currentThread()) {
this.locked = false;
Optional.of(Thread.currentThread().getName() + " release the lock.")
.ifPresent(System.out::println);
this.notifyAll();
}
}
}
/**
* 获取当前有哪些线程被阻塞
* @return 当前被阻塞的线程列表
*/
@Override
public List<Thread> getBlockedThreads() {
return Collections.unmodifiableList(blockedList);
}
}
package com.example.demo.thread;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
public class BooleanLockTest {
private final Lock lock = new BooleanLock();
public void syncMethod() {
try {
lock.lock();
int randomInt = ThreadLocalRandom.current().nextInt(10);
System.out.println(Thread.currentThread() + " get the lock.");
TimeUnit.SECONDS.sleep(randomInt);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
BooleanLockTest blt = new BooleanLockTest();
IntStream.range(0, 10).mapToObj(i -> new Thread(blt::syncMethod)).forEach(Thread::start);
}
}
//result
Thread[Thread-0,5,main] get the lock.
Thread-0 release the lock.
Thread[Thread-9,5,main] get the lock.
Thread-9 release the lock.
Thread[Thread-2,5,main] get the lock.
Thread-2 release the lock.
Thread[Thread-8,5,main] get the lock.
Thread-8 release the lock.
Thread[Thread-3,5,main] get the lock.
Thread-3 release the lock.
Thread[Thread-5,5,main] get the lock.
Thread-5 release the lock.
Thread[Thread-6,5,main] get the lock.