java.util.concurrent 之LinkedBlockingQueue源码分析

本文深入剖析了Java并发包中的LinkedBlockingQueue类,介绍了其内部结构、工作原理及关键方法的实现,包括put、take、poll和offer等方法,并探讨了锁和条件变量的应用。

   LinkedBlockingQueue类

一个基于已链接节点的、范围任意的 blocking queue。此队列按 FIFO(先进先出)排序元素。队列的头部 是在队列中时间最长的元素。队列的尾部 是在队列中时间最短的元素。新元素插入到队列的尾部,并且队列获取操作会获得位于队列头部的元素。链接队列的吞吐量通常要高于基于数组的队列,但是在大多数并发应用程序中,其可预知的性能要低。 

可选的容量范围构造方法参数作为防止队列过度扩展的一种方法。如果未指定容量,则它等于 Integer.MAX_VALUE。除非插入节点会使队列超出容量,否则每次插入后会动态地创建链接节点。 

 

   这个类的常见用法,应该用过的都应该知道了,就不再举例了,直接进入源码的分析。在分析其他方法前,先看下类变量

	/** The capacity bound, or Integer.MAX_VALUE if none */
	private final int capacity;

	/** Current number of elements */
	private final AtomicInteger count = new AtomicInteger(0);

	/** Head of linked list */
	private transient Node<E> head;

	/** Tail of linked list */
	private transient Node<E> last;

	/** Lock held by take, poll, etc */
	private final ReentrantLock takeLock = new ReentrantLock();

	/** Wait queue for waiting takes */
	private final Condition notEmpty = takeLock.newCondition();

	/** Lock held by put, offer, etc */
	private final ReentrantLock putLock = new ReentrantLock();

	/** Wait queue for waiting puts */
	private final Condition notFull = putLock.newCondition();

 

其中有两个锁和两个条件变量是最重要的,决定了下面的方法实现。。阻塞的效果是靠这些来实现的。具体看下面的方法分析。

 

new 创建对象

	/**
	 * Creates a <tt>LinkedBlockingQueue</tt> with a capacity of
	 * {@link Integer#MAX_VALUE}. 从类名可以看出它主要是用链表来保存数据的。。下面的分析也可以看出来确实如此
	 * 
	 */
	public LinkedBlockingQueue() {
		// 这里可以看出这个类是有容量限制的,默认是最大容量 int.max
		this(Integer.MAX_VALUE);
	}

	public LinkedBlockingQueue(int capacity) {
		if (capacity <= 0)
			throw new IllegalArgumentException();
		this.capacity = capacity;
		// 这里可以看出链表的头和尾默认都是null节点
		last = head = new Node<E>(null);
	}

	/**
	 * Linked list node class 这里是链表的节点实现了
	 */
	static class Node<E> {
		/** The item, volatile to ensure barrier separating write and read */
		// 这里的volatile变量确保了多线程中内存的一致性
		volatile E item;
		Node<E> next;

		Node(E x) {
			item = x;
		}
	}

 

put方法

	public void put(E e) throws InterruptedException {
		if (e == null)
			throw new NullPointerException();
		// Note: convention in all put/take/etc is to preset
		// local var holding count negative to indicate failure unless set.
		int c = -1;
		final ReentrantLock putLock = this.putLock;
		final AtomicInteger count = this.count;
		// put 加锁
		putLock.lockInterruptibly();
		try {
			/*
			 * Note that count is used in wait guard even though it is not
			 * protected by lock. This works because count can only decrease at
			 * this point (all other puts are shut out by lock), and we (or some
			 * other waiting put) are signalled if it ever changes from
			 * capacity. Similarly for all other uses of count in other wait
			 * guards.
			 */
			try {
				// 判断是否满了,如果满了则notFull 线程等待
				while (count.get() == capacity)
					notFull.await();
			} catch (InterruptedException ie) {
				notFull.signal(); // propagate to a non-interrupted thread
				throw ie;
			}
			// 插入链表
			insert(e);
			// 更新容量
			c = count.getAndIncrement();
			// 没满则唤醒notFull线程
			if (c + 1 < capacity)
				notFull.signal();
		} finally {
			putLock.unlock();
		}
		// 当c==0代表容量刚从空转为非空状态,则唤醒非空线程
		if (c == 0)
			signalNotEmpty();
	}

	/**
	 * Signals a waiting take. Called only from put/offer (which do not
	 * otherwise ordinarily lock takeLock.)
	 */
	private void signalNotEmpty() {
		final ReentrantLock takeLock = this.takeLock;
		takeLock.lock();
		try {
			// 唤醒非空线程
			notEmpty.signal();
		} finally {
			takeLock.unlock();
		}
	}

 

