JAVA线程模型

java当中的线程和操作系统的线程是什么关系?

关于操作系统的线程

linux操作系统的线程控制原语

 int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);

根据man配置的信息可以得出pthread_create会创建一个线程,这个函数是linux系统的函数,可以用C或者C++直接调用,上面信息也告诉程序员这个函数在pthread.h, 这个函数有四个参数

pthread_t *thread传出参数,调用之后会传出被创建线程的id定义 pthread_t pid;继而 取地址 &pid
const pthread_attr_t *attr线程属性,关于线程属性是linux的知识在学习pthread_create函数的时候一般穿NULL,保持默认属性
void (start_routine) (void *)线程的启动后的主体函数相当于java当中的run需要你定义一个函数,然后传函数名即可
void *arg主体函数的参数如果没有可以传NULL

在linux上启动一个线程的代码:

​
#include <pthread.h>//头文件
#include <stdio.h>
​
pthread_t pid; //定义一个变量,接受创建线程后的线程id
​
void*  thread_entity(void* args){
​
    while(1){
​
    usleep(500);        
    printf("i am new Thread!\n");
    }
   
}
​
int  main()
{
     //调用操作系统的函数创建线程,注意四个参数
    pthread_create(&pid,NULL,thread_entity,NULL);
   
   
   while(1){
    usleep(500);
    printf("main\n");
​
   }
    return 0;
}
​

执行 gcc -o xx thread.c -pthread 编译thread.c文件

运行 xx文件 ./xx

注意:这里面必须要让主线程进行一个死循环 否则主线程执行完毕之后 进程就被结束,无法去创建一个子线程出来。这和我们Java中创建一个线程不同

运行结果如下

我们可以看到使用我们c语言也能实现多线程,那么我们操作系统中的线程和我们JAVA中的线程之前又有什么关系呢?这两个是相同的嘛?从上面我们能够知道操作系统的多线程是调用pthread_create并且利用mutex(互斥)机制来完成,那么我们Java中的多线程是如何实现的呢。首先我们在使用的时候会去通过new thread创建一个线程,然后在执行thread.start()方法,去等待cpu的调度。一旦cpu执行到,那么将会执行我们自己的主题函数run(),最后去完成逻辑。

在这里为了大家更好的理解先来介绍一下我们的jdk,众所周知jdk是我们java运行时可以提供的一套内库,比如我们String类、Io类,但是sun公司除了提供这一套内库之外,sun公司还提供了一些C文件,那么sun公司它为什么要提供这些C文件呢?首先我们来看这些C文件的

作用,这些C文件大体来分的话,可以被分为两个作用。

  • 其一:实现调用操作系统的函数

  • 其二:实现调用JVM的一些代码

注意jdk中除了上文说的这些文件之外,还提供了java.exe【如果是windows系统】,那我们如何去理解java.exe呢,如果你去将这个java.exe进行放大的话,它是我们某一个项目编译完成得到的。那么这项目又是什么项目呢?其实是我们大名鼎鼎的hostpot

我们java层面去创建一个线程,其实并没有通过c语言直接去调用操作系统的pthread_create方法,【为什么???】而是通过c文件去先执行hotspot【因为我们hotspost虚拟机就是用c或者c++写的】在hotspost虚拟机中有我们的JavaThread,进而通过JavaThread类再去调用我们的os系统中的pthread_create方法。

这个hotspot中的JavaThread和我们的Java层面的API是一一对应的,举个例子,比如我们Java层面的 start、sleep、wait等这些方法在JavaThread中都会唯一对应一个。

为什么我们在Java层面通过API不去直接调用os层面的线程创建呢?

  • 如果我们在api层面去直接调用os系统的话 我们就需要在每一个c文件中写上重复的线程创建代码,这样就会导致你的c文件很臃肿,因为核心都是差不多的c文件。

  • Jvm无法去对我们线程做管理了,此时将我们的执行权完全交出去了,导致脱离了Jvm。

那C语言又是如何调到我们的hotspot虚拟机?

我们先来看一下这个C文件在哪

D:\ChromeDownloads\openjdk8-master\openjdk\jdk\src\share\native\java\lang

