线程基础知识复习

并发相关Java包

  • 涉及到的包内容
    • java.util.concurrent
    • java.util.concurrent.atomic
    • java.util.concurrent.locks

image.png

JUC包创始人 Doug Lea

image.png

start线程解读

  • 初始程序
public static void main(String[] args) {
        Thread t1 = new Thread(() ->{
        },"t1");
        t1.start();
    }

//start
    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、Thread类在被加载的时候,首先会执行静态代码块,调用本地方法registerNatives去注册所有的本地方法。
2、在new Thread的过程中会执行如下核心逻辑:
1)设置线程的名字。
2)获取调用线程是否是守护线程及优先级并设置到当前线程。
3)设置线程的执行体到线程对象中。
4)最后重新设置优先级
3、然后执行start方法执行线程体,调用本地方法start0执行线程体。
综上所述,我们可以清楚知道,本地线程的创建和java线程与本地线程的映射的逻辑是在native层完成的。据此我们主要分析两个native方法的执行逻辑:
1)registerNatives :所有本地方法的注册。
2)start0 :创建本地线程并执行java线程体。
native方法就是一个Java调用非Java代码的接口。一个native方法是指该方法的实现由非Java语言实现,比如用C或C++实现。

private native void start0();//start0是一个native方法

native调用了本地方法,我们可以通过下载官网OpenJDK查看其源码

Native层的实现

native层由jdk实现,据此我们需要翻越jdk的源码,我用的openjdk1.8源码。
那么在java层线程对应的实现在 Thread.java,同样在natvie层叫Thread.c
openjdk/jdk/src/share/native/java/lang/Thread.c

#define THD "Ljava/lang/Thread;"
#define OBJ "Ljava/lang/Object;"
#define STE "Ljava/lang/StackTraceElement;"
#define STR "Ljava/lang/String;"

#define ARRAY_LENGTH(a) (sizeof(a)/sizeof(a[0]))

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},
};

#undef THD
#undef OBJ
#undef STE
#undef STR

JNIEXPORT void JNICALL
Java_java_lang_Thread_registerNatives(JNIEnv *env, jclass cls)
{
    (*env)->RegisterNatives(env,  cls, methods, ARRAY_LENGTH(methods));
}


由以上代码可以看到所有Thread.java中的本地方法都在这里注册。好,接下来我们看start0的本地实现JVM_StartThread
openjdk/hotspot/src/share/vm/prims/jvm.cpp

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
    JVMWrapper("JVM_StartThread");
JavaThread *native_thread = NULL;
{
    //此处省略若干代码 ……
    
    //创建本地线程,用与包装java线程与内线线程
    native_thread = new JavaThread(&thread_entry, sz);
    
    //此处省略若干代码 ……
    
}

//启动本地线程
Thread::start(native_thread);

JVM_END

继续追进 new JavaThread(&thread_entry, sz);
/home/wangzijie/code2/openjdk/hotspot/src/share/vm/runtime/thread.cpp

JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :
Thread()
    
{
    //此处省略若干代码 ……
    
    //此时调用和os平台相关的线程创建实现
    os::create_thread(this, thr_type, stack_sz);
    
    //此处省略若干代码 ……
    
}

继续追追进去 os::create_thread(this, thr_type, stack_sz); 我们选择linux的线程实现
openjdk/hotspot/src/os/linux/vm/os_linux.cpp

bool os::create_thread(Thread* thread, ThreadType thr_type, size_t stack_size) {
    
    
    //此处省略若干代码 ……
    
    // Allocate the OSThread object 创建 os线程对象
    OSThread* osthread = new OSThread(NULL, NULL);
    if (osthread == NULL) {
        return false;
    }
    
    //此处省略若干代码 ……
    
    // set the correct thread state
    osthread->set_thread_type(thr_type);
    
    //此处省略若干代码 ……
    
    // Initial state is ALLOCATED but not INITIALIZED
    osthread->set_state(ALLOCATED);
    
    //此处省略若干代码 ……
    
    thread->set_osthread(osthread);
    
    //此处省略若干代码 ……
    
    // 初始化线程创建参数
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    
    //此处省略若干代码 ……
    
    pthread_t tid;
    //到这里我们可以看到熟悉的glibc线程创函数pthread_create,到此真正的native线程就创建出来了,javastart是线程的执行入口函数
    int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread);
    
    //此处省略若干代码 ……
    
    pthread_attr_destroy(&attr);
    
    // 设置native线程与osthread设置关联
    osthread->set_pthread_id(tid);
    
    //此处省略若干代码 ……
    
}

继续追进 java_start
openjdk/hotspot/src/os/linux/vm/os_linux.cpp

