// 等待具体时间
> sleep(time)
// 该方式不释放锁 ,低优先级有机会执行
// sleep 后转入 阻塞(blocked)
> wait(time)
> join(time)
> LockSupport.parkNanos()
> LockSupport.parkUnit()
> yield
// 该方式同样不会释放锁 ,同优先级及高优先级执行
// 执行后转入ready
// 仅 进入等待
> wait()
> join()
> LockSuppot.park()
// 对于设定具体等待时间的 timeout 后自动转入就绪
// 其他等待
> notify()
> notifyAll()
> 不同线程之间采用字符串作为监视器锁,会唤醒别的线程
> 不同线程之间的信号没有共享,等待线程被唤醒后继续进入wait状态:
> 下图为不同线程的等待与唤醒
> 执行wait () 时释放锁 , 否则等待的线程如果继续持有锁 , 其他线程就没办法获取锁 , 会陷入死锁
// Wait - Notify 深入知识点
// 一 : Wait 等待知识点
- 当前线程必须拥有这个对象的监视器
// 二 : Wait 等待后
- 执行等待后 , 当前线程将自己放在该对象的等待集中,然后放弃该对象上的所有同步声明
- 如果当前线程在等待之前或等待期间被任何线程中断 , 会抛出 InterruptedException 异常
// 三 : 唤醒时注意点
- Notify 唤醒一个正在等待这个对象的线程监控 (monitor)
- 执行 wait 时会释放锁 , 同时执行 notify 后也会释放锁 (如下图)
- notify 会任意选择一个等待对象来提醒
// 四 : 唤醒后知识点
- 线程唤醒后 , 仍然要等待该对象的锁被释放
- 线程唤醒后 , 将会与任何竞争该锁的对象公平竞争
// 假醒 :
线程也可以在不被通知、中断或超时的情况下被唤醒,这就是所谓的伪唤醒。
> interrupt()
// 方法,用于中断线程。调用该方法的线程的状态为将被置为”中断”状态。
> interrupted ()
// 查询当前线程的中断状态,并且清除原状态。
> isInterrupted ()
// 查询指定线程的中断状态,不会清除原状态+
// interrupt() 方法干了什么 ?
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
// 1 checkAccess() : 其中涉及到 SecurityManager , 所以我们先看看这个类干什么的
- SecurityManager security = System.getSecurityManager();
- security.checkAccess(this);
C- SecurityManager :
?- 这是 Java.lang 底下的一个类
死锁简介 : 当多个进程竞争资源时互相等待对方的资源
死锁的条件 :
// 资源的分类
- 可抢占资源 : 可抢占资源指某进程在获得这类资源后,该资源可以再被其他进程或系统抢占 , 例如 CPU 资源
- 不可抢占资源
// 死锁的常见原因 :
- 竞争不可抢占资源引起死锁 (共享文件)
- 竞争可消耗资源引起死 (程通信时)
- 进程推进顺序不当引起死锁
// 死锁的预防
- 通过系统中尽量破坏死锁的条件 , 当四大条件有一个不符合时 , 死锁就不会发生
- 通过加锁顺序处理(线程按照一定的顺序加锁)
- 加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
- 死锁检测
// 死锁的解除
- 资源剥离 , 挂起死锁进程且强制对应资源 , 分配进程
- 撤销进程
- 回退进程
> 热锁不算一个物理概念 , 它表示线程在频繁的竞争资源并且资源在频繁的切换\
- 循环等待 :
> 线程有以下状态
- NEW : 尚未启动的线程的hread状态
- RUNNABLE : 可运行 , 从虚拟机的角度 , 已经执行 ,但是可能正在等待资源
- BLOCKED : 阻塞 , 此时等待 monitor锁 , 以读取 synchronized 代码
- WAITING : 等待状态 , 处于等待状态的线程正在等待另一个线程执行特定操作
- wait()
- join()
- LockSupport#park()
- TIMED_WAITING : 指定等待时间的等待
- Thread.sleep
- wait(long)
- join(long)
- LockSupport#parkNanos
- LockSupport#parkUntil
- TERMINATED : 终止线程
// 线程间状态改变的方式
• 还没起床:sleeping 。
• 起床收拾好了,随时可以坐地铁出发:Runnable 。
• 等地铁来:Waiting 。
• 地铁来了,但要排队上地铁:I/O 阻塞 。
• 上了地铁,发现暂时没座位:synchronized 阻塞。
• 地铁上找到座位:Running 。
• 到达目的地:Dead 。
// 节点一 : 你是否发现 , wait 和 notify 是 object 的方法
点开 wait 和 notify 方法就能发现 , 这两个方法是基于 Object 对象的 , 所以我们要理解 ,通知不是通知的线程 ,而是通知的对象
这也就是为什么 , 不要用常量作为通知对象
// 节点二 : java.lang.IllegalMonitorStateException
当我们 wait/notify 时 , 如果未获取到对应对象的 Monitor , 实际上我们会抛出 IllegalMonitorStateException
所以你先要获得监视器 , 有三种方式 :
- 通过执行该对象的同步实例方法。
- 通过执行在对象上同步语句体。
- 对于类型为Class的对象,可以执行该类的同步静态方法。
// 节点三 : 如何进行转换 ?
Step 1 : 首先看 Object 对象 Native 方法 , bative src 中搜索名字即可
static JNINativeMethod methods[] = {
{"hashCode", "()I", (void *)&JVM_IHashCode},
{"wait", "(J)V", (void *)&JVM_MonitorWait},
{"notify", "()V", (void *)&JVM_MonitorNotify},
{"notifyAll", "()V", (void *)&JVM_MonitorNotifyAll},
{"clone", "()Ljava/lang/Object;", (void *)&JVM_Clone},
};
//可以看到这里分别调用了 JVM_MonitorWait , JVM_MonitorNotify , JVM_MonitorNotifyAll ,
//从名字就能看到 , 这里是和Monitor 有关的
Step 2 : 进入全路径了解 : \openjdk\hotspot\src\share\vm\prims
JVM_ENTRY(void, JVM_MonitorWait(JNIEnv* env, jobject handle, jlong ms))
JVMWrapper("JVM_MonitorWait");
Handle obj(THREAD, JNIHandles::resolve_non_null(handle));
JavaThreadInObjectWaitState jtiows(thread, ms != 0);
if (JvmtiExport::should_post_monitor_wait()) {
JvmtiExport::post_monitor_wait((JavaThread *)THREAD, (oop)obj(), ms);
// 当前线程已经拥有监视器,并且还没有添加到等待队列中,因此当前线程不能成为后续线程
}
ObjectSynchronizer::wait(obj, ms, CHECK);
JVM_END
Step 3 : ObjectSynchronizer::wait(obj, ms, CHECK);
// TODO : 看不懂了呀....先留个坑
// 总得来说就是 ObjectMonitor通过一个双向链表来保存等待该锁的线程
Step End : link by @ https://www.jianshu.com/p/a604f1a9f875
void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) {
...................
// 创建ObjectWaiter,添加到_WaitSet队列中
ObjectWaiter node(Self);
node.TState = ObjectWaiter::TS_WAIT ;
Self->_ParkEvent->reset() ;
OrderAccess::fence(); // ST into Event; membar ; LD interrupted-flag
//WaitSetLock保护等待队列。通常只锁的拥有着才能访问等待队列
Thread::SpinAcquire (&_WaitSetLock, "WaitSet - add") ;
//加入等待队列,等待队列是循环双链表
AddWaiter (&node) ;
//使用的是一个简单的自旋锁
Thread::SpinRelease (&_WaitSetLock) ;
.....................
}
// 节点一 : 区别 run 和 start
run 是通过方法栈直接调用对象的方法 , 而 Start 才是开启线程 , 这一点我们可以从源码中发现 :
- start 方法是同步的
- start0 是一个 native 方法
- group 是线程组 (ThreadGroup) , 线程可以访问关于它自己线程组的信息
?- 线程组主要是为了管理线程 , 将一个大线程分成多个小线程 (盲猜 fork 用到了 , 后面验证一下)
?- 线程组也可以通过关闭组来关闭所有的线程
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
// Step 1 : Thread.c 结构 -> openjdk\src\native\java\lang
static JNINativeMethod methods[] = {
{"start0", "()V", (void *)&JVM_StartThread},
{"stop0", "(" OBJ ")V", (void *)&JVM_StopThread},
{"isAlive", "()Z", (void *)&JVM_IsThreadAlive},
{"suspend0", "()V", (void *)&JVM_SuspendThread},
{"resume0", "()V", (void *)&JVM_ResumeThread},
{"setPriority0", "(I)V", (void *)&JVM_SetThreadPriority},
{"yield", "()V", (void *)&JVM_Yield},
{"sleep", "(J)V", (void *)&JVM_Sleep},
{"currentThread", "()" THD, (void *)&JVM_CurrentThread},
{"countStackFrames", "()I", (void *)&JVM_CountStackFrames},
{"interrupt0", "()V", (void *)&JVM_Interrupt},
{"isInterrupted", "(Z)Z", (void *)&JVM_IsInterrupted},
{"holdsLock", "(" OBJ ")Z", (void *)&JVM_HoldsLock},
{"getThreads", "()[" THD, (void *)&JVM_GetAllThreads},
{"dumpThreads", "([" THD ")[[" STE, (void *)&JVM_DumpThreads},
{"setNativeName", "(" STR ")V", (void *)&JVM_SetNativeThreadName},
};
// \openjdk\hotspot\src\share\vm\prims\jvm.cpp
// Step 2 : JVM_StartThread , 翻译了一下 , 大概可以看到那一句 native_thread = new JavaThread(&thread_entry, sz);
// 以及最后的 Thread::start(native_thread);
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
JVMWrapper("JVM_StartThread");
JavaThread *native_thread = NULL;
//由于排序问题,在抛出异常时不能持有Threads_lock。示例:在构造异常时,可能需要获取heap_lock。
bool throw_illegal_thread_state = false;
// 我们必须释放Threads_lock才能在Thread::start中post jvmti事件
{
// 确保c++ Thread和OSThread结构体在操作之前没有被释放
MutexLocker mu(Threads_lock);
//从JDK 5开始threadStatus用于防止重新启动一个已经启动的线程,所以我们通常会发现javthread是null。然而,对于JNI附加的线程,在创建的线程对象(带有javthread集)和更新其线程状态之间有一个小窗口,因此我们必须检查这一点
if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {
throw_illegal_thread_state = true;
} else {
//我们也可以检查stillborn标志来查看这个线程是否已经停止,但是由于历史原因,我们让线程在开始运行时检测它自己
jlong size =
java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
//分配c++线程结构并创建原生线程。
//从java检索到的堆栈大小是有符号的,但是构造函数接受size_t(无符号类型),因此避免传递负值,因为这会导致非常大的堆栈。
size_t sz = size > 0 ? (size_t) size : 0;
native_thread = new JavaThread(&thread_entry, sz);
// 此时可能由于缺少内存而没有为javthread创建osthread。检查这种情况并在必要时抛出异常。
// 最终,我们可能想要更改这一点,以便只在线程成功创建时才获取锁——然后我们也可以执行这个检查并在javthread构造函数中抛出异常。
if (native_thread->osthread() != NULL) {
// 注意:当前线程没有在“prepare”中使用。
native_thread->prepare(jthread);
}
}
}
if (throw_illegal_thread_state) {
THROW(vmSymbols::java_lang_IllegalThreadStateException());
}
assert(native_thread != NULL, "Starting null thread?");
if (native_thread->osthread() == NULL) {
// No one should hold a reference to the 'native_thread'.
delete native_thread;
if (JvmtiExport::should_post_resource_exhausted()) {
JvmtiExport::post_resource_exhausted(
JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR | JVMTI_RESOURCE_EXHAUSTED_THREADS,
"unable to create new native thread");
}
THROW_MSG(vmSymbols::java_lang_OutOfMemoryError(),
"unable to create new native thread");
}
Thread::start(native_thread);
JVM_END
C- Thread
M- yield() : 可以看到 , yield 同样是一个 native 方法
// Step 1: \openjdk\hotspot\src\share\vm\prims\jvm.cpp
// 主要是2句 : os::sleep(thread, MinSleepInterval, false);
// os::yield();
JVM_ENTRY(void, JVM_Yield(JNIEnv *env, jclass threadClass))
JVMWrapper("JVM_Yield");
if (os::dont_yield()) return;
#ifndef USDT2
HS_DTRACE_PROBE0(hotspot, thread__yield);
#else /* USDT2 */
HOTSPOT_THREAD_YIELD();
#endif /* USDT2 */
// 当ConvertYieldToSleep关闭(默认)时,这与传统VM的yield使用相匹配。对于类似的线程行为至关重要
if (ConvertYieldToSleep) {
os::sleep(thread, MinSleepInterval, false);
} else {
os::yield();
}
JVM_END
// TODO : 主要的其实还没有看懂 , 毕竟 C基础有限
阅读量:2015
点赞量:0
收藏量:0