Thread线程源码解析

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Thread线程源码解析相关的知识,希望对你有一定的参考价值。


1 首先我们来看Thread类的大致注释翻译如下:

Thread是程序执行中的一个线程,JVM允许程序有多个线程并发执行。

每个线程都有一个优先级,线程优先级高的先执行,每个线程都可能被标记为守护进程。在程序运行时,一个线程创建了新的Thread对象,会为新的线程对象设置优先级,其优先级会等于创建他的线程的优先级。

当JVM启动时,会创建一个非守护线程(被叫做"main"的主线程),JVM会继续执行线程直到如下情况:

-->

调用exit时,线程退出。System.exit(-1); //异常退出,0/1正常退出

除了守护线程,其他线程都挂掉了或者return或者抛异常就退出

-->

有两种方式实现Thread线程,一种是继承Thread子类,另一种是声明一个类实现Runnable

每个线程有特定标识,如果没被指定,就会分配一个默认的线程名字

2 观看前情提要:

注意点:线程组的出现时机。线程组的设计模式->组合模式(统一管理)

以 new Thread(myRunnable, "线程1").start();  总结:

先看有没线程组,有组就赋进去,有线程名就赋值进去,优先级给进去。他的线程状态得自父亲,再看安全管理器有没限制行为,有的话就限制一下,没有就不限制。

3 代码感想:

setPriority:如果线程组不为空,设置的优先级不能大于线程组优先级getMaxPriority

nextThreadID:通过自增++threadSeqNumber获取线程名中的序列号

clone:会抛CloneNotSupportedException,Thread不支持克隆

 

4 构造器解析

//1 无参构造,用法:new Thread();

public Thread()

init(null, null, "Thread-" + nextThreadNum(), 0);

解析:调用init(),初始化group、daemon、priority、stackSize、contextClassLoader等属性,其中daemon、priority、ContextClassLoader都默认从父类线程(即创建当前线程的线程)中获取。并默认给了一个线程名 "Thread-" + nextThreadNum() ,数字自增方式显示。

//2 用法:new Thread(Runnable runnable);

public Thread(Runnable target)

init(null, target, "Thread-" + nextThreadNum(), 0);

解析:将继承了Runnable接口的对象传入init并初始化,其他与1同。

//3 用法:new Thread(group, ruunable);

public Thread(ThreadGroup group, Runnable target)

init(group, target, "Thread-" + nextThreadNum(), 0);



//4 new Thread("线程名");

public Thread(String name)

init(null, null, name, 0);



//4 new Thread(group, "线程名");

public Thread(ThreadGroup group, String name)

init(group, null, name, 0);



//5 new Thread(runnable, "线程名");

public Thread(Runnable target, String name)

init(null, target, name, 0);



//6 new Thread(group, runnable, "线程名");

public Thread(ThreadGroup group, Runnable target, String name)

init(group, target, name, 0);



//7 new Thread(group, runnable, name, stackSize);

public Thread(ThreadGroup group, Runnable target, String name, long stackSize)

init(group, target, name, stackSize);

上面的Thread进行了多次重载,都调用了init()方法,现在讲解下init方法如下:

/**

* 初始化一个线程

* @param 一个线程组

* @param 调用run()方法的对象

* @param 新的线程名称

* @param 新线程需要的堆栈大小,或者0为忽视

* @param 通过控制上下文来继承,或可为null

**/

private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals)

//线程名为空抛异常

if (name == null)

throw new NullPointerException("name cannot be null");



//初始化线程名

this.name = name;

//获取当前该线程的当前线程,即被创建线程的父线程

Thread parent = currentThread();

/**

** 这里解释下什么是SecurityManager(jdk1.8中的注释):

** 安全管理器允许应用程序类实现一个安全策略,允许应用程序是否在安全或敏感的环境执行

** 安全管理器包含很多以check 开头的方法,这些方法在java类库中被个钟敏感操作调用,如下示例:

** SecurityManager security = System.getSecurityManager();

** if (security != null)

** security.check<i>XXX</i>(argument,  . . . );

**

** 安全管理器有机会阻止一个抛出异常的操作,如果操作允许,安全管理器程序就会直接返回,

** 不允许则抛SecurityException异常,这个异常唯一的意外是checkTopLevelWindow,他返回的是boolean,不会抛出异常。

** 当前安全管理器是通过System.setSecurityManager方法来设置,通过System.getSecurityManager来获得

** 以下有一个特定的方法:

** 指定一个请求是否被允许或否定,默认的继承调用如下:

** AccessController.checkPermission(perm)

** 请求允许,checkPermission就会返回空,否则抛SecurityException

** 以下是一个简单的调用

** Object context = null;

** SecurityManager sm = System.getSecurityManager();

** if (sm != null) context = sm.getSecurityContext();

** if (sm != null) sm.checkPermission(permission, context);

**/