/*
 * Copyright (c) 1994, 2012, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
​
/*-
 *      Stuff for dealing with threads.
 *      originally in threadruntime.c, Sun Sep 22 12:09:39 1991
 */
​
#include "jni.h"
#include "jvm.h"
​
#include "java_lang_Thread.h"
​
#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));
}
​

我们可以怎么去理解呢?

从源码中我们可以看到JNINativeMethod是一个静态变量,并且是数组类型,而在数组中的内容都是我们一些在Java中定义的native方法,我选取数组中的一种来举个例子

   {"start0",           "()V",        (void *)&JVM_StartThread}
   本地定义的native方法                hotspot虚拟机中的一个叫JVM_StartThread方法 

而StartThread这个方法不在我们jdk这个包下,在hostpot这个文件下的一个jvm.cpp

D:\ChromeDownloads\openjdk8-master\openjdk\hotspot\src\share\vm\prims

找到jvm.cpp的核心源码;

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
  JVMWrapper("JVM_StartThread");
  JavaThread *native_thread = NULL;
​
  // We cannot hold the Threads_lock when we throw an exception,
  // due to rank ordering issues. Example:  we might need to grab the
  // Heap_lock while we construct the exception.
  bool throw_illegal_thread_state = false;
​
  // We must release the Threads_lock before we can post a jvmti event
  // in Thread::start.
  {
    // Ensure that the C++ Thread and OSThread structures aren't freed before
    // we operate.
    MutexLocker mu(Threads_lock);
​
    // Since JDK 5 the java.lang.Thread threadStatus is used to prevent
    // re-starting an already started thread, so we should usually find
    // that the JavaThread is null. However for a JNI attached thread
    // there is a small window between the Thread object being created
    // (with its JavaThread set) and the update to its threadStatus, so we
    // have to check for this
    if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {
      throw_illegal_thread_state = true;
    } else {
      // We could also check the stillborn flag to see if this thread was already stopped, but
      // for historical reasons we let the thread detect that itself when it starts running
​
      jlong size =
             java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
      // Allocate the C++ Thread structure and create the native thread.  The
      // stack size retrieved from java is signed, but the constructor takes
      // size_t (an unsigned type), so avoid passing negative values which would
      // result in really large stacks.
      size_t sz = size > 0 ? (size_t) size : 0;
        
        
      // 可以清楚看到在这个jvm.cpp里 new了一个JavaThread对象,而这个对象就是和我们java中的
      // 线程一一对应。因此 我们也可以理解我们java中的线程创建方式就是从我们jvm中new Thread创建开始的。像我们的sleep方法就在这个类中  
      native_thread = new JavaThread(&thread_entry, sz);
        
​
      // At this point it may be possible that no osthread was created for the
      // JavaThread due to lack of memory. Check for this situation and throw
      // an exception if necessary. Eventually we may want to change this so
      // that we only grab the lock if the thread was created successfully -
      // then we can also do this check and throw the exception in the
      // JavaThread constructor.
      if (native_thread->osthread() != NULL) {
        // Note: the current thread is not being used within "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);

在这里再唠叨一次,jdk包含三部分内容

  1. Java中类库,比如String、Io类

  2. C中的类库,比如本地native方法

  3. hotspot虚拟机

虽然你在jdk中没有看到hostpot虚拟机,但是你可以看到叫java.exe的应用程序,而这个java.exe的应用程序其实就是我们hostpot项目编译完成得到的。

那操作系统是如何去创建一个线程的,我们在次找到源码中的位置

D:\ChromeDownloads\openjdk8-master\openjdk\hotspot\src\os\linux\vm 下的os_linux.cpp文件中

 int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread);

从上面我们至少可以猜想一个结论

JAVA中Thread和我们操作系统中线程是1----->1的关系

假设有了上面知识的铺垫,那么可以试想一下java的线程模型到底是什么情况呢?

在java代码里启动一个线程的代码

public class Example4Start {
​
    public static void main(String[] args) {
        Thread thread = new Thread(){
            @Override
            public void run() {
                System.out.println("i am new Thread!");
            }
        }; 
        thread.start();
    }
}

这里启动的线程和上面我们通过linux的pthread_create函数启动的线程有什么关系呢?

只能去可以查看start()的源码了,看看java的start()到底干了什么事才能对比出来.

start源码的部分截图,可以看到这个方法最核心的就是调用了一个start0方法,而start0方法又是一个native方法,故而如果要搞明白start0我们需要查看Hotspot的源码,好吧那我们就来看一下Hotspot的源码吧,Hotspot的源码怎么看么?一般直接看openjdk的源码,在没有openjdk的情况下,我们做一个大胆的猜测,java级别的线程其实就是操作系统级别的线程,什么意思呢?说白了我们大胆验证 start-start0-ptherad_create

我们鉴于这个猜想来模拟实现一下。

 

如何来模拟实现呢?

public class LubanThread {
    public static void main(String[] args) {
     HzkThread hzkThread =new HzkThread();
        hzkThread.start0();
    }
    private native void start0();
}

这里我们让自己写的start0调用一个本地方法,在本地方法里面去启动一个系统线程,好吧我们写一个c程序来启动本地线程

#include <pthread.h>
#include <stdio.h>
pthread_t pid;
void* thread_entity(void* arg)
{   
    while(1){
		usleep(100);
    	printf("I am new Thread\n");
    }
}
int main()
{
    pthread_create(&pid,NULL,thread_entity,NULL);
    while(1){
		usleep(100);
		printf("I am  main\n");
	}
}

在linux上编译、运行上述C程序

gcc thread.c -o thread.out -pthread

./thread.out

结果是两个线程一直在交替执行,得到我们预期的结果。现在的问题就是我们如何通过start0调用这个c程序,这里就要用到JNI了,

好吧你要是实在不懂,再说一遍

java代码如下:

/**
 * Created by 撸码的小孩 on 2022/1/10
 * time  15:55
 */
public class HzkThread {

    static {
        System.loadLibrary( "HzkThreadNative" );
    }
    private native void start0();


    public static void main(String[] args) {
        HzkThread hzkThread = new HzkThread();
        hzkThread.start0();



    }


}

装载库,保证JVM在启动的时候就会装载,故而一般是也给static

System.loadLibrary( "LubanThreadNative" );

编译成class文件

生成.h头文件

javah packageName.className

需要注意的运行javah命令得在包外面和编译不一样,编译运行javac得在包当中

javah HzkThread

继而开始编写C程序,要上面那个thread.c程序上做一点修改,这里我复制一份出来修改,修改的时候需要参考那个这个.h文件,一下是.h文件的内容

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HzkThread */

#ifndef _Included_HzkThread
#define _Included_HzkThread
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HzkThread
 * Method:    start0
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HzkThread_start0
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

上面代码中的Java_HzkThread_start0方法就是你需要在C程序中定义的方法。

首先复制一份thread.c

#include <pthread.h>//头文件
#include <stdio.h>
#include "HzkThread.h" //记得导入刚刚编译的那个.h文件
pthread_t pid; //定义一个变量,接受创建线程后的线程id

void*  thread_entity(void* args){

	while(1){

    usleep(500);		
	printf("i am new Thread!\n");
	}
   
}


//这个方法要参考.h文件的代码,这里的参数得注意,你写死就行,不用明白为什么
JNIEXPORT void JNICALL Java_HzkThread_start0 (JNIEnv *env, jobject c1)
{
     //调用操作系统的函数创建线程,注意四个参数
    pthread_create(&pid,NULL,thread_entity,NULL);
   
    while(1){
        usleep(500);
        printf("main\n");
    }
    
  
   
}

解析类,把这个thread.c编译成为一个动态链接库,这样在java代码里会被laod到内存

libHzkThreadNative这个命名需要注意libxx,xx就等于你java那边写的字符串

gcc  -fPIC -I /usr/lib/jvm/java-1.8.0-openjdk/include -I /usr/lib/jvm/java-1.8.0-openjdk/include/linux  -shared -o libHzkThreadNative.so thread.c

做完这一系列事情之后需要把这个.so文件加入到path,这样java才能load到

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:{libLubanThreadNative.so}所在的路径

回车

 

牛逼!我们已经通过自己写的一个类,启动了一个线程,但是这个线程函数体是不是java的是C程序的,这个java线程的run方法不同。接下来我们来实现一下这个run

首先在java的代码里面提供一个run方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值