两个方法都是native方法,本身由c实现的核心功能。
两个方法底层使用操作系统提供的信号量机制来实现。
park方法有两个参数来控制休眠多长时间,第一个参数isAbsolute表示第二个参数是绝对时间还是相对时间,单位是毫秒。
线程从启动开始就一直跑,除了操作系统的任务调度策略外,只有在调用park时候才会暂停运行。锁可以暂停线程的奥秘就是因为锁在底层调用来park方法。
Thread内部有个parkBlocker属性,保存来当前线程因为什么而park。起到一系列冲突线程的管理的协调者,哪个线程该休眠该唤醒都是由他来控制的。
当线程被unpark唤醒后,这个属性设置为null。LockSupport可以对Unsafe的park和unpark调用设置parkBlocker属性。
Java的锁数据结构是通过调用LockSupport来实现休眠和唤醒的。线程对象里面的parkBlocker字段值是排队管理器。
AbstractQueuedSynchronizer
AbstractQueuedSynchronizer类是一个抽象类,所有的锁队列管理的父类,JDK中的各种形式的锁内部队列都继承这个类,是Java并发世界的基石。Java中的并发工具类都需要进行一些方法抽象,需要对这个管理器进行定制,并发数据结构都是在这些锁保护下完成的。
锁管理器维护是一个普通的双向列表形式的队列,这个数据结构很简单,但仔细维护起来相当复杂,因为需要考虑多线程并发问题。
公平锁和非公平锁
公平锁是确保请求锁和获取锁的顺序相同,公平锁会排队,非公平锁会插队。
线程在执行Lock.park方法时会自我休眠,并不是非得等到其他线程unpark了才会唤醒,它可能因为某种未知原因醒来,park返回原因有四种:
也就是说当park方法返回时并不意味锁自由了,醒过来的线程在重新尝试获取锁失败后将会在此park自己。所以在加锁过程需要写在一个循环里,在成功拿到锁之前可能多次尝试。
非公平锁的服务效率高于公平锁,所以默认锁都是非公平的。当然为了避免混乱可以采用公平锁。
共享锁和排它锁
加锁之前的循环重试中需要间隔sleep(1),不然cpu就会因为空转而飚高。
但是sleep多久不好控制,间隔久类,会拖慢整体效率,甚至错过时机,时间太短,导致cpu空转。可以引入signal()和await()方法,当条件满足时,调用signal()或者signalAll()方法,阻塞的线程可以立即被唤醒几乎没有任何延迟。
如果在循环加锁过程中被其他线程打断了,线程需要给自己设置一个打断标识位。
当线程park醒来后调用Thread.interrupted()就知道自己释放被打断了,但是这个方法只能调用一次,因为在调用之后立即clear打断标识位。
AQS队列的管理为解决多线程并发,在代码中会使用CAS操作队尾指针,没有竞争到的线程会继续下一轮竞争。
Java并发能力的基石是park和unpark方法,volatile变量,synchronized,cas操作和aqs队列。
你好,我是春哥叨叨,更多真实架构案例分享,等你很久了!