take 方法

	// 这里的加锁方式刚好和put 方法相反。就不多说了。
	public E take() throws InterruptedException {
		E x;
		int c = -1;
		final AtomicInteger count = this.count;
		final ReentrantLock takeLock = this.takeLock;
		takeLock.lockInterruptibly();
		try {
			try {
				while (count.get() == 0)
					notEmpty.await();
			} catch (InterruptedException ie) {
				notEmpty.signal(); // propagate to a non-interrupted thread
				throw ie;
			}

			x = extract();
			c = count.getAndDecrement();
			if (c > 1)
				notEmpty.signal();
		} finally {
			takeLock.unlock();
		}
		if (c == capacity)
			signalNotFull();
		return x;
	}

 

poll 方法

// 这个其实和poll()无参数的方法类似,只是多了for循环的一个计数的功能。poll()就不分析了。
	public E poll(long timeout, TimeUnit unit) throws InterruptedException {
		E x = null;
		int c = -1;
		long nanos = unit.toNanos(timeout);
		final AtomicInteger count = this.count;
		final ReentrantLock takeLock = this.takeLock;
		takeLock.lockInterruptibly();
		try {
			for (;;) {// 这个是为了计时而做的循环
				if (count.get() > 0) {
					x = extract();
					c = count.getAndDecrement();
					if (c > 1)
						notEmpty.signal();
					break;
				}
				if (nanos <= 0)
					return null;
				try {
					// awaitNanos 方法返回的是nanosTimeout
					// 值减去花费在等待此方法的返回结果的时间的估算,相当于就是剩余时间了。
					nanos = notEmpty.awaitNanos(nanos);
				} catch (InterruptedException ie) {
					notEmpty.signal(); // propagate to a non-interrupted thread
					throw ie;
				}
			}
		} finally {
			takeLock.unlock();
		}
		if (c == capacity)
			signalNotFull();
		return x;
	}

 

offer方法

// 这里和poll(long timeout, TimeUnit unit)方法的锁的实现相反。。也不多说了。
	public boolean offer(E e, long timeout, TimeUnit unit)
			throws InterruptedException {

		if (e == null)
			throw new NullPointerException();
		long nanos = unit.toNanos(timeout);
		int c = -1;
		final ReentrantLock putLock = this.putLock;
		final AtomicInteger count = this.count;
		putLock.lockInterruptibly();
		try {
			for (;;) {
				if (count.get() < capacity) {
					insert(e);
					c = count.getAndIncrement();
					if (c + 1 < capacity)
						notFull.signal();
					break;
				}
				if (nanos <= 0)
					return false;
				try {
					nanos = notFull.awaitNanos(nanos);
				} catch (InterruptedException ie) {
					notFull.signal(); // propagate to a non-interrupted thread
					throw ie;
				}
			}
		} finally {
			putLock.unlock();
		}
		if (c == 0)
			signalNotEmpty();
		return true;
	}

 

可以看出LinkedBlockingQueue 类的实现比较简单,也很容易理解,灵活应用了条件变量(Condition),减少了锁的竞争。。   by zhxing

