java并发笔记之java线程模型
java并发笔记之java线程模型
java当中的线程和操作系统的线程是什么关系?
猜想: java thread —-对应-—> OS thread
Linux关于操作系统的线程控制源码:pthread_create()
Linux命令:man pthread_create
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, 这个函数有四个参数:
然后我们来在linux上启动一个线程的代码:
创建一个后缀名.c的文件:
复制代码
//引入头文件
include
include
//定义一个变量,接受创建线程后的线程id
pthread_t pid;
//定义子线程的主体函数
void thread_entity(void arg)
{
while (1) { usleep(100); printf("i am new Thread!\n"); }
}
//main方法,程序入口,main和java的main一样会产生一个进程,继而产生一个main线程
int main()
{
//调用操作系统的函数创建线程,注意四个参数 pthread_create(&pid,NULL,thread_entity,NULL); //usleep是睡眠的意思,那么这里的睡眠是让谁睡眠呢?为什么需要睡眠?如果不睡眠会出现什么情况 //让主线程睡眠,目的是为了让子线程执行 while (1) { usleep(100); printf("main\n"); }
}
复制代码
运行命令:
复制代码
gcc -o thread.out thread.c -pthread
Thread.out 是thread.c 编译成功之后的文件
运行:./thread.out
输出:
i am new Thread!
main
i am new Thread!
main
i am new Thread!
main
i am new Thread!
main
。。。。。。
//一直交替执行
复制代码
经过以上分析Linux线程创建的过程
可以试想一下java 的线程模型到底是什么情况?
分析: java代码里启动一个线程的代码:
复制代码
import java.lang.Thread;
public class ThreadTest {
public static void main(String[] args) {
Thread thread = new Thread(){ @Override public void run() { System.out.println("i am new Thread!\n”) } }; thread.start(); }
}
复制代码
这里启动的线程(start() 方法)和上面我们通过linux的pthread_create()函数启动的线程有什么关系呢?
只能去可以查看start()的源码了,看看java的start()到底干了什么事才能对比出来。
复制代码
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 */ } } } //start0方法是一个native方法 //native方法:就是一个java调用非java代码的接口,该接口方法的实现由非java语言实现,比如C语言。 private native void start0();
复制代码
根据Start()源码可以看到这个方法最核心的就是调用了一个start0方法,而start0方法又是一个native方法,故而如果要搞明白start0我们需要查看Hotspot的源码。
好吧那我们就来看一下Hotspot的源码吧,Hotspot的源码怎么看么??一般直接看openjdk的源码,openjdk的源码如何查看、编译调试?
Mac 10.14.4 编译openjdk1.9源码 及集成clion动态调试 : https://app.yinxiang.com/fx/b20706bb-ae55-4ec5-a17b-79930e7e67ea
我们做一个大胆的猜测,java级别的线程其实就是操作系统级别的线程,什么意思呢?
说白了我们大胆猜想 start()—>start0()—>ptherad_create()
我们鉴于这个猜想来模拟实现一下:
一:自己写一个start0()方法来调用一个 native 方法,在native方法中启动一个系统线程
//java 代码
复制代码
public class TestThread {
public static void main(String[] args) {
TestThread testThread = new TestThread(); testThread.start0();
}
//native方法 private native void start0();
}
复制代码
二:然后我们来写一个c程序来启动本地线程:
复制代码
include
include
//定义一个变量,接受创建线程后的线程id
pthread_t pid;
//定义子线程的主体函数
void thread_entity(void arg)
{
while (1)
{
usleep(100);
printf("i am new Thread!n");
}
}
//main方法,程序入口,main和java的main一样会产生一个进程,继而产生一个main线程
int main()
{
//调用操作系统的函数创建线程,注意四个参数
pthread_create(&pid,NULL,thread_entity,NULL);
//usleep是睡眠的意思,那么这里的睡眠是让谁睡眠呢?为什么需要睡眠?如果不睡眠会出现什么情况
//让主线程睡眠,目的是为了让子线程执行
while (1)
{
usleep(100);
printf("mainn");
}
}
复制代码
三:在Linux上编译运行C程序:
复制代码
编译: gcc -o thread.out thread.c -pthread
运行: ./thread.out
就会出现线程交替执行:
main
i am new Thread!
main
i am new Thread!
main
i am new Thread!
main
。。。。。。
复制代码
现在的问题就是我们如何通过start0()调用这个c程序,这里就要用到JNI了(JNI自行扫盲)
Java代码如下:
复制代码
public class TestThread {
static { //装载库,保证JVM在启动的时候就会装载,故而一般是也给static System.loadLibrary("TestThread"); } public static void main(String[] args) { TestThread testThread = new TestThread(); testThread.start0();
}
private native void start0();
}
复制代码
在Linux下编译成clas文件:
编译: javac java1.java
生成class文件:java1.class
在生成 .h 头文件:
编译: javah TestThread
生成class文件:TestThread.h
复制代码
.h文件分析
include
/ Header for class TestThread /
ifndef _Included_TestThread
define _Included_TestThread
ifdef __cplusplus
extern "C" {
endif
/*
- Class: TestThread
- Method: start0
- Signature: ()V
*/
//15行代码, Java_com_luban_concurrency_LubanThread_start0方法就是你需要在C程序中定义的方法
JNIEXPORT void JNICALL Java_TestThread_start0(JNIEnv *, jobject);
#ifdef __cplusplus
}
endif
endif
复制代码
然后继续修改.c程序,修改的时候参考.h文件,复制一份.c文件,取名threadNew.c 定义一个Java_com_luban_concurrency_LubanThread_start0 方法在方法中启动一个子线程:
复制代码
include
include
//记得导入刚刚编译的那个.h文件
include "TestThread.h"
//定义一个变量,接受创建线程后的线程id
pthread_t pid;
//定义子线程的主体函数
void thread_entity(void arg)
{
while (1) { usleep(100); printf("i am new Thread!\n"); }
}
//这个方法要参考.h文件的15行代码
JNIEXPOR void JNICALL Java_com_luban_concurrency_LubanThread_start0(JNIEnv *env, jobject c1){
pthread_create(&pid,NULL,thread_entity,NULL);
while(1)
{
usleep(100);
printf("I am Java_com_luban_concurrency_LubanThread_start0 n");
}
}
复制代码
解析类,把这个threadNew.c编译成为一个动态链接库,这样在java代码里会被laod到内存libTestThreadNative这个命名需要注意libxx,xx就等于你java那边写的字符串
gcc ‐fPIC ‐I ${JAVA_HOME}/include ‐I ${JAVA_HOME}/include/linux ‐shared ‐o libTestThreadNative.so threadNew.c
//需要把这个.so文件加入到path,这样java才能load到:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:{libLubanThreadNative.so}
直接测试,运行我们自己写的那个java类直接测试看看结果能不能启动线程:
运行:java java1
现象:
main
I am Java_com_luban_concurrency_LubanThread_start0
main
I am Java_com_luban_concurrency_LubanThread_start0
main
I am Java_com_luban_concurrency_LubanThread_start0
main
。。。。。。
我们已经通过自己写的一个类,启动了一个线程,但是这个线程函数体是不是java的是C程序的,这个java线程的run方法不同。接下来我们来实现一下这个run:(C来调用java的方法,是jni反调用java方法)
java的代码里面提供一个run方法:
复制代码
public class TestThread {
static {
//装载库,保证JVM在启动的时候就会装载,故而一般是也给static
System.loadLibrary("TestThread");
}
public static void main(String[] args) {
TestThread testThread = new TestThread();
testThread.start0();
}
//这个run方法,要让C程序员调用到,就完美了
public void run(){
System.out.println("I am java Thread !!");
}
private native void start0();
}
复制代码
C程序:
复制代码
include
include
//记得导入刚刚编译的那个.h文件
include "TestThread.h"
//定义一个变量,接受创建线程后的线程id
pthread_t pid;
//定义子线程的主体函数
void thread_entity(void arg)
{
run();
}
//JNIEnv *env 相当于虚拟机
JNIEXPOR void JNICALL Java_com_luban_concurrency_LubanThread_start0(JNIEnv *env, jobject c1){
//定一个class 对象
jclass cls;
jmethodID cid;
jmethodID rid;
//定一个对象
jobject obj;
jint ret = 0;
//通过虚拟机对象找到TestThread java class
cls = (*env)->FindClass(env,"TestThread");
if(cls == NULL){
printf("FindClass Error!n")
return;
}
cid = (*env)->GetMethodID(env, cls, "", "()V");
if (cid == NULL) {
printf("GetMethodID Error!n");
return;
}
//实例化一个对象
obj = (*env)->NewObject(env, cls, cid);
if(obj == NULL){
printf("NewObject Error!n")
return;
}
rid = (*env)->GetMethodID(env, cls, "run", "()V");
ret = (*env)->CallIntMethod(env, obj, rid, Null);
printf("Finsh call method!n")
}
int main(){
return 0;
}
复制代码
复制代码
然后再走一遍生成.class、.h 、so 然后执行
jni反调用java编译:
gcc -o threadNew threadNew.c -I /usr/lib/jvm/java-1.8.0-openjdk/include -I /usr/lib/jvm/java-1.8.0-openjdk/include/linux -L /usr/lib/jvm/java-1.8.0- openjdk/jre/lib/amd64/server -ljvm -pthread
指定:export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:{libLubanThreadNative.so}
显示:
I am java Thread !!
Finsh call method!
复制代码
至此c调用java的已经完成(只是模拟)(其实C调用java的时候并不是调用jni反射调用的,而是用的C++的一个函数)
由上可知java thread的调用及反调用:
调用了一个start0方法,而start0方法又是一个native方法,native方法是由Hotspot提供的,并且调用OS pthread_create()
证实: java thread —-对应-—> OS thread
原文地址https://www.cnblogs.com/yuhangwang/p/11256476.html
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
C++ 过滤出字符串的中文(GBK,UTF-8)
C++ 过滤出字符串的中文(GBK,UTF-8)最近在处理游戏敏感词之类的东西,为了加强屏蔽处理,所以需要过滤掉字符串中的除汉字之外的是其他东西如数字,符号,英文字母等。 首先我查阅资料并写了个函数: 示例:返回输入字符串中汉字的个数: 复制代码std::string StrWithOutSymbol(const std::string &source){ string sourceWithOutSymbol; int i = 0; while (source[i] != 0) { if (source[i] & 0x80 ) { sourceWithOutSymbol += source[i]; sourceWithOutSymbol += source[i + 1]; i += 2; else { i ++; } } return sourceWithOutSymbol; } 复制代码这个函数的原理是ord($str)&0x80来判断汉字 80对应的二进制代码为1000 0000,最高位为一,代表汉字汉字编码格式通称为10格式一个汉字占2字节,但只代表一个字...
- 下一篇
jquery 操作HTML data全局属性缓存的坑
jquery 操作HTML data全局属性缓存的坑data-* 全局属性 是一类被称为自定义数据属性的属性,它赋予我们在所有 HTML 元素上嵌入自定义数据属性的能力,并可以通过脚本(一般指JavaScript) 与 HTML 之间进行专有数据的交换。 简单的说就是html元素可以通过data-*属性存一些数据,类似于一个map,如果我们想在html的元素上额外的存一些东西是非常方便的。 一:读取是没问题的比如我们读取div中data-num的数据: dataSet 分别用jquery和js的方法读取,结果都是1。 二:修改就有坑了但是修改的data-num的时候就有意思了: jquery设置data-num中的值为2jquery读取值的值是2js读取值的值是1,奇怪,查看下html元素 data-num的值还是1。。。 这个坑,坑了我一早上,后来百度下才知道,原来jquery设置的值是在缓存里。。。果断查看下jquery的源码,真相都在代码里: 三:正确的使用方式如果需要修改dom元素上的data必须用js的方式:document.getElementById("div1").da...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7,CentOS8安装Elasticsearch6.8.6
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- Linux系统CentOS6、CentOS7手动修改IP地址
- CentOS7安装Docker,走上虚拟化容器引擎之路
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- SpringBoot2全家桶,快速入门学习开发网站教程