pthread_create函数
前言
在Java中我们知道调用Thread.start()方法可以使得一个线程处于一个就绪状态.有的同学可能会纳闷,为什么不是立刻执行?原因在于Java中的线程模型是和OS(Operation System)一一对应的.Java层面创建了一个线程的概念模型,Thread在交给JVM之后,JVM会去OS中申请CPU时间片轮转,如果申请到了资源,进而在OS中创建线程,JVM到OS这个过程调用了JNI.让C++去执行相关函数,进而调用OS的pthread_create方法.这个方法会在下方进行讲解.
如果显示找不到pther_create命令的手册,需要执行以下命令:
yum -y install man-pages
1.Thread.start()
private volatile int threadStatus = 0;
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
源码解读:
1.该方法是由Synchronized修饰符进行修饰的.意味着它在同一时刻只能被一个线程进行调用.JVM要做到一个线程只能调用一次start()方法.
后面又是通过一个volatile修饰的threadStatus变量来表示该线程的状态.
2.group.add(this); 线程组的概念.方便统一管理.
3.start0()方法
private native void start0();
这个方法在OpenJdk中的一个叫jvm.cpp文件中定义的,其中定义了start0()、sleep()等相关方法.它放在了一个static静态代码块中.静态代码块中有一个叫method[]的数组.将其相关方法存储在了该数组中.(后续我会再编译下openJdk的源码,截图给大家看).随后便调用了pthread_create()方法来进行创建线程.
2.pthread_create()
man pthrea_create // 查看Linux创建线程的源码
NAME
pthread_create - create a new thread
SYNOPSIS
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
Compile and link with -pthread.
DESCRIPTION
The pthread_create() function starts a new thread in the calling process. The new thread starts execution by invoking start_routine(); arg is passed as the sole argument of start_routine().
The new thread terminates in one of the following ways:
* It calls pthread_exit(3), specifying an exit status value that is available to another thread in the same process that calls pthread_join(3).
* It returns from start_routine(). This is equivalent to calling pthread_exit(3) with the value supplied in the return statement.
* It is canceled (see pthread_cancel(3)).
* Any of the threads in the process calls exit(3), or the main thread performs a return from main(). This causes the termination of all threads in the process.
The attr argument points to a pthread_attr_t structure whose contents are used at thread creation time to determine attributes for the new thread; this structure is initialized using
pthread_attr_init(3) and related functions. If attr is NULL, then the thread is created with default attributes.
Before returning, a successful call to pthread_create() stores the ID of the new thread in the buffer pointed to by thread; this identifier is used to refer to the thread in subsequent
calls to other pthreads functions.
The new thread inherits a copy of the creating thread's signal mask (pthread_sigmask(3)). The set of pending signals for the new thread is empty (sigpending(2)). The new thread does not
inherit the creating thread's alternate signal stack (sigaltstack(2)).
The new thread inherits the calling thread's floating-point environment (fenv(3)).
The initial value of the new thread's CPU-time clock is 0 (see pthread_getcpuclockid(3));
2.1 函数解读
2.1.1 该函数有4个参数
pthread_t * thread //这个参数一般是线程pid的地址,在全局变量中定义一个pid,函数结束时会返回一个线程的pid
const pthread_attr_t * attr // 这个参数表示线程的属性相关,因为每个OS的线程属性会有不同的差异,当这个参数为NULL的时候,就会默认当前OS下线程中的属性.
void *(*start_routine)(void *)// 这个参数就是我们在Java代码层面中写的run()中的内容,当前线程创建出来之后,将会执行方法体中的内容.
void * arg; // 方法体中的相关参数
2.1.2 函数描述
该函数会在进程中创建出一个新的线程,这个线程将会调用方法体开始执行.线程在被创建出来的时候,将会处于一种就绪状态.在CPU时间片轮转时会进入运行状态.在方法体运行完毕时,OS也会对其进行销毁(此处先抛开线程池)
Linux也对该函数的销毁做出了一定的描述.分为4种情况.
1.当执行pthread_exit()、pthread_join()
2.从start_routine()函数中返回.该情况与调用pthread_exit()返回
3.调用pthread_cancel()函数
4.该线程所在的进程执行了exit()函数, 或者从main线程中返回
3.后记
第一次写博客,如果写的不好还请各位朋友留下你的见解.我们一起讨论学习.
JNI中的Synchronized加锁流程
JDK12 对应的C++代码解读:
对应的文件:\jdk-jdk-12-31\jdk-jdk-12-31\src\hotspot\share\interpreter\bytecodeInterpreter.cpp
//TODO Cover synchronized 的加锁流程
CASE(_monitorenter): {
// TODO Cover
// 1.获取当前的锁对象
oop lockee = STACK_OBJECT(-1);
// derefing's lockee ought to provoke implicit null check
CHECK_NULL(lockee);
// find a free monitor or one already allocated for this object
// if we find a matching object then we need a new monitor
// since this is recursive enter
// 翻译:寻找一个自由状态的monitor 或者已经分配过的对象,如果我们找到了这个新的monitor对象, 因为这是一个锁重入
BasicObjectLock* limit = istate->monitor_base();
BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base();
BasicObjectLock* entry = NULL;
while (most_recent != limit ) {
if (most_recent->obj() == NULL) entry = most_recent;
else if (most_recent->obj() == lockee) break;
most_recent++;
}
// 2. 获取当前线程栈帧中的lock_record -> 它的结构为: Displaced Mark Word、Object reference
if (entry != NULL) {
// 3.将当前线程栈帧中的lock_record中的object reference 设置为锁对象
entry->set_obj(lockee);
// 4.初始化一个success,这个变量非常重要,后面的重要逻辑都跟这个变量有关
int success = false;
// 5.获取线程过期相关变量
uintptr_t epoch_mask_in_place = (uintptr_t)markOopDesc::epoch_mask_in_place;
// 6.获取锁对象的对象头的Mark Word
markOop mark = lockee->mark();
intptr_t hash = (intptr_t) markOopDesc::no_hash;
// implies UseBiasedLocking
// 7.JVM是否开启了偏向锁模式
if (mark->has_bias_pattern()) {
uintptr_t thread_ident;
uintptr_t anticipated_bias_locking_value;
thread_ident = (uintptr_t)istate->thread();
// 8.根据线程id和当前锁对象的header 以及锁对象的Mark Word 做一个位运算
anticipated_bias_locking_value =
(((uintptr_t)lockee->klass()->prototype_header() | thread_ident) ^ (uintptr_t)mark) &
~((uintptr_t) markOopDesc::age_mask_in_place);
// 9.如果经过位运算得到是一个0的值,表示着当前线程之前已经加过锁,此时的加锁是第二次偏向锁进行加锁.
// 这里假设Thread t1 第一次进行加锁,那么计算出来的这个值不会是0
if (anticipated_bias_locking_value == 0) {
// already biased towards this thread, nothing to do
if (PrintBiasedLockingStatistics) {
(* BiasedLocking::biased_lock_entry_count_addr())++;
}
success = true;
}
// 10. 当前环境中是否仅用了偏向锁模式,这里涉及到一个批量重偏向的概念
// 预备知识:
// 偏向锁的撤销:一把偏向锁的撤销过程是检查当前线程是否处于活跃状态,判断活跃的依据是栈帧中是否能找到该锁对象对应lock_record,
// 栈帧中的如果处于活跃状态将会遍历当前线程栈帧中的锁记录和Mark Word要么重新偏向其他线程
// 要么重新偏向或标记该对象不适合作为偏向锁对象.不活跃状态,则会等到该线程处于一个safe point(安全点)的状态下,做偏向锁的撤销,将线程Id设置为null
// 批量撤销: 情况比较复杂的一种,当Thread t1对一个类的多个对象进行过加锁时,这些对象的对象头的Mark Word是t1线程id+101,t1线程执行完毕之后,
// Thread t2 再去获取这些对象,t2对这些对象进行加锁,由于这些对象的标识是t1的,当t2产生自己线程id加101的标识进行CAS加锁时,会失败,
// 这个时候将会发生轻量锁升级,t2会对这些对象逐个进行偏向锁的撤销.因为t1线程在执行完偏向锁加锁以及相关操作时,是不会进行撤销的.
// 而且如果t1做撤销将会使得偏向锁没有意义.所以这里要做轻量锁的升级,首先要进行偏向锁的撤销.如果JVM之前进行了19次偏向锁的撤销.
// 那么JVM将会认为这些对象的锁状态设计的有问题,不是特别好.JVM会在19次偏向锁撤销完毕之后,禁用偏向锁.直接升级为轻量锁.因为偏向锁的撤销也是比较-
// 消耗性能的. 这个20次偏向锁的撤销是JVM规定的.
// 如果当前环境被禁用了偏向锁模式,则不会给success赋值,将会进入下面的执行逻辑.
// 注意:当前环境偏向锁的禁用是局部的禁用,JVM的偏向锁的禁用是全局的禁用.两者不矛盾
else if ((anticipated_bias_locking_value & markOopDesc::biased_lock_mask_in_place) != 0) {
// try revoke bias
markOop header = lockee->klass()->prototype_header();
if (hash != markOopDesc::no_hash) {
header = header->copy_set_hash(hash);
}
if (lockee->cas_set_mark(header, mark) == mark) {
if (PrintBiasedLockingStatistics)
(*BiasedLocking::revoked_lock_entry_count_addr())++;
}
}
// 11. 当前锁对象中的对象头中的线程是否过期? 过期则进行重偏向
else if ((anticipated_bias_locking_value & epoch_mask_in_place) !=0) {
// try rebias
// 根据自己线程id生成一个新的header
markOop new_header = (markOop) ( (intptr_t) lockee->klass()->prototype_header() | thread_ident);
if (hash != markOopDesc::no_hash) {
new_header = new_header->copy_set_hash(hash);
}
// CAS判断判断当前线程从开始进入到现在锁对象的mark word是否被别人更改过,
// 如果没有更改过,则将自己的线程ID设置到锁对象的对象头中.如果有人更改过,则进行锁膨胀
if (lockee->cas_set_mark(new_header, mark) == mark) {
if (PrintBiasedLockingStatistics)
(* BiasedLocking::rebiased_lock_entry_count_addr())++;
}
else {
CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
}
success = true;
}
else {
// 12.进入该逻辑的条件是
// 1.匿名偏向: 当前锁对象没有被任何线程加过锁.
// 2.新的线程过来进行加锁, 这个时候要进行锁膨胀,膨胀为轻量锁
// 偏向锁在膨胀为轻量锁时,要做偏向锁的撤销. 撤销之后的对象头结构为线程ID为空,锁标识变为001
// try to bias towards thread in case object is anonymously biased
markOop header = (markOop) ((uintptr_t) mark & ((uintptr_t)markOopDesc::biased_lock_mask_in_place |
(uintptr_t)markOopDesc::age_mask_in_place |
epoch_mask_in_place));
if (hash != markOopDesc::no_hash) {
header = header->copy_set_hash(hash);
}
markOop new_header = (markOop) ((uintptr_t) header | thread_ident);
// debugging hint
DEBUG_ONLY(entry->lock()->set_displaced_header((markOop) (uintptr_t) 0xdeaddead);)
if (lockee->cas_set_mark(new_header, header) == header) {
if (PrintBiasedLockingStatistics)
(* BiasedLocking::anonymously_biased_lock_entry_count_addr())++;
}
else {
CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
}
success = true;
}
}
// traditional lightweight locking
// 13.轻量锁加锁过程
if (!success) {
// 在内存中产生一个无锁的001标识.
markOop displaced = lockee->mark()->set_unlocked();
// 这里的entry指的是当前线程栈帧中的lock_record,将lock_record中的Displaced Mark Word设置为无锁.因为轻量锁在释放的时候,要将对象头设置为无锁。
entry->lock()->set_displaced_header(displaced);
bool call_vm = UseHeavyMonitors;
// CAS操作 将内存中的001 和 对象头的锁标识做比较,如果两者相等,则替换成功,加锁成功,加锁失败则会进行膨胀为重量锁
if (call_vm || lockee->cas_set_mark((markOop)entry, displaced) != displaced) {
// Is it simple recursive case?
// 加锁成功,判断是否是轻量锁的重入,重入的话是会在栈帧中创建一个新的lock_record,并将Displaced Mark Word设置为NULL.
if (!call_vm && THREAD->is_lock_owned((address) displaced->clear_lock_bits())) {
entry->lock()->set_displaced_header(NULL);
} else {
CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
}
}
}
// CPU执行下一条指令
UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
} else {
istate->set_msg(more_monitors);
UPDATE_PC_AND_RETURN(0); // Re-execute
}
}