四时宝库

程序员的知识宝库

AQS中acquire的中断(aqs 中断)

在JUC中的ReentrantLock的尝试获得锁有两种方式,分别是:

1.不响应中断

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

2.响应中断

    public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }

1处逻辑,虽然不立即响应中断,但当线程从park处继续执行后,会返回线程的interrupted状态,该方法会复位线程中断状态。

    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

相关中断方法方法参考:


方法参数true/false即控制是否复位(重置)状态标记

从AQS源码中其返回的是调用Thread.interrupted(),也就是说返回中线程的中断标记后,会进行重置。

注意在线程内部如果捕获InterruptedException,该线程的中断标记会自动重置

可以这样理解,中断异常都不获处理了,说明用户或业务已经知道该线程中断过,也处理过,所以没有必要继续保留该状态了。

如下代码示例可以证明上述描述:

public class ThreadStopSenior {
    public static void main(String[] args) throws InterruptedException {
        Thread th1 = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("abc....");
                System.out.println(Thread.currentThread().isInterrupted());
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    System.out.println(Thread.currentThread().isInterrupted());
                    //在异常时使用interruptedException,inTerrupted会自动复位,所以在循环时又为False
                    e.printStackTrace();
                }
            }
        });
        th1.start();
        Thread.sleep(1000);
        //中断线程th1
        th1.interrupt();
        Thread.sleep(2000);
    }
}

执行的结果是线程th1一直无法结束,原因即为上面所说虽然执行了interrupt,但由于异常捕获,标记又恢复了,所以while条件是一直成立。

在AQS中的实现在返回Thread.interrupted()为true,会继续强制执行一次interrupt

static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

那AQS里为何不使用isInterrupt方法返回,原因是:

若当前线程被唤醒后(从LockSupport.park方法返回),若return Thread.currentThread.isinterrupt()方法,线程中断标志不会清除。之后,线程再次试图获取锁,若依旧没有获取到,会再次尝试调用LockSupport.park方法将自己挂起。但是 此时,线程中断标志位为true,而在该状态下LockSupport.park方法并不会生效,使得程序继续执行。若该线程始终获取不到锁,该线程将在acquireQueue方法的循环中空转,cpu有可能会出现100%

尝试从park的实现代码中查找证据,调用链:

  • LockSupport
  • class Parker : public os::PlatformParker
  • class PlatformParker : public CHeapObj<mtInternal>

答案在park的实现中

void Parker::park(bool isAbsolute, jlong time) {
  
  //原子交换,如果_counter > 0,则将_counter置为0,直接返回,否则_counter为0
  if (Atomic::xchg(0, &_counter) > 0) return;
  //获取当前线程
  Thread* thread = Thread::current();
  assert(thread->is_Java_thread(), "Must be JavaThread");
  //下转型为java线程
  JavaThread *jt = (JavaThread *)thread;
 
 
  //如果当前线程设置了中断标志,调用park则直接返回,所以如果在park之前调用了interrupt就会直接返回
  if (Thread::is_interrupted(thread, false)) {
    return;
  }
 
  // 高精度绝对时间变量
  timespec absTime;
  //如果time小于0,或者isAbsolute是true并且time等于0则直接返回
  if (time < 0 || (isAbsolute && time == 0) ) { // don't wait at all
    return;
  }
  //如果time大于0,则根据是否是高精度定时计算定时时间
  if (time > 0) {
    unpackTime(&absTime, isAbsolute, time);
  }
 
 
  //进入安全点避免死锁
  ThreadBlockInVM tbivm(jt);
 
 
  //如果当前线程设置了中断标志,或者获取mutex互斥锁失败则直接返回
  //由于Parker是每个线程都有的,所以_counter cond mutex都是每个线程都有的,
  //不是所有线程共享的所以加锁失败只有两种情况,第一unpark已经加锁这时只需要返回即可,
  //第二调用调用pthread_mutex_trylock出错。对于第一种情况就类似是unpark先调用的情况,所以
  //直接返回。
  if (Thread::is_interrupted(thread, false) || pthread_mutex_trylock(_mutex) != 0) {
    return;
  }
 
  int status ;
  //如果_counter大于0,说明unpark已经调用完成了将_counter置为了1,
  //现在只需将_counter置0,解锁,返回
  if (_counter > 0)  { // no wait needed
    _counter = 0;
    status = pthread_mutex_unlock(_mutex);
    assert (status == 0, "invariant");
    OrderAccess::fence();
    return;
  }
 
 
  OSThreadWaitState osts(thread->osthread(), false /* not Object.wait() */);
  jt->set_suspend_equivalent();
  // cleared by handle_special_suspend_equivalent_condition() or java_suspend_self()
 
  assert(_cur_index == -1, "invariant");
  //如果time等于0,说明是相对时间也就是isAbsolute是fasle(否则前面就直接返回了),则直接挂起
  if (time == 0) {
    _cur_index = REL_INDEX; // arbitrary choice when not timed
    status = pthread_cond_wait (&_cond[_cur_index], _mutex) ;
  } else { //如果time非0
    //判断isAbsolute是false还是true,false的话使用_cond[0],否则用_cond[1]
    _cur_index = isAbsolute ? ABS_INDEX : REL_INDEX;
    //使用条件变量使得当前线程挂起。
    status = os::Linux::safe_cond_timedwait (&_cond[_cur_index], _mutex, &absTime) ;
    //如果挂起失败则销毁当前的条件变量重新初始化。
    if (status != 0 && WorkAroundNPTLTimedWaitHang) {
      pthread_cond_destroy (&_cond[_cur_index]) ;
      pthread_cond_init    (&_cond[_cur_index], isAbsolute ? NULL : os::Linux::condAttr());
    }
  }
 
  //如果pthread_cond_wait成功则以下代码都是线程被唤醒后执行的。
  _cur_index = -1;
  assert_status(status == 0 || status == EINTR ||
                status == ETIME || status == ETIMEDOUT,
                status, "cond_timedwait");
 
#ifdef ASSERT
  pthread_sigmask(SIG_SETMASK, &oldsigs, NULL);
#endif
  //将_counter变量重新置为1
  _counter = 0 ;
  //解锁
  status = pthread_mutex_unlock(_mutex) ;
  assert_status(status == 0, status, "invariant") ;
  // 使用内存屏障使_counter对其它线程可见
  OrderAccess::fence();
 
  // 如果在park线程挂起的时候调用了stop或者suspend则还需要将线程挂起不能返回
  if (jt->handle_special_suspend_equivalent_condition()) {
    jt->java_suspend_self();
  }
}

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接