SecurityManager security = System.getSecurityManager();

//构造函数初始化时没传入线程组

if (g == null)

/* Determine if its an applet or not */



/* If there is a security manager, ask the security manager what to do. */

if (security != null)

//安全管理器不为空,就从中取一个线程管理器,即一般不传线程组,就由安全管理器获取。

g = security.getThreadGroup();





/* If the security doesnt have a strong opinion of the matter use the parent thread group. */

//如果安全管理器都为空,拿不到线程组,那就从创建该线程的父线程中获取线程组

if (g == null)

g = parent.getThreadGroup();







/* checkAccess regardless of whether or not threadgroup is explicitly passed in. */

//决定当前运行线程是否由许可去修改线程组

g.checkAccess();



/*

* Do we have the required permissions?

*/

//安全管理器不为空时

if (security != null)

//验证当前实例是否能在不违反安全规范的情况下被构建,子类不能覆盖安全敏感的非final方法。

if (isCCLOverridden(getClass()))

security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);





//将ThreadGroup中的就绪线程计数器+1 nUnstartedThreads++,此时线程还没被加入ThreadGroup

g.addUnstarted();

//初始化Thread中属性 线程组、守护进程标识、优先级、

this.group = g;

//是否守护进程标识、优先级 都会继承自父线程(即创建当前线程的线程)

this.daemon = parent.isDaemon();

this.priority = parent.getPriority();

//如果安全管理器为空或允许父线程在不违反安全规范的情况下被构建,则重新获取父类加载器

if (security == null || isCCLOverridden(parent.getClass()))

this.contextClassLoader = parent.getContextClassLoader();

else

this.contextClassLoader = parent.contextClassLoader;

this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext();

this.target = target;

//设置优先级,如果当前设置的优先级大于线程组优先级则取当前线程组的最大优先级

/**

public final void setPriority(int newPriority)

ThreadGroup g;

checkAccess();

if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY)

throw new IllegalArgumentException();



if((g = getThreadGroup()) != null)

if (newPriority > g.getMaxPriority())

newPriority = g.getMaxPriority();



setPriority0(priority = newPriority);





**/

setPriority(priority);

if (inheritThreadLocals && parent.inheritableThreadLocals != null)

this.inheritableThreadLocals =

ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

/* Stash the specified stack size in case the VM cares */

this.stackSize = stackSize;



/* Set thread ID */

//设置线程id,return ++threadSeqNumber;

tid = nextThreadID();

注:默认情况下安全管理器是关闭的,可用-Djava.security.manager 开启。


5 线程启动过程讲解

上面我们说了构造方法是如何初始化的,接下来讲解下一些方法

初始化一个线程首先要实现他内部的run方法,然后再start(),示例如下:

new Thread() 

@Override

public void run()

//执行的操作



.start();

然后点开看Thread.java的源码可看到run()方法是调了runnable接口的run(),在没传入runnable即像这种new Thread(runnable)时可像上面一样重写run()。

@Override

public void run()

if (target != null)

target.run();



 

然后再看start()线程启动方法如下:

public synchronized void start() 

......

start0();

......

从上面可看到一个关键的native方法start0(),这个本地方法就是调用java在执行start()启动线程去调用run()方法的关键,如下:

private native void start0();

 

start()调用run()的过程如下:

大致流程图为:

Thread线程源码解析_线程

 

详细过程如下:

1 线程启动调用start()时,会调用一个native方法start0()。

public synchronized void start() 

......

start0();

......



private native void start0();

2 当start0被加载到jvm时,调用Thread.java类中registerNatives(该方法定义在Thread.c)进行注册该本地方方法(stop0之类的也是一样被注册)

private static native void registerNatives();

static

registerNatives();

3 然后可在Thread.c中看到定义好的线程公用数据和操作,可看到start0()映射的方法是JVM_StartThread如下:

static JNINativeMethod methods[] =  

"start0", "()V",(void *)&JVM_StartThread,

"stop0", "(" OBJ ")V", (void *)&JVM_StopThread,

"isAlive","()Z",(void *)&JVM_IsThreadAlive,

"suspend0","()V",(void *)&JVM_SuspendThread,

..................

因此java的线程调用start()实际会调到C里面的JVM_StartThread方法,然后JVM_StartThread又会调用thread_enrty如下:

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread)) 



native_thread = new JavaThread(&thread_entry, sz);



static void thread_entry(JavaThread* thread, TRAPS)