<think>我们正在讨论Java线程处于WAITING (parking)状态的处理方法。根据之前的对话历史和引用内容,我们可以总结如下: 引用[1]指出:调用LockSupport.park()会使线程进入WAITING (parking)状态,通过jstack可以查看到线程状态为"WAITING (parking)"。 引用[2]提到:当大量线程处于WAITING (parking)状态时,可能会导致线程资源占用累计,甚至影响GC执行和新请求处理。 引用[3]说明:WAITING (parking)状态是线程通过LockSupport.park()挂起,而带超时的等待(如parkNanos)属于TIMED_WAITING状态。 引用[4]则展示了一个案例:线程池中的工作线程因为任务不足而大量处于等待状态。 基于以上信息,处理WAITING (parking)状态的方法主要包括诊断、解决和预防三个步骤。 ### 一、诊断线程状态 1. **获取线程快照** 使用jstack命令查看线程堆栈,找出处于WAITING (parking)状态的线程: ```bash jstack -l <pid> > thread_dump.txt ``` 然后分析这些线程的堆栈跟踪,确定它们是在哪个位置被挂起的[^1][^2]。 2. **定位阻塞点** 常见的阻塞点包括: - `LockSupport.park()`:直接调用park方法。 - `Condition.await()`:内部调用park。 - 同步队列的take操作(如`LinkedBlockingQueue.take()`)[^4]。 ### 二、解决方案 针对不同的阻塞原因,采取相应措施: 1. **优化线程池配置** 如果线程是线程池的工作线程,且因等待任务而阻塞: - 调整线程池参数,例如设置空闲线程的超时时间(`keepAliveTime`),允许核心线程超时终止: ```java ThreadPoolExecutor executor = new ThreadPoolExecutor( coreSize, maxSize, 60, TimeUnit.SECONDS, // 空闲线程等待60秒后终止 new LinkedBlockingQueue<>(1000) ); executor.allowCoreThreadTimeOut(true); // 允许核心线程超时 ``` - 使用有界队列避免任务无限堆积,防止工作线程永远阻塞在队列操作上。 2. **引入超时机制** 将无限等待改为带超时的等待,防止永久阻塞: ```java // 使用LockSupport的带超时方法 LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(30)); // 最多等待30秒 // 或使用Condition的await超时 condition.await(30, TimeUnit.SECONDS); ``` 超时后线程会自动恢复,可以执行异常处理或重试逻辑[^3]。 3. **确保资源释放** 如果线程因等待某个条件而阻塞(如锁或信号),需确保在其他线程正确触发条件: ```java // 在另一个线程中唤醒 LockSupport.unpark(blockedThread); // 或对于Condition condition.signalAll(); ``` 4. **中断处理** 为长时间阻塞的线程设计中断响应: ```java Thread worker = new Thread(() -> { while (!Thread.currentThread().isInterrupted()) { // ... 执行任务 ... LockSupport.park(); if (Thread.interrupted()) { // 处理中断:清理资源并退出 break; } } }); // 需要停止时调用 worker.interrupt(); ``` ### 三、预防措施 1. **代码审查** - 避免在核心逻辑中使用无超时的`LockSupport.park()`。 - 对所有阻塞操作(park、await、take等)添加超时控制。 2. **监控与告警** - 使用监控工具(如Prometheus + Grafana)跟踪WAITING状态的线程数量。 - 设置阈值告警:当WAITING线程超过总线程数的50%时发出警告。 3. **压力测试** 模拟高并发场景,确保线程不会因任务不足或资源竞争而永久阻塞。 ### 四、案例分析 | 场景 | 表现 | 解决方法 | |-------------------|--------------------------|------------------------| | 线程池任务不足 | 所有工作线程在take()阻塞 | 减少核心线程数或设置超时[^4] | | 条件变量未唤醒 | 线程在Condition.await() | 检查signal/signalAll调用逻辑 | | 中断忽略 | park()后未响应中断 | 在park后检查中断状态 | > **关键点**:`WAITING (parking)`是正常状态,但需避免永久阻塞。超时控制和中断响应是核心解决思路。 §§相关问题§§ 1. 如何通过jstack分析线程死锁? 2. 如何监控Java应用的线程状态? 3. LockSupport.park()和Object.wait()有什么区别? 4. 如何设计可中断的线程任务? 5. 线程池的keepAliveTime参数如何配置? </think>### Java线程处于WAITING (parking)状态的处理方法 当Java线程因调用`LockSupport.park()`进入`WAITING (parking)`状态时,需系统化处理。以下是诊断和解决方案: --- #### 一、问题诊断(定位阻塞点) 1. **获取线程堆栈** 执行命令查看阻塞详情: ```bash jstack -l <pid> | grep -A 30 "WAITING (parking)" # 定位具体阻塞点[^1] ``` - 示例输出: ``` "Thread-1" #15 prio=5 tid=0x00007f... nid=0x5e03 waiting on condition java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x000000076d4f8d38> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) at java.util.concurrent.LinkedBlockingQueue.take() # 阻塞点[^2] ``` 2. **分析阻塞类型** | 阻塞位置 | 常见原因 | |-----------------------------|--------------------------| | `LinkedBlockingQueue.take()` | 任务队列空,工作线程饥饿 | | `Condition.await()` | 同步条件未触发 | | 自定义`LockSupport.park()` | 未设计唤醒/超时机制 | --- #### 二、解决方案(按场景) 1. **线程池任务饥饿** ```java ThreadPoolExecutor executor = new ThreadPoolExecutor( 4, // corePoolSize 10, // maxPoolSize 30, TimeUnit.SECONDS, // 空闲线程超时时间[^4] new SynchronousQueue<>() // 避免无界队列积压 ); executor.allowCoreThreadTimeOut(true); // 核心线程也可超时终止 ``` - **效果**:空闲超时后线程自动销毁,释放资源 2. **同步条件未触发** ```java Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); lock.lock(); try { // 使用带超时的等待(30秒上限) boolean satisfied = condition.await(30, TimeUnit.SECONDS); if (!satisfied) { log.error("条件等待超时,执行补偿逻辑"); recoverResource(); // 资源释放 } } finally { lock.unlock(); } ``` 3. **自定义park()的改进** ```java Thread worker = new Thread(() -> { while (!Thread.currentThread().isInterrupted()) { LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(10)); // 限时10秒 if (Thread.interrupted()) { cleanUp(); // 响应中断 return; } } }); // 外部唤醒机制 LockSupport.unpark(worker); ``` --- #### 三、预防性措施 1. **代码规范** ```java // ✅ 正例:带超时和中断检查 public void safePark() { LockSupport.parkUntil(System.currentTimeMillis() + 60000); // 1分钟超时 if (Thread.interrupted()) { throw new InterruptedException(); } } ``` 2. **监控告警配置** - 监控项:`WAITING (parking)`线程占比 > 总线程数40% - 工具:Arthas `thread -b` 或 Prometheus + Grafana 仪表盘 3. **线程泄漏检测** ```java ThreadMXBean bean = ManagementFactory.getThreadMXBean(); long[] threadIds = bean.findDeadlockedThreads(); // 定期扫描 if (threadIds != null) { cleanDeadThreads(threadIds); // 清理僵尸线程 } ``` --- #### 四、典型场景处理流程 ```mermaid graph TD A[发现WAITING(parking)线程] --> B{阻塞类型} B -->|任务队列空| C[优化线程池参数] B -->|条件未满足| D[添加await超时] B -->|自定义park| E[增加parkNanos+中断检查] C --> F[设置keepAliveTime] D --> G[添加超时补偿逻辑] E --> H[注册unpark调用点] ``` > **关键原则**:所有阻塞操作必须包含**超时控制**和**中断响应**,避免永久等待[^3]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值