static void *java_start(Thread *thread) {
    {
        //此处省略若干代码 ……
        
        // 设置线程状态为INITIALIZED
        osthread->set_state(INITIALIZED);
        sync->notify_all();
        
        //此处省略若干代码 ……
        
        // 注意这里由于创建线程的时候设置了状态为INITIALIZED,此处会一直阻塞,这也就是java线程创建完以后的状态,并不是立马执行
        while (osthread->get_state() == INITIALIZED) {
            sync->wait(Mutex::_no_safepoint_check_flag);
        }
    }
    
    //此处省略若干代码 ……
    
    // 执行线程,此处会执行java线程体的内容也就是Runnable的实现
    thread->run();
    return 0;
}

好了,到这里java线程的创建与执行基本已经分析完了,但是还差一点,那就是上面的代码的阻塞何时解除?因为解除阻塞以后才能执行thread->run,进而执行Runnable的的实现,真正执行我们的java代码。要回答这个问题我们需要往前看,还记得本地线程创建的入口吗

openjdk/hotspot/src/share/vm/prims/jvm.cpp

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
    JVMWrapper("JVM_StartThread");
JavaThread *native_thread = NULL;
{
    //此处省略若干代码 ……
    
    //创建本地线程,用与包装java线程与内线线程
    native_thread = new JavaThread(&thread_entry, sz);
    
    //此处省略若干代码 ……
    
}

//启动本地线程
Thread::start(native_thread);

JVM_END

这里的 Thread::start(native_thread); 我们追进去看一下
openjdk/hotspot/src/share/vm/runtime/thread.cpp

void Thread::start(Thread* thread) {  
    trace("start", thread);  
    if (!DisableStartThread) {   
        if (thread->is_Java_thread()) {
            //设置线程的状态为RUNNABLE解除阻塞执行run
            java_lang_Thread::set_thread_status(((JavaThread*)thread)->threadObj(), 
                                                java_lang_Thread::RUNNABLE);   
        }    
        os::start_thread(thread); 
    }	
}

这里的Thread::start会解除线程的阻塞进而执行run方法

Java多线程相关概念

1把锁

2个并(并发和并行)

  1. 并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行(需要多核CPU)
  2. 并发是指两个任务都请求运行,而处理器只能接收一个任务,就是把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行(12306抢票的案例)

3个程(进程 线程 管程)

  • 通过上面start线程的案例,其实进程线程都来源于操作系统。

进程

系统中运行的一个应用程序就是一个进程,每一个进程都有它自己的内存空间和系统资源。

线程

也被称为轻量级进程,在同一个进程内基本会有1一个或多个线程,是大多数操作系统进行调度的基本单元。

进程是资源分配的单位,线程是cpu调度的单位。

管程

Monitor(监视器),也就是我们平时说的锁
Monitor其实是一种同步机制,他的义务是保证(同一时间)只有一个线程可以访问被保护的数据和代码。
JVM中同步是基于进入和退出监视器对象(Monitor,管程对象)来实现的,每个对象实例都会有一个Monitor对象,
Monitor对象会和Java对象一同创建并销毁,它底层是由C++语言来实现的。

进程VS线程

进程是…,线程是…,进程和线程的最大不同在于进程基本上是独立的,而线程不一定,线程共享方法区,线程私有****栈本地方法栈程序计数器

用户线程和守护线程

Java线程分为用户线程和守护线程

  • 线程的daemon属性为
    • true表示是守护线程
    • false表示是用户线程

[

](https://blog.csdn.net/dolpin_ink/article/details/125082584)

用户线程

是系统的工作线程,它会完成这个程序需要完成的业务操作

守护线程

是一种特殊的线程,为其他线程服务的,在后台默默地完成一些系统性的服务,比如垃圾回收线程。

总结

public class DaemonDemo
{
public static void main(String[] args)
{
    Thread t1 = new Thread(() -> {
        System.out.println(Thread.currentThread().getName()+"\t 开始运行,"+(Thread.currentThread().isDaemon() ? "守护线程":"用户线程"));
        while (true) {

        }
    }, "t1");
    //线程的daemon属性为true表示是守护线程,false表示是用户线程
    //---------------------------------------------
    t1.setDaemon(true);
    //-----------------------------------------------
    t1.start();
    //3秒钟后主线程再运行
    try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }

    System.out.println("----------main线程运行完毕");
}

}

两种情况

  1. 未加t1.setDaemon(true);,默认是用户线程,他会继续运行,所以灯亮着
  2. 加了t1.setDaemon(true);是守护线程,当用户线程main方法结束后自动退出了
  • 守护线程作为一个服务线程,没有服务对象就没有必要继续运行了,如果用户线程全部结束了,意味着程序需要完成的业务操作已经结束了,系统可退出了。假如当系统只剩下守护线程的时候,java虚拟机会自动退出。

  • setDaemon(true)方法必须在start()之前设置,否则报IIIegalThreadStateException异常

posted @ 2022-07-16 23:06  金融融融融果果  阅读(19)  评论(0编辑  收藏  举报