HandleMark hm(THREAD);

Handle obj(THREAD, thread->threadObj());

JavaValue result(T_VOID);

JavaCalls::call_virtual(&result,obj,

KlassHandle(THREAD,SystemDictionary::Thread_klass()),

vmSymbolHandles::run_method_name(), //重点:最终调到这个方法

vmSymbolHandles::void_method_signature(),THREAD);

所以可总结调用的链路如下:

Java启动一个线程即thread.start()->调用native方法start0()->通过registerNatives()注册start0()

->在Thread.c的methods找到start0映射的JVM_StartThread

->JVM_StartThread中new JavaThread

->调用thead_enrty->调vmSymbolHandles::run_method_name()

 

 

参考资料:

jdk1.8源码


 

 

 

 

 

 

thread start()方法源码解析

无论用哪种方式实现多线程,最终究其根源都是在调用Thread类的start()方法。而此时就有一个疑问了,若要实现多线程需要覆写其run()方法,而为什么要使用start()启动线程而不直接调用run()方法?

  • 1.首先来看一下start()方法的源码,看看这个方法究竟在干什么事情。
    /**
     * Causes this thread to begin execution; the Java Virtual Machine
     * calls the <code>run</code> method of this thread.
     * <p>
     * The result is that two threads are running concurrently: the
     * current thread (which returns from the call to the
     * <code>start</code> method) and the other thread (which executes its
     * <code>run</code> method).
     * <p>
     * It is never legal to start a thread more than once.
     * In particular, a thread may not be restarted once it has completed
     * execution.
     *
     * @exception  IllegalThreadStateException  if the thread was already
     *               started.
     * @see        #run()
     * @see        #stop()
     */
    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 */
            }
        }
    }

从整个方法上的注释看,start()使得该线程开始执行,Java虚拟机调用这个线程的run()方法,结果是两个线程会并发的运行:当前线程(调用start()方法的"主线程")与另一个线程(执行其run方法)

  • 2.3.start()之后的代码的核心就是其中调用的start0()方法,下面看看start0()的定义:
    private native void start0();

start0 是一个本地的方法,start0()这个方法是在Thread的静态块中来注册的,其作用就是注册一些本地方法提供给Thread类来使用,比如start0()、isAlive()… ,具体方法在一个C的文件中Thread.c,其定义了各个操作系统平台要用的关于线程的公共数据和操作,具体定义了很多的方法都是各个操作系统能公共调用的,也就是线程在构建时候,会将这些方法注册上去
 

    /* Make sure registerNatives is the first thing <clinit> does. */
    private static native void registerNatives();
    static {
        registerNatives();
    }
  • 3.start0() 会调用 JVM_StartThread 这个方法,JVM层面去启动一个线程

我们需要下载hotspot的源码才能找到,我们找到: jvm.cpp这个文件
JVM_ENTRY是用来定义 JVM_StartThread函数的,在这个函数里面创建了一个真正和平台有关的本地线程

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
  JVMWrapper("JVM_StartThread");
...
native_thread = new JavaThread(&thread_entry, sz);

hotspot的源码中 thread.cpp文件中1558行的位置

JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :
  Thread()
#if INCLUDE_ALL_GCS
  , _satb_mark_queue(&_satb_mark_queue_set),
  _dirty_card_queue(&_dirty_card_queue_set)
#endif // INCLUDE_ALL_GCS
{
  if (TraceThreadEvents) {
    tty->print_cr("creating thread %p", this);
  }
  initialize();
  _jni_attach_state = _not_attaching_via_jni;
  set_entry_point(entry_point);
  // Create the native thread itself.
  // %note runtime_23
  os::ThreadType thr_type = os::java_thread;
  thr_type = entry_point == &compiler_thread_entry ? os::compiler_thread :
                                                     os::java_thread;
  os::create_thread(this, thr_type, stack_sz);
  _safepoint_visible = false;
 
}

这个方法有两个参数,第一个是函数名称,线程创建成功之后会根据这个函数名称调用对应的函数;第二个是当前进程内已经有的线程数量。最后我们重点关注与一下 os::create_thread,实际就是调用平台创建线程的方法来创建线程。

  • 4.其中start方法中会有一个函数调用,os::start_thread(thread);,调用平台启动线程的方法,最终会调用Thread.cpp文件中的JavaThread::run()方法
