线程的阻塞和唤醒

本文深入探讨Java中线程的阻塞与唤醒机制,解析Unsafe类的park和unpark方法如何实现线程控制,揭示LockSupport、AbstractQueuedSynchronizer在锁管理和线程调度中的作用,以及公平锁、非公平锁、共享锁和排它锁的区别。

Java的线程阻塞和唤醒是通过Unsafe类的park和unpark方法做到的。

两个方法都是native方法,本身由c实现的核心功能。

  • park:是让当前运行的线程Thread.currentThread()休眠。
  • unpark:是唤醒指定线程。

两个方法底层使用操作系统提供的信号量机制来实现。

park方法有两个参数来控制休眠多长时间,第一个参数isAbsolute表示第二个参数是绝对时间还是相对时间,单位是毫秒。

线程从启动开始就一直跑,除了操作系统的任务调度策略外,只有在调用park时候才会暂停运行。锁可以暂停线程的奥秘就是因为锁在底层调用来park方法。

Thread内部有个parkBlocker属性,保存来当前线程因为什么而park。 起到一系列冲突线程的管理的协调者,哪个线程该休眠该唤醒都是由他来控制的。

当线程被unpark唤醒后,这个属性设置为null。 LockSupport可以对Unsafe的park和unpark调用设置parkBlocker属性。

Java的锁数据结构是通过调用LockSupport来实现休眠和唤醒的。线程对象里面的parkBlocker字段值是排队管理器。

  • 当多个线程争用一把锁时,必须排队机制将那些没能取得锁的线程串在一起。
  • 当释放锁时,锁管理器就会挑选一个合适的线程来占有这个刚刚释放的锁。
  • 每一把锁内部都会有这样一个队列管理器,管理器维护一个等待的线程队列。
  • ReentrantLock里面的队列管理器是AbstractQueuedSynchronizer,内部的等待队列是一个双向列表结构。
  • 加锁不成功时,当前线程会把自己放入等待队列尾部,然后调用LockSupport.park将自己休眠。
  • 其他线程解锁时,会从链表表头取一个节点,调用LockSupport.unpark唤醒它。

AbstractQueuedSynchronizer

AbstractQueuedSynchronizer类是一个抽象类,所有的锁队列管理的父类,JDK中的各种形式的锁内部队列都继承这个类,是Java并发世界的基石。 Java中的并发工具类都需要进行一些方法抽象,需要对这个管理器进行定制,并发数据结构都是在这些锁保护下完成的。

锁管理器维护是一个普通的双向列表形式的队列,这个数据结构很简单,但仔细维护起来相当复杂,因为需要考虑多线程并发问题。

公平锁和非公平锁

公平锁是确保请求锁和获取锁的顺序相同,公平锁会排队,非公平锁会插队。

线程在执行Lock.park方法时会自我休眠,并不是非得等到其他线程unpark了才会唤醒,它可能因为某种未知原因醒来,park返回原因有四种:

  • 其他线程unpark了当前线程。
  • 时间到了自然醒(park的实现参数)。
  • 其他线程interrupt了当前线程。
  • 其他未知原因导致了假醒。

也就是说当park方法返回时并不意味锁自由了,醒过来的线程在重新尝试获取锁失败后将会在此park自己。所以在加锁过程需要写在一个循环里,在成功拿到锁之前可能多次尝试。

非公平锁的服务效率高于公平锁,所以默认锁都是非公平的。当然为了避免混乱可以采用公平锁。

共享锁和排它锁

  • ReentrantLock是排它锁,一个线程持有,其他线程必须等待。
  • ReadWriteLock里面的读锁不是排它锁,运行多个线程同时持有读锁,这是共享锁,共享锁和排它锁是通过Node类里面的nextWaiter字段区分的。

加锁之前的循环重试中需要间隔sleep(1),不然cpu就会因为空转而飚高。

但是sleep多久不好控制,间隔久类,会拖慢整体效率,甚至错过时机,时间太短,导致cpu空转。可以引入signal()和await()方法,当条件满足时,调用signal()或者signalAll()方法,阻塞的线程可以立即被唤醒几乎没有任何延迟。

如果在循环加锁过程中被其他线程打断了,线程需要给自己设置一个打断标识位。

