深入理解Java中的底层阻塞原理及实现

news/2025/2/25 18:44:44
深入理解Java中的底层阻塞原理及实现

Information Technology Solutions as a Presentation

谈到阻塞,相信大家都不会陌生了。阻塞的应用场景真的多得不要不要的,比如 生产-消费模式,限流统计等等。什么 ArrayBlockingQueue、 LinkedBlockingQueue、DelayQueue 等等,都是阻塞队列的实现啊,多简单!

阻塞,一般有两个特性很亮眼:1. 不耗 CPU 等待;2. 线程安全;

额,要这么说也 OK 的。毕竟,我们遇到的问题,到这里就够解决了。但是有没有想过,这容器的阻塞又是如何实现的呢?

好吧,翻开源码,也很简单了:(比如 ArrayBlockingQueue 的 take、put….)


// ArrayBlockingQueue

/**

* Inserts the specified element at the tail of this queue, waiting

* for space to become available if the queue is full.

*

* @throws InterruptedException {@inheritDoc}

* @throws NullPointerException {@inheritDoc}

*/

public void put(E e) throws InterruptedException {

checkNotNull(e);

final ReentrantLock lock = this.lock;

lock.lockInterruptibly();

try {

while (count == items.length)

// 阻塞的点

notFull.await();

enqueue(e);

finally {

lock.unlock();

}

}

/**

* Inserts the specified element at the tail of this queue, waiting

* up to the specified wait time for space to become available if

* the queue is full.

*

* @throws InterruptedException {@inheritDoc}

* @throws NullPointerException {@inheritDoc}

*/

public boolean offer(E e, long timeout, TimeUnit unit)

throws InterruptedException {

checkNotNull(e);

long nanos = unit.toNanos(timeout);

final ReentrantLock lock = this.lock;

lock.lockInterruptibly();

try {

while (count == items.length) {

if (nanos <= 0)

return false;

// 阻塞的点

nanos = notFull.awaitNanos(nanos);

}

enqueue(e);

return true;

finally {

lock.unlock();

}

}

public E take() throws InterruptedException {

final ReentrantLock lock = this.lock;

lock.lockInterruptibly();

try {

while (count == 0)

// 阻塞的点

notEmpty.await();

return dequeue();

finally {

lock.unlock();

}

}

看来,最终都是依赖了 AbstractQueuedSynchronizer 类(著名的AQS)的 await 方法,看起来像那么回事。那么这个同步器的阻塞又是如何实现的呢?

Java的代码总是好跟踪的:

// AbstractQueuedSynchronizer.await()


/**

* Implements interruptible condition wait.

* <ol>

* <li> If current thread is interrupted, throw InterruptedException.

* <li> Save lock state returned by {@link #getState}.

* <li> Invoke {@link #release} with saved state as argument,

* throwing IllegalMonitorStateException if it fails.

* <li> Block until signalled or interrupted.

* <li> Reacquire by invoking specialized version of

* {@link #acquire} with saved state as argument.

* <li> If interrupted while blocked in step 4, throw InterruptedException.

* </ol>

*/

public final void await() throws InterruptedException {

if (Thread.interrupted())

throw new InterruptedException();

Node node = addConditionWaiter();

int savedState = fullyRelease(node);

int interruptMode = 0;

while (!isOnSyncQueue(node)) {

// 此处进行真正的阻塞

LockSupport.park(this);

if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)

break;

}

if (acquireQueued(node, savedState) && interruptMode != THROW_IE)

interruptMode = REINTERRUPT;

if (node.nextWaiter != null) // clean up if cancelled

unlinkCancelledWaiters();

if (interruptMode != 0)

reportInterruptAfterWait(interruptMode);

}

如上,可以看到,真正的阻塞工作又转交给了另一个工具类: LockSupport 的 park 方法了,这回跟锁扯上了关系,看起来已经越来越接近事实了:

// LockSupport.park()


/**

* Disables the current thread for thread scheduling purposes unless the

* permit is available.

*

* <p>If the permit is available then it is consumed and the call returns

* immediately; otherwise

* the current thread becomes disabled for thread scheduling

* purposes and lies dormant until one of three things happens:

*

* <ul>

* <li>Some other thread invokes {@link #unpark unpark} with the

* current thread as the target; or

*

* <li>Some other thread {@linkplain Thread#interrupt interrupts}

* the current thread; or

*

* <li>The call spuriously (that is, for no reason) returns.

* </ul>

*

* <p>This method does <em>not</em> report which of these caused the

* method to return. Callers should re-check the conditions which caused

* the thread to park in the first place. Callers may also determine,

* for example, the interrupt status of the thread upon return.

*

* @param blocker the synchronization object responsible for this

* thread parking

* @since 1.6

*/

public static void park(Object blocker) {

Thread t = Thread.currentThread();

setBlocker(t, blocker);

UNSAFE.park(false, 0L);

setBlocker(t, null);

}

看得出来,这里的实现就比较简洁了,先获取当前线程,设置阻塞对象,阻塞,然后解除阻塞。

好吧,到底什么是真正的阻塞,我们还是不得而知!

UNSAFE.park(false, 0L); 是个什么东西? 看起来就是这一句起到了最关键的作用呢!但由于这里已经是 native 代码,我们已经无法再简单的查看源码了!那咋整呢?

那不行就看C/C++的源码呗,看一下 parker 的定义(park.hpp):


class Parker : public os::PlatformParker {

private:

volatile int _counter ;

Parker * FreeNext ;

JavaThread * AssociatedWith ; // Current association

public:

Parker() : PlatformParker() {

_counter = 0 ;

FreeNext = NULL ;

AssociatedWith = NULL ;

}

protected:

~Parker() { ShouldNotReachHere(); }

public:

// For simplicity of interface with Java, all forms of park (indefinite,

// relative, and absolute) are multiplexed into one call. c中暴露出两个方法给java调用

void park(bool isAbsolute, jlong time);

void unpark();

// Lifecycle operators

static Parker * Allocate (JavaThread * t) ;

static void Release (Parker * e) ;

private:

static Parker * volatile FreeList ;

static volatile int ListLock ;

};

那 park() 方法到底是如何实现的呢? 其实是继承的 os::PlatformParker 的功能,也就是平台相关的私有实现,以 Linux 平台实现为例(os_linux.hpp):


// Linux中的parker定义

class PlatformParker : public CHeapObj<mtInternal> {

protected:

enum {

REL_INDEX = 0,

ABS_INDEX = 1

};

int _cur_index; // which cond is in use: -1, 0, 1

pthread_mutex_t _mutex [1] ;

pthread_cond_t _cond [2] ; // one for relative times and one for abs.

public: // TODO-FIXME: make dtor private

~PlatformParker() { guarantee (0, "invariant") ; }

public:

PlatformParker() {

int status;

status = pthread_cond_init (&_cond[REL_INDEX], os::Linux::condAttr());

assert_status(status == 0, status, "cond_init rel");

status = pthread_cond_init (&_cond[ABS_INDEX], NULL);

assert_status(status == 0, status, "cond_init abs");

status = pthread_mutex_init (_mutex, NULL);

assert_status(status == 0, status, "mutex_init");

_cur_index = -1; // mark as unused

}

};

看到 park.cpp 中没有重写 park() 和 unpark() 方法,也就是说阻塞实现完全交由特定平台代码处理了(os_linux.cpp):


// park方法的实现,依赖于 _counter, _mutex[1], _cond[2]

void Parker::park(bool isAbsolute, jlong time) {

// Ideally we'd do something useful while spinning, such

// as calling unpackTime().

// Optional fast-path check:

// Return immediately if a permit is available.

// We depend on Atomic::xchg() having full barrier semantics

// since we are doing a lock-free update to _counter.

if (Atomic::xchg(0, &_counter) > 0) return;

Thread* thread = Thread::current();

assert(thread->is_Java_thread(), "Must be JavaThread");

JavaThread *jt = (JavaThread *)thread;

// Optional optimization -- avoid state transitions if there's an interrupt pending.

// Check interrupt before trying to wait

if (Thread::is_interrupted(threadfalse)) {

return;

}

// Next, demultiplex/decode time arguments

timespec absTime;

if (time < 0 || (isAbsolute && time == 0) ) { // don't wait at all

return;

}

if (time > 0) {

unpackTime(&absTime, isAbsolute, time);

}

// Enter safepoint region

// Beware of deadlocks such as 6317397.

// The per-thread Parker:: mutex is a classic leaf-lock.

// In particular a thread must never block on the Threads_lock while

// holding the Parker:: mutex. If safepoints are pending both the

// the ThreadBlockInVM() CTOR and DTOR may grab Threads_lock.

ThreadBlockInVM tbivm(jt);

// Don't wait if cannot get lock since interference arises from

// unblocking. Also. check interrupt before trying wait

if (Thread::is_interrupted(threadfalse) || pthread_mutex_trylock(_mutex) != 0) {

return;

}

int status ;

if (_counter > 0) { // no wait needed

_counter = 0;

status = pthread_mutex_unlock(_mutex);

assert (status == 0, "invariant") ;

// Paranoia to ensure our locked and lock-free paths interact

// correctly with each other and Java-level accesses.

OrderAccess::fence();

return;

}

#ifdef ASSERT

// Don't catch signals while blocked; let the running threads have the signals.

// (This allows a debugger to break into the running thread.)

sigset_t oldsigs;

sigset_t* allowdebug_blocked = os::Linux::allowdebug_blocked_signals();

pthread_sigmask(SIG_BLOCK, allowdebug_blocked, &oldsigs);

#endif

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");

if (time == 0) {

_cur_index = REL_INDEX; // arbitrary choice when not timed

status = pthread_cond_wait (&_cond[_cur_index], _mutex) ;

else {

_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());

}

}

_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 = 0 ;

status = pthread_mutex_unlock(_mutex) ;

assert_status(status == 0, status, "invariant") ;

// Paranoia to ensure our locked and lock-free paths interact

// correctly with each other and Java-level accesses.

OrderAccess::fence();

// If externally suspended while waiting, re-suspend

if (jt->handle_special_suspend_equivalent_condition()) {

jt->java_suspend_self();

}

}

// unpark 实现,相对简单些

void Parker::unpark() {

int s, status ;

status = pthread_mutex_lock(_mutex);

assert (status == 0, "invariant") ;

s = _counter;

_counter = 1;

if (s < 1) {

// thread might be parked

if (_cur_index != -1) {

// thread is definitely parked

if (WorkAroundNPTLTimedWaitHang) {

status = pthread_cond_signal (&_cond[_cur_index]);

assert (status == 0, "invariant");

status = pthread_mutex_unlock(_mutex);

assert (status == 0, "invariant");

else {

// must capture correct index before unlocking

int index = _cur_index;

status = pthread_mutex_unlock(_mutex);

assert (status == 0, "invariant");

status = pthread_cond_signal (&_cond[index]);

assert (status == 0, "invariant");

}

else {

pthread_mutex_unlock(_mutex);

assert (status == 0, "invariant") ;

}

else {

pthread_mutex_unlock(_mutex);

assert (status == 0, "invariant") ;

}

}

从上面代码可以看出,阻塞主要借助于三个变量,_cond、_mutex、_counter, 调用 Linux 系统的 pthread_cond_wait、pthread_mutex_lock、pthread_mutex_unlock (一组 POSIX 标准的阻塞接口)等平台相关的方法进行阻塞了!

而 park.cpp 中,则只有 Allocate、Release 等的一些常规操作!


// 6399321 As a temporary measure we copied & modified the ParkEvent::

// allocate() and release() code for use by Parkers. The Parker:: forms

// will eventually be removed as we consolide and shift over to ParkEvents

// for both builtin synchronization and JSR166 operations.

volatile int Parker::ListLock = 0 ;

Parker * volatile Parker::FreeList = NULL ;

Parker * Parker::Allocate (JavaThread * t) {

guarantee (t != NULL, "invariant") ;

Parker * p ;

// Start by trying to recycle an existing but unassociated

// Parker from the global free list.

// 8028280: using concurrent free list without memory management can leak

// pretty badly it turns out.

Thread::SpinAcquire(&ListLock, "ParkerFreeListAllocate");

{

p = FreeList;

if (p != NULL) {

FreeList = p->FreeNext;

}

}

Thread::SpinRelease(&ListLock);

if (p != NULL) {

guarantee (p->AssociatedWith == NULL, "invariant") ;

else {

// Do this the hard way -- materialize a new Parker..

p = new Parker() ;

}

p->AssociatedWith = t ; // Associate p with t

p->FreeNext = NULL ;

return p ;

}

void Parker::Release (Parker * p) {

if (p == NULL) return ;

guarantee (p->AssociatedWith != NULL, "invariant") ;

guarantee (p->FreeNext == NULL , "invariant") ;

p->AssociatedWith = NULL ;

Thread::SpinAcquire(&ListLock, "ParkerFreeListRelease");

{

p->FreeNext = FreeList;

FreeList = p;

}

Thread::SpinRelease(&ListLock);

}

综上源码,在进行阻塞的时候,底层并没有(并不一定)要用 while 死循环来阻塞,更多的是借助于操作系统的实现来进行阻塞的。当然,这也更符合大家的猜想!

从上的代码我们也发现一点,底层在做许多事的时候,都不忘考虑线程中断,也就是说,即使在阻塞状态也是可以接收中断信号的,这为上层语言打开了方便之门。

如果要细说阻塞,其实还远没完,不过再往操作系统层面如何实现,就得再下点功夫,去翻翻资料了,把底线压在操作系统层面,大多数情况下也够用了!

最后送波福利。现在加群即可获取Java工程化、高性能及分布式、高性能、高架构、zookeeper、性能调优、Spring、MyBatis、Netty源码分析和大数据等多个知识点高级进阶干货的直播免费学习权限及相关资料,群号:835638062 点击链接加入群聊【Java高级架构学习交流】:https://jq.qq.com/?_wv=1027&k=5S3kL3v


http://www.niftyadmin.cn/n/3459251.html

相关文章

sqlserver 所有系统和用户定义错误消息

下面的示例将查询 sys.messages 目录视图以返回具有英文文本 (1033) 的数据库引擎中所有系统和用户定义错误消息的列表。 language_id1033 英文 language_id1041 日文 language_id2052 中文简体 language_id1028 中文繁体 SELECTmessage_id,language_id,severity,is_event_log…

DDD学习笔记 - 领域、子域和限界上下文

在设计欠佳的软件里&#xff0c;子域和限界上下文(context)很难存在一对一的映射关系。-- 说明设计较好的&#xff0c;子域和限界上下文应该是一对一的映射关系。 在不同的模型中存在名字相同或者相近的对象&#xff0c;但是它们的意思缺不同。当模型被一个显示的边界所包围时&…

C++线程编程-设计无锁的并发数据结构

定义和结果 使用互斥元、条件变量以及future 来同步数据的算法和数据结构被称为阻塞的算法和数据结构.调用库函数的应用会中断一个线程的执行&#xff0c;直到另一个线程执行一个动作.这种库函数调用被称为阻塞调用&#xff0c;因为直到阻塞被释放时线程才能继续执行下去.通常…

你人再好有屁用?(火了)

你人再好&#xff0c;不是每个人都会喜欢你。有人羡慕你&#xff0c;也有人讨厌你&#xff0c;有人嫉妒你&#xff0c;也有人看不起你。生活就是这样&#xff0c;你所做的一切不能让每个人都满意&#xff0c;不要为了讨好别人而丢失自己的本性&#xff0c;因为每个人都有原则和…

python统计apache、nginx访问日志IP访问次数并且排序(显示前20条)【转】

前言&#xff1a;python统计apache、nginx访问日志IP访问次数并且排序&#xff08;显示前20条&#xff09;。其实用awksort等命令可以实现&#xff0c;用awk数组也可以实现&#xff0c;这里只是用python尝试下。 apache脚本&#xff1a; ips {} with open("/root/mail_ac…

sqlserver 查看表结构 字段 长度的2种方式

第1种&#xff1a;自己写SQL查询 selectc.name as tablename,a.name as [column],a.length ,a.isnullable,b.name as [type]FROM syscolumns a left join systypes b on a.xtypeb.xusertypeinner join sysobjects c on a.idc.id and c.xtypeUwhere c.name 表名 或 sele…

C/C++Unix网络编程-套接字编程简介

套接字地址结构 套接字的地址结构可以在两个方向上传播&#xff1a;从进程到内核和从内核到进程。 IPV4套接字地址结构 sin_len是长度字段&#xff0c;有的平台上的套接字没有这个东西&#xff0c;因为有外置的sizeof运算符。不是posxi标准强制的字段sin_family,sin_addr,sin…

Unix环境高级编程-进程间通信

进程间通信 同一台主机上的进程通信 – 不同主机上的通信 1、管道 内核提供&#xff0c;单工&#xff0c;自同步机制&#xff1a;管道永远是迁就慢的那一方 在使用管道的时候&#xff0c;一般一个进程的一个管道只使用read或者write&#xff0c; 最好把另一端关闭。 绝对不会一…