void Thread::start(Thread* thread) {
  trace("start", thread);
  // Start is different from resume in that its safety is guaranteed by context or
  // being called from a Java method synchronized on the Thread object.
  if (!DisableStartThread) {
    if (thread->is_Java_thread()) {
      // Initialize the thread state to RUNNABLE before starting this thread.
      // Can not set it after the thread started because we do not know the
      // exact thread state at that time. It could be in MONITOR_WAIT or
      // in SLEEPING or some other state.
      java_lang_Thread::set_thread_status(((JavaThread*)thread)->threadObj(),
                                          java_lang_Thread::RUNNABLE);
    }
    os::start_thread(thread);
  }
}
  • 5.在其中JavaThread会进行一系列的初始化操作,最后有一个方法 thread_main_inner
void JavaThread::run() {
  // initialize thread-local alloc buffer related fields
  this->initialize_tlab();
  // used to test validitity of stack trace backs
  this->record_base_of_stack_pointer();
  // Record real stack base and size.
  this->record_stack_base_and_size();
  // Initialize thread local storage; set before calling MutexLocker
  this->initialize_thread_local_storage();
  this->create_stack_guard_pages();
  this->cache_global_variables();
  // Thread is now sufficient initialized to be handled by the safepoint code as being
  // in the VM. Change thread state from _thread_new to _thread_in_vm
  ThreadStateTransition::transition_and_fence(this, _thread_new, _thread_in_vm);
  assert(JavaThread::current() == this, "sanity check");
  assert(!Thread::current()->owns_locks(), "sanity check");
  DTRACE_THREAD_PROBE(start, this);
  // This operation might block. We call that after all safepoint checks for a new thread has
  // been completed.
  this->set_active_handles(JNIHandleBlock::allocate_block());
  if (JvmtiExport::should_post_thread_life()) {
    JvmtiExport::post_thread_start(this);
  }
  EventThreadStart event;
  if (event.should_commit()) {
     event.set_javalangthread(java_lang_Thread::thread_id(this->threadObj()));
     event.commit();
  }
  // We call another function to do the rest so we are sure that the stack addresses used
  // from there will be lower than the stack base just computed
  thread_main_inner();
  // Note, thread is no longer valid at this point!
}
  • 6.thread_main_inner 中,找到核心的代码块 this->entry_point()(this,this),在第四部中native_thread=newJavaThread(&thread_entry,sz); 的时候传递了一个thread_entry函数
void JavaThread::thread_main_inner() {
  assert(JavaThread::current() == this, "sanity check");
  assert(this->threadObj() != NULL, "just checking");
  // Execute thread entry point unless this thread has a pending exception
  // or has been stopped before starting.
  // Note: Due to JVM_StopThread we can have pending exceptions already!
  if (!this->has_pending_exception() &&
      !java_lang_Thread::is_stillborn(this->threadObj())) {
    {
      ResourceMark rm(this);
      this->set_native_thread_name(this->get_thread_name());
    }
    HandleMark hm(this);
    this->entry_point()(this, this);
  }
  DTRACE_THREAD_PROBE(stop, this);
  this->exit(false);
  delete this;
}
  • 7.我们找到thread_entry函数,找到vmSymbols::run_method_name()这个调用,通过回调方法调用Java线程中定义的run方法,让我们来看看, run_method_name是一个宏定义,在vmSymbols.hpp文件中可以找到
static void thread_entry(JavaThread* thread, TRAPS) {
{
  HandleMark hm(THREAD);
  Handle obj(THREAD, thread->threadObj());
  JavaValue result(T_VOID);
  JavaCalls::call_virtual(&result,
                          obj,
                          KlassHandle(THREAD, SystemDictionary::Thread_klass()),
                          vmSymbols::run_method_name(), //《---看看看------这里
                          vmSymbols::void_method_signature(),
                          THREAD);
}
  • 8.找到VM_SYMBOLS_DO 函数,我们可以看到,其对应的就是一个,方法,
#define VM_SYMBOLS_DO(template, do_alias)    
...
template(run_method_name, "run")  
...

总结全文

其大概意思就是,Java里面创建线程之后,必须要调用start方法才能创建一个线程,该方法会通过虚拟机启动一个本地线程,本地线程的创建会调用当前系统去创建线程的方法进行创建线程,并且线程被执行的时候会回调 run方法进行业务逻辑的处理,大概意思就是,系统给你留了一个接口,你要去用这个接口调用你的run 方法,该线程提供给你一系列的操作。
 

参考:

https://blog.csdn.net/qq_41259576/article/details/107056383

https://blog.csdn.net/weixin_43490440/article/details/101519957

https://developer.aliyun.com/article/72803

以上是关于Thread线程源码解析的主要内容,如果未能解决你的问题,请参考以下文章

thread start()方法源码解析

thread start()方法源码解析

thread start()方法源码解析

thread start()方法源码解析

Java并发源码解析

ThreadLocal源码解析