当线程park醒来后调用Thread.interrupted()就知道自己释放被打断了,但是这个方法只能调用一次,因为在调用之后立即clear打断标识位。

AQS队列的管理为解决多线程并发,在代码中会使用CAS操作队尾指针,没有竞争到的线程会继续下一轮竞争。

Java并发能力的基石是park和unpark方法,volatile变量,synchronized,cas操作和aqs队列。

转载于:https://my.oschina.net/u/1000241/blog/3083310

### Java 线程阻塞唤醒的方法 在多线程编程中,线程阻塞唤醒是实现并发控制的重要机制。Java 提供了多种方法来实现线程阻塞唤醒,主要包括 `wait()`、`notify()`、`notifyAll()`、`sleep()`、`join()` 以及 `LockSupport` 类等。 #### 阻塞线程的方法 1. **`wait()`**:该方法使当前线程进入等待状态,并释放持有的对象锁。线程会一直等待,直到其他线程调用 `notify()` 或 `notifyAll()` 方法唤醒它。需要注意的是,`wait()` 必须在同步代码块或同步方法中调用,否则会抛出 `IllegalMonitorStateException`。 2. **`sleep(long millis)`**:该方法使当前线程暂停执行指定时间(以毫秒为单位),但不会释放任何锁资源。线程在睡眠结束后会自动恢复运行。 3. **`join()`**:该方法用于等待另一个线程执行完成后再继续执行当前线程。例如,线程 A 调用 `threadB.join()`,A 将被阻塞,直到线程 B 执行完毕。 4. **`LockSupport.park()`**:该方法通过 `LockSupport` 类提供的静态方法实现线程阻塞操作。它可以替代传统的 `wait()` `notify()`,并且不需要持有对象锁即可使用。 以下是一个简单的示例,展示如何使用 `wait()` `notify()`: ```java class WaitNotifyExample { private boolean flag = false; public void waitForFlag() throws InterruptedException { synchronized (this) { while (!flag) { wait(); // 线程进入等待状态 } System.out.println("Flag is true, continuing execution"); } } public void setFlag() { synchronized (this) { flag = true; notify(); // 唤醒等待的线程 } } public static void main(String[] args) { WaitNotifyExample example = new WaitNotifyExample(); Thread waiter = new Thread(() -> { try { example.waitForFlag(); } catch (InterruptedException e) { e.printStackTrace(); } }); Thread notifier = new Thread(() -> { try { Thread.sleep(2000); // 模拟延迟 example.setFlag(); } catch (InterruptedException e) { e.printStackTrace(); } }); waiter.start(); notifier.start(); } } ``` #### 唤醒线程的方法 1. **`notify()`**:唤醒一个正在等待该对象监视器的线程。如果有多个线程在等待,则选择其中一个线程唤醒,具体选择哪个线程由 JVM 决定。 2. **`notifyAll()`**:唤醒所有正在等待该对象监视器的线程。这些线程会在锁释放后竞争重新获取锁并继续执行。 3. **`interrupt()`**:中断线程可以唤醒因调用 `sleep()` 或 `join()` 而阻塞线程,并抛出 `InterruptedException`。这种方式适用于需要提前终止线程的情况。 4. **`LockSupport.unpark(Thread thread)`**:该方法通过 `LockSupport` 类提供的静态方法实现对指定线程唤醒操作。它可以在任意时刻调用,即使目标线程尚未调用 `park()`,也不会导致异常。 以下是一个使用 `LockSupport` 的示例: ```java import java.util.concurrent.locks.LockSupport; public class LockSupportExample { public static void main(String[] args) { Thread mainThread = Thread.currentThread(); Thread counterThread = new Thread(() -> { for (int i = 1; i <= 1000; i++) { System.out.println(i); if (i == 500) { LockSupport.unpark(mainThread); // 唤醒线程 } } }); counterThread.start(); LockSupport.park(); // 主线程进入阻塞状态 System.out.println("Main thread was unparked."); } } ``` #### 特殊情况:IO 阻塞的处理 对于因 I/O 操作而阻塞线程Java 的中断机制无法直接唤醒它们。此时需要依赖具体的 I/O 操作提供的机制,例如设置超时时间、检查操作状态等[^3]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值