- 随着多过程多线程的呈现,对共享资源 (设施,数据等) 的竞争往往会导致资源的应用体现为随机无序
- 例如:一个线程想在控制台输入 ”I am fine”,刚写到 ”I am”,就被另一线程抢占控制台输入 ”naughty”,导致后果是 ”I am naughty”;对于资源的被抢占应用,咱们能怎么办呢?当然不是凉拌,可应用锁进行同步治理,使得资源在加锁期间,其余线程不可抢占应用
1 锁的分类
-
乐观锁
- 乐观锁,每次去申请数据的时候,都认为数据会被抢占更新(乐观的想法);所以每次操作数据时都要先加上锁,其余线程批改数据时就要期待获取锁。实用于写多读少的场景,synchronized 就是一种乐观锁
-
乐观锁
- 在申请数据时,感觉无人抢占批改。等真正更新数据时,才判断此期间他人有没有批改过(事后读出一个版本号或者更新工夫戳,更新时判断是否变动,没变则期间无人批改);和乐观锁不同的是,期间数据容许其余线程批改
-
自旋锁
- 一句话,魔力转转圈。当尝试给资源加锁却被其余线程先锁定时,不是阻塞期待而是循环再次加锁
- 在锁常被短暂持有的场景下,线程阻塞挂起导致 CPU 上下文频繁切换,这可用自旋锁解决;但自旋期间它占用 CPU 空转,因而不实用长时间持有锁的场景
2 synchronized 底层原理
- 代码应用 synchronized 加锁,在编译之后的字节码是怎么的呢
public class Test {public static void main(String[] args){synchronized(Test.class){System.out.println("hello");
}
}
}
截取局部字节码,如下
4: monitorenter
5: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
8: ldc #15 // String hello
10: invokevirtual #17 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
13: aload_1
14: monitorexit
字节码呈现了 4: monitorenter 和 14: monitorexit 两个指令;字面了解就是监督进入,监督退出。能够了解为代码块执行前的加锁,和退出同步时的解锁
- 那 monitorenter 和 monitorexit,又背着咱们干了啥呢?
- 执行 monitorenter 指令时,线程会为锁对象关联一个 ObjectMonitor 对象
objectMonitor.cpp
ObjectMonitor() {
_header = NULL;
_count = 0; \\ 用来记录该线程获取锁的次数
_waiters = 0,
_recursions = 0; \\ 锁的重入次数
_object = NULL;
_owner = NULL; \\ 以后持有 ObjectMonitor 的线程
_WaitSet = NULL; \\wait()办法调用后的线程期待队列
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ; \\ 阻塞期待队列
FreeNext = NULL ;
_EntryList = NULL ; \\synchronized 进来线程的排队队列
_SpinFreq = 0 ;
_SpinClock = 0 ; \\ 自旋计算
OwnerIsThread = 0 ;
}
- 每个线程都有两个 ObjectMonitor 对象列表,别离为 free 和 used 列表,如果以后 free 列表为空,线程将向全局 global list 申请调配 ObjectMonitor
- ObjectMonitor 的 owner、WaitSet、Cxq、EntryList 这几个属性比拟要害。WaitSet、Cxq、EntryList 的队列元素是包装线程后的对象 -ObjectWaiter;而获取 owner 的线程,既为取得锁的线程
- monitorenter 对应的执行办法
void ATTR ObjectMonitor::enter(TRAPS) {
...
// 获取锁:cmpxchg_ptr 原子操作,尝试将_owner 替换为本人,并返回旧值
cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
...
// 反复获取锁,次数加 1,返回
if (cur == Self) {
_recursions ++ ;
return ;
}
// 首次获取锁状况解决
if (Self->is_lock_owned ((address)cur)) {assert (_recursions == 0, "internal state error");
_recursions = 1 ;
_owner = Self ;
OwnerIsThread = 1 ;
return ;
}
...
// 尝试自旋获取锁
if (Knob_SpinEarly && TrySpin (Self) > 0) {...
-
monitorexit 对应的执行方 法
void ATTR ObjectMonitor::exit(TRAPS)...
代码太长,就不贴了。次要是 recursions 减 1、count 缩小 1 或者如果线程不再持有 owner(非重入加锁)则设置 owner 为 null,退锁的持有状态,并唤醒 Cxq 队列的线程
总结
- 线程遇到 synchronized 同步时,先会进入 EntryList 队列中,而后尝试把 owner 变量设置为以后线程,同时 monitor 中的计数器 count 加 1,即取得对象锁。否则通过 尝试自旋肯定次数加锁,失败则进入 Cxq 队列阻塞期待
- 线程执行结束将开释持有的 owner,owner 变量复原为 null,count 自减 1,以便其余线程进入获取锁
- synchronized 润饰办法原理也是相似的。只不过没用 monitor 指令,而是应用 ACC_SYNCHRONIZED 标识办法的同步
public synchronized void lock(){System.out.println("world");
}
....
public synchronized void lock();
descriptor: ()V
flags: (0x0029) ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=0, args_size=0
0: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #26 // String world
5: invokevirtual #28 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
- synchronized 是可重入,非偏心锁,因为 entryList 的线程会先自旋尝试加锁,而不是退出 cxq 排队期待,不偏心
3 Object 的 wait 和 notify 办法原理
- wait,notify 必须是持有以后对象锁 Monitor 的线程能力调用 (对象锁代指 ObjectMonitor/Monitor,锁对象代指 Object)
- 下面有说到,当在 sychronized 中锁对象 Object 调用 wait 时会退出 waitSet 队列,WaitSet 的元素对象就是 ObjectWaiter
class ObjectWaiter : public StackObj {
public:
enum TStates {TS_UNDEF, TS_READY, TS_RUN, TS_WAIT, TS_ENTER, TS_CXQ} ;
enum Sorted {PREPEND, APPEND, SORTED} ;
ObjectWaiter * volatile _next;
ObjectWaiter * volatile _prev;
Thread* _thread;
ParkEvent * _event;
volatile int _notified ;
volatile TStates TState ;
Sorted _Sorted ; // List placement disposition
bool _active ; // Contention monitoring is enabled
public:
ObjectWaiter(Thread* thread);
void wait_reenter_begin(ObjectMonitor *mon);
void wait_reenter_end(ObjectMonitor *mon);
};
调用对象锁的 wait()办法时,线程会被封装成 ObjectWaiter,最初应用 park 办法挂起
//objectMonitor.cpp
void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS){
...
// 线程封装成 ObjectWaiter 对象
ObjectWaiter node(Self);
node.TState = ObjectWaiter::TS_WAIT ;
...
// 一系列判断操作,当线程的确退出 WaitSet 时,则应用 park 办法挂起
if (node._notified == 0) {if (millis <= 0) {Self->_ParkEvent->park () ;
} else {ret = Self->_ParkEvent->park (millis) ;
}
}
而当对象锁应用 notify()时
- 如果 waitSet 为空,则间接返回
- waitSet 不为空从 waitSet 获取一个 ObjectWaiter,而后依据不同的 Policy 退出到 EntryList 或通过
Atomic::cmpxchg_ptr
指令自旋操作退出 cxq 队列 或者间接 unpark 唤醒
void ObjectMonitor::notify(TRAPS){CHECK_OWNER();
//waitSet 为空,则间接返回
if (_WaitSet == NULL) {TEVENT (Empty-Notify) ;
return ;
}
...
// 通过 DequeueWaiter 获取_WaitSet 列表中的第一个 ObjectWaiter
Thread::SpinAcquire (&_WaitSetLock, "WaitSet - notify") ;
ObjectWaiter * iterator = DequeueWaiter() ;
if (iterator != NULL) {
....
if (Policy == 2) { // prepend to cxq
// prepend to cxq
if (List == NULL) {
iterator->_next = iterator->_prev = NULL ;
_EntryList = iterator ;
} else {
iterator->TState = ObjectWaiter::TS_CXQ ;
for (;;) {
ObjectWaiter * Front = _cxq ;
iterator->_next = Front ;
if (Atomic::cmpxchg_ptr (iterator, &_cxq, Front) == Front) {break ;}
}
}
}
- Object 的 notifyAll 办法则对应
voidObjectMonitor::notifyAll(TRAPS)
,流程和 notify 相似。不过会通过 for 循环取出 WaitSet 的 ObjectWaiter 节点,再顺次唤醒所有线程
4 jvm 对 synchronized 的优化
- 先介绍下 32 位 JVM 下 JAVA 对象头的构造
-
偏差锁
- 未加锁的时候,锁标记为 01,蕴含哈希值、年龄分代和偏差锁标记位(0)
- 施加偏差锁时,哈希值和一部分无用内存会转化为锁客人的线程信息,以及加锁时的工夫戳 epoch,此时锁标记位没变,偏差锁标记改为 1
- 加锁时先判断以后线程 id 是否与 MarkWord 的线程 id 是否统一,统一则执行同步代码;不统一则查看偏差标记是否偏差,未偏差则应用 CAS 加锁;未偏差 CAS 加锁失败 和存在偏差锁 会导致偏差锁收缩为轻量级锁,或者从新偏差
- 偏差锁只有遇到其余线程竞争偏差锁时,持有偏差锁的线程才会开释锁,线程不会被动去开释偏差锁
-
轻量级锁
- 当产生多个线程竞争时,偏差锁会变为轻量级锁,锁标记位为 00
- 取得锁的线程会先将偏差锁撤销(在平安点),并在栈桢中创立锁记录 LockRecord,对象的 MarkWord 被复制到刚创立的 LockRecord,而后 CAS 尝试将记录 LockRecord 的 owner 指向锁对象,再将锁对象的 MarkWord 指向锁,加锁胜利
- 如果 CAS 加锁失败,线程会 自旋肯定次数加锁,再失败则降级为重量级锁
-
重量级锁
- 重量级锁就是下面介绍到 synchronized 应用监视器 Monitor 实现的锁机制
- 竞争线程强烈,锁则持续收缩,变为重量级锁,也是互斥锁,锁标记位为 10,MarkWord 其余内容被替换为一个指向对象锁 Monitor 的指针
-
自旋锁
- 缩小不必要的 CPU 上下文切换;在轻量级锁降级为重量级锁时,就应用了自旋加锁的形式
-
锁粗化
- 屡次加锁操作在 JVM 外部也是种耗费,如果多个加锁能够合并为一个锁,就可缩小不必要的开销
Test.class
// 编译器会思考将两次加锁合并
public void test(){synchronized(this){System.out.println("hello");
}
synchronized(this){System.out.println("world");
}
}
-
锁打消
- 删除不必要的加锁操作,如果变量是独属一个线程的栈变量,加不加锁都是平安的,编译器会尝试打消锁
- 开启锁打消须要在 JVM 参数上设置
-server -XX:+DoEscapeAnalysis -XX:+EliminateLocks
//StringBuffer 的 append 操作会加上 synchronized,// 然而变量 buf 不加锁也平安的,编译器会把锁打消
public void test() {StringBuffer buf = new StringBuffer();
buf.append("hello").append("world");
}
-
其余锁优化办法
- 分段锁,分段锁也并非一种理论的锁,而是一种思维;ConcurrentHashMap 是学习分段锁的最好实际。次要是将大对象拆成小对象,而后对大对象的加锁操作变成对小对象加锁,减少了并行度
5 CAS 的底层原理
- 在
volatile int i = 0; i++
中,volatile 类型的读写是原子同步的,然而 i ++ 却不能保障同步性,咱们该怎么呢? - 能够应用 synchronized 加锁;还有就是用 CAS(比拟并替换),应用乐观锁的思维同步,先判断共享变量是否扭转,没有则更新。上面看看不同步版本的 CAS
int expectedValue = 1;
public boolean compareAndSet(int newValue) {if(expectedValue == 1){
expectedValue = newValue;
return ture;
}
return false;
}
在 jdk 是有提供同步版的 CAS 解决方案,其中应用了 UnSafe.java 的底层办法
//UnSafe.java
@HotSpotIntrinsicCandidate
public final native boolean compareAndSetInt(Object o, long offset, int expected, int x) ..
@HotSpotIntrinsicCandidate
public final native int compareAndExchangeInt(Object o, long offset, int expected, int x)...
咱们再来看看本地办法,Unsafe.cpp 中的 compareAndSwapInt
//unsafe.cpp
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
UnsafeWrapper("Unsafe_CompareAndSwapInt");
oop p = JNIHandles::resolve(obj);
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
在 Linux 的 x86,Atomic::cmpxchg 办法的实现如下
/**
1 __asm__示意汇编的开始;2 volatile 示意禁止编译器优化;// 禁止指令重排
3 LOCK_IF_MP 是个内联函数,依据以后零碎是否为多核处理器,决定是否为 cmpxchg 指令增加 lock 前缀 // 内存屏障
*/
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {int mp = os::is_MP();
__asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
: "=a" (exchange_value)
: "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
: "cc", "memory");
return exchange_value;
}
到这一步,能够总结到:jdk 提供的 CAS 机制,在汇编层级,会禁止变量两侧的指令优化,而后应用 cmpxchg 指令比拟并更新变量值(原子性),如果是多核则应用 lock 锁定(缓存锁、MESI)
6 CAS 同步操作的问题
-
ABA 问题
- 线程 X 筹备将变量的值从 A 改为 B,然而这期间线程 Y 将变量的值从 A 改为 C,而后再改为 A;最初线程 X 检测变量值是 A,并置换为 B。但实际上,A 曾经不再是原来的 A 了
- 解决办法,是把变量定为惟一类型。值能够加上版本号,或者工夫戳。如加上版本号,线程 Y 的批改变为 A1->B2->A3,此时线程 X 再更新则能够判断出 A1 不等于 A3
-
只能保障一个共享变量的原子操作
- 只保障一个共享变量的原子操作,对多个共享变量同步时,循环 CAS 是无奈保障操作的原子
7 基于 volatile + CAS 实现同步锁的原理
- CAS 只能同步一个变量的批改,咱们又应该如何用它来锁住代码块呢?
-
先说说实现锁的因素
- 1 同步代码块同一时刻只能有一个线程能执行
- 2 加锁操作要 happens-before 同步代码块里的操作,而代码块里的操作要 happens-before 解锁操作
- 3 同步代码块完结后绝对其余线程其批改的变量是可见的 (内存可见性)
-
因素 1:能够利用 CAS 的原子性来实现,任意时刻只有一个线程能胜利操作变量
- 先构想 CAS 操作的共享变量是一个关联代码块的同步状态变量,同步开始之前先 CAS 更新 状态变量 为加锁状态,同步完结之后,再 CAS状态变量 为无锁状态
- 如果期间有第二个线程来加锁,则会发现状态变量为加锁状态,则放弃执行同步代码块
-
因素 2:应用 volatile 润饰状态变量,禁止指令重排
- volatile 保障同步代码里的操作 happens-before 解锁操作,而加锁操作 happens-before 代码块里的操作
-
因素 3:还是用 volatile,volatile 变量写指令前后会插入内存屏障
- volatile 润饰的状态变量被 CAS 为无锁状态前,同步代码块的脏数据就会被更新,被各个线程可见
// 伪代码
volatile state = 0 ; // 0- 无锁 1- 加锁;volatile 禁止指令重排,退出内存屏障
...
if(cas(state, 0 , 1)){ // 1 加锁胜利,只有一个线程能胜利加锁
... // 2 同步代码块
cas(state, 1, 0); // 3 解锁时 2 的操作具备可见性
}
8 LockSupport 理解一下
- LockSupport 是基于 Unsafe 类,由 JDK 提供的线程操作工具类,次要作用就是 挂起线程,唤醒线程。Unsafe.park,unpark 操作时,会调用以后线程的变量 parker 代理执行。Parker 代码
JavaThread* thread=JavaThread::thread_from_jni_environment(env);
...
thread->parker()->park(isAbsolute != 0, time);
class PlatformParker : public CHeapObj {
protected:
// 互斥变量类型
pthread_mutex_t _mutex [1] ;
// 条件变量类型
pthread_cond_t _cond [1] ;
...
}
class Parker : public os::PlatformParker {
private:
volatile int _counter ;
...
public:
void park(bool isAbsolute, jlong time);
void unpark();
...
}
- 在 Linux 零碎下,用的 POSIX 线程库 pthread 中的 mutex(互斥量),condition 来实现线程的挂起、唤醒
- 留神点:当 park 时,counter 变量被设置为 0,当 unpark 时,这个变量被设置为 1
-
unpark 和 park 执行程序不同时,counter 和 cond 的状态变动如下
- 先 park 后 unpark; park:counter 值不变,但会设置一个 cond; unpark:counter 先加 1,查看 cond 存在,counter 减为 0
- 先 unpark 后 park;park:counter 变为 1,但不设置 cond;unpark:counter 减为 0(线程不会因为 park 挂起)
- 先屡次 unpark;counter 也只设置为为 1
9 LockSupport.park 和 Object.wait 区别
- 两种形式都有具备挂起的线程的能力
- 线程在 Object.wait 之后必须等到 Object.notify 能力唤醒
- LockSupport 能够先 unpark 线程,等线程执行 LockSupport.park 是不会挂起的,能够继续执行
- 须要留神的是就算线程屡次 unpark;也只能让线程第一次 park 是不会挂起
10 AbstractQueuedSynchronizer(AQS)
- AQS 其实就是基于 volatile+cas 实现的锁模板;如果须要线程阻塞期待,唤醒机制,则应用 LockSupport 挂起、唤醒线程
//AbstractQueuedSynchronizer.java
public class AbstractQueuedSynchronizer{
// 线程节点
static final class Node {
...
volatile Node prev;
volatile Node next;
volatile Thread thread;
...
}
....
//head 期待队列头尾节点
private transient volatile Node head;
private transient volatile Node tail;
// The synchronization state. 同步状态
private volatile int state;
...
// 提供 CAS 操作,状态具体的批改由子类实现
protected final boolean compareAndSetState(int expect, int update) {return STATE.compareAndSet(this, expect, update);
}
}
- AQS 外部保护一个同步队列,元素就是包装了线程的 Node
- 同步队列中首节点是获取到锁的节点,它在开释锁的时会唤醒后继节点,后继节点获取到锁的时候,会把本人设为首节点
public final void acquire(int arg) {if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();}
- 线程会先尝试获取锁,失败则封装成 Node,CAS 退出同步队列的尾部。在退出同步队列的尾部时,会判断前驱节点是否是 head 结点,并尝试加锁(可能前驱节点刚好开释锁),否则线程进入阻塞期待
在 AQS 还存一个 ConditionObject 的外部类,它的应用机制和 Object.wait、notify 相似
//AbstractQueuedSynchronizer.java
public class ConditionObject implements Condition, java.io.Serializable {
// 条件队列;Node 复用了 AQS 中定义的 Node
private transient Node firstWaiter;
private transient Node lastWaiter;
...
- 每个 Condition 对象外部蕴含一个 Node 元素的 FIFO 条件队列
- 当一个线程调用 Condition.await()办法,那么该线程将会开释锁、结构 Node 退出条件队列并进入期待状态
// 相似 Object.wait
public final void await() throws InterruptedException{
...
Node node = addConditionWaiter(); // 结构 Node, 退出条件队列
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
// 挂起线程
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//notify 唤醒线程后,退出同步队列持续竞争锁
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
- 调用 Condition.signal 时,获取条件队列的首节点,将其挪动到同步队列并且利用 LockSupport 唤醒节点中的线程。随后继续执行 wait 挂起前的状态,调用 acquireQueued(node, savedState)竞争同步状态
// 相似 Object.notify
private void doSignal(Node first) {
do {if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
- volatile+cas 机制保障了代码的同步性和可见性,而 AQS 封装了线程阻塞期待挂起,解锁唤醒其余线程的逻辑。AQS 子类只需依据状态变量,判断是否可获取锁,是否开释锁胜利即可
- 继承 AQS 须要选性重写以下几个接口
protected boolean tryAcquire(int arg);// 尝试独占性加锁
protected boolean tryRelease(int arg);// 对应 tryAcquire 开释锁
protected int tryAcquireShared(int arg);// 尝试共享性加锁
protected boolean tryReleaseShared(int arg);// 对应 tryAcquireShared 开释锁
protected boolean isHeldExclusively();// 该线程是否正在独占资源,只有用到 condition 才须要取实现它
11 ReentrantLock 的原理
- ReentrantLock 实现了 Lock 接口,并应用外部类 Sync(Sync 继承 AbstractQueuedSynchronizer)来实现同步操作
- ReentrantLock 外部类 Sync
abstract static class Sync extends AbstractQueuedSynchronizer{
....
final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 间接 CAS 状态加锁,非偏心操作
if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);
return true;
}
}
...
// 重写了 tryRelease
protected final boolean tryRelease(int releases) {
c = state - releases; // 扭转同步状态
...
// 批改 volatile 润饰的状态变量
setState(c);
return free;
}
}
- Sync 的子类 NonfairSync 和 FairSync 都重写了 tryAcquire 办法
- 其中 NonfairSync 的 tryAcquire 调用父类的 nonfairTryAcquire 办法, FairSync 则本人重写 tryAcquire 的逻辑。其中调用 hasQueuedPredecessors()判断是否有排队 Node,存在则返回 false(false 会导致以后线程排队期待锁)
static final class NonfairSync extends Sync {protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);
}
}
....
static final class FairSync extends Sync {protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);
return true;
}
}
....
12 AQS 排他锁的实例 demo
public class TwinsLock implements Lock {private final Sync sync = new Sync(2);
@Override
public void lockInterruptibly() throws InterruptedException { throw new RuntimeException(""); }
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {throw new RuntimeException("");}
@Override
public Condition newCondition() { return sync.newCondition(); }
@Override
public void lock() { sync.acquireShared(1); }
@Override
public void unlock() { sync.releaseShared(1); } }
@Override
public boolean tryLock() { return sync.tryAcquireShared(1) > -1; }
}
再来看看 Sync 的代码
class Sync extends AbstractQueuedSynchronizer {Sync(int count) {if (count <= 0) {throw new IllegalArgumentException("count must large than zero");
}
setState(count);
}
@Override
public int tryAcquireShared(int reduceCount) {for (; ;) {int current = getState();
int newCount = current - reduceCount;
if (newCount < 0 || compareAndSetState(current, newCount)) {return newCount;}
}
}
@Override
public boolean tryReleaseShared(int returnCount) {for (; ;) {int current = getState();
int newCount = current + returnCount;
if (compareAndSetState(current, newCount)) {return true;}
}
}
public Condition newCondition() {return new AbstractQueuedSynchronizer.ConditionObject();
}
}
13 应用锁,能避免线程死循环吗
- 答案是不肯定的;对于单个资源来说是能够做的;然而多个资源会存在死锁的状况,例如线程 A 持有资源 X,期待资源 Y,而线程 B 持有资源 Y,期待资源 X
- 有了锁,能够对资源加状态管制,然而咱们还须要避免死锁的产生,突破产生死锁的四个条件之一就行
- 1 资源不可反复被两个及以上的使用者占用
- 2 使用者持有资源并期待其余资源
- 3 资源不可被抢占
- 4 多个使用者造成期待对方资源的循环圈
14 ThreadLocal 是否可保障资源的同步
- 当应用 ThreadLocal 申明变量时,ThreadLocal 为每个应用该变量的线程提供独立的变量正本,每一个线程都能够独立地扭转本人的正本,而不会影响其它线程所对应的正本
- 从下面的概念可知,ThreadLocal 其实并不能保障变量的同步性,只是给每一个线程调配一个变量正本
关注公众号,大家一起交换
参考文章
- objectMonitor.cpp
- Moniter 的实现原理
- JVM 源码剖析之 Object.wait/notify 实现
- Java 对象头与锁
- LockSupport 中 park 与 unpark 根本应用与原理
- Java 并发之 Condition
- 从 jvm 源码看 synchronized