java 多线程通信
2019-09-24 本文已影响0人
Tim在路上
线程间的通信又称为进程内的通信
wait和notify是Object中的方法
wait(0) 0代表永不超时, Object的wait方法会导致当前的线程陷入阻塞状态,直到其他线程notify或notifyAll 才能将其唤醒,或者阻塞时间到而自动唤醒.
当前线程执行对象的wait方法之后,将会放弃对monitor的所有权,并进入与对象关联的wait set中,一旦线程执行了wait会释放monitor的所有权
notify 唤醒正在执行wait的方法的线程.
如果某个线程由于执行wait进入阻塞则会被唤醒,被唤醒需要重新获取对象所关联的monitor的lock才能继续执行
wait方法是可中断的方法,当前线程调用了wait方法进入了阻塞状态,其他线程可以使用interrupt方法将其打断,可中断方法在被打断后会收到InterruptedException异常,同时Interupt标识也会被擦除
线程执行了某一个对象的wait方法后会加入到对应的wait set中,notify可以将其唤醒,从wait set中进行弹出,同时中断wait中的线程也会将其唤醒
必须在同步方法中使用wait和notify方法,因为执行wait和notify的前提条件是持有同步方法的monitor的所有权
同步方法的monitor锁,必须与wait 和 notify的方法的对象一致
private final Object MUTEX = new Object();
private synchronized void testWait(){
try{
MUTEX.wait();
}catch(InterruptedException e){
}
}
private synchronized void testNotify(){
MUTEX.notify();
}
这里同步方法使用的是this 锁,但是wait和notify方法使用的是MUTEX的方法
wait 和 sleep 相似与区别
wait 和 sleep 都可以让当前的线程陷入阻塞,都是可中断的方法
wait 是 object 的方法,而sleep是thread的特有方法
线程在同步方法中执行sleep方法时,并不会释放monitor的锁,而wait方法则会.
多线程通信
notify 是唤醒阻塞线程中的一个,但是notifyAll 可以唤醒全部的阻塞线程,同样的是被唤醒的线程需要争抢monitor的锁.
public void offer(Event event){
synchronized (eventQueue){
while(eventQueue.size >= max){
try{
console(" the queue is full");
eventQueue.wait();
}catch(InterruptedException e){
}
}
console("the new event is submitted");
eventQueue.addLast(event);
eventQueue.notifyAll();
}
}
synchronized 关键字缺陷
- 无法控制阻塞的时长
- 阻塞不可能被中断
- synchronized 不能捕获到中断信号
public class BooleanLock implements Lock{
private Thread currentThread;
private boolean locked = false;
private final List<Thread> blockedThreadList = new ArrayList<>();
@Override
public void lock() throws InterruptedException {
synchronized (this){
// lock方法体使用synchronized包裹,lock完就释放真实的锁 @1
while (locked){
if(!blockedThreadList.contains(Thread.currentThread())){
blockedThreadList.add(Thread.currentThread());
}
try {
//locked标志为true,当前线程就等待locked为false,自身等待,wait方法释放真实的锁 @2
this.wait();
}catch (InterruptedException e){
//这里线程是上面的wait方法被打断会跳出,因为线程是调用wait方法主动阻塞
// 而不是在等待synchronized锁释放而陷入阻塞,所以是可以打断的,
//所以这里是整个BooleanLock机制的核心!
blockedThreadList.remove(Thread.currentThread());
throw new InterruptedException(Thread.currentThread().getName() + "被打断");
}
}
//如果走到这里,表示之前没有线程获取到锁,我获取到了
blockedThreadList.remove(Thread.currentThread());
//上锁
locked = true;
currentThread = Thread.currentThread();
}
}
@Override
public void lock(long mills) throws InterruptedException,TimeoutException {
synchronized (this){
//如果传入的时间不合法,就直接调用没有超时功能的Lock
if(mills <= 0){
this.lock();
}else{
long lastMills = mills;
//这里记录一个时间戳,totalMills表示到达这个时刻,如果还没有获取到锁,就报超时
long totalMills = lastMills + System.currentTimeMillis();
while (locked){
if (lastMills <= 0){
throw new TimeoutException(Thread.currentThread().getName() + "超时" + mills + ".mills" + "没有获取到锁");
}
if(!blockedThreadList.contains(Thread.currentThread())){
blockedThreadList.add(Thread.currentThread());
}
//这里释放了真实的锁
this.wait(lastMills);
// lastMills表示还需要等待多久时间,而lastMills减少表示两种情况
// 第一种: 线程已经wait到时间了,自动唤醒了,这个时候已经过去了lastMills的时间,下面这个操作就把lastMills置0
// 第二种: 线程wait没有到时间,被unlock唤醒,但是被唤醒后还是没有抢到锁,于是lastMills被减少,
// 继续等待,时间减去已经等待了多长时间
lastMills = totalMills - System.currentTimeMillis();
}
blockedThreadList.remove(Thread.currentThread());
locked = true;
currentThread = Thread.currentThread();
}
}
}
@Override
public void unlock() {
synchronized (this){
//如果当前线程是获取到锁的线程,就可以去执行释放锁的操作
if (currentThread == Thread.currentThread()){
//释放锁
this.locked = false;
//唤醒所有正在wait而陷入阻塞的线程,因为locked已经被设置成false,所以可以进入下一轮争抢
this.notifyAll();
}
}
}
@Override
public List<Thread> getLockedThreads() {
//unmodifiableList表示不可修改,防止阻塞队列被强行主动修改,出现错误
return Collections.unmodifiableList(blockedThreadList);
}
}