用了这么久的线程,都不知道底层到底怎么实现的,觉得有必要花时间研究一下。
c语言线程例子
这是一段c代码,mutexLock使两个线程交替执行。
#include<stdio.h>
#include<pthread.h>
void* run(void* args){
while(1){
usleep(1000);
printf("sub\n");
}
}
int main(){
pthread_t tid;
pthread_create(&tid,NULL,run,NULL);
while(1){
usleep(1000);
printf("main\n");
}
return 0;
}
gcc -o thread.out pthread.c -lpthread
在centos上用上面的指令编译生成thread.out文件
gcc的安装请看gcc安装
-o 是输出,不写的话会默认命名为a.out
thread.out是输出的文件名
pthread.c是上面的c代码
-lpthread加上这个 解决undefined reference to pthread_create bug
./thread.out 执行这个c代码
结果正是两个线程交替执行。
操作系统层面的线程创建
使用命令man pthread_create查看pthread_create函数相关注释
#include <pthread.h>
int pthread_create(pthread_t *thread,//新创建的线程ID指向的内存单元
const pthread_attr_t *attr, //pthread_attr_t 结构体,attr用于确定线程属性
void *(*start_routine) (void *), //start_routine函数指针
void *arg);默认为NULL。若上面函数需要参数,将参数放入结构中并将地址作为arg传入
Compile and link with -pthread. //编译和链接时需要加 -pthread
DESCRIPTION
//pthread_create函数的功能是在调用进程中启动一个新线程
The pthread_create() function starts a new thread in the calling process.
//新线程通过调用 start_routine() 开始执行;
The new thread starts execution by invoking start_routine();
//arg 作为 start_routine() 的唯一参数传递。
arg is passed as the sole argument of start_routine().
//这个线程以下列几种方式终止
The new thread terminates in one of the following ways:
//调用 pthread_exit(3) 函数来指定一个退出状态值,
1: It calls pthread_exit(3), specifying an exit status value
//可以在同一进程的其他线程中调用 pthread_join(3) 函数来获得这个状态值。
that is available to another thread in the same process that calls pthread_join(3).
// 从 start_routine() 函数中返回:
2: It returns from start_routine().
//这相当于调用 pthread_exit(3),并且返回值由 return 声明提供。
This is equivalent to calling pthread_exit(3) with the value supplied in the return statement.
//它被取消(参见 pthread_cancel(3))。
3: It is canceled (see pthread_cancel(3)).
//进程中的任何线程调用了 exit(3)
4: Any of the threads in the process calls exit(3),
//或者主线程的 main() 函数中执行了 return
or the main thread performs a return from main().
//这会导致进程中所有线程都终止
This causes the termination of all threads in the process.
//attr是第二个参数
//attr参数是pthread_attr_t结构体
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;
// 使用 pthread_attr_init(3) 和相关函数初始化此结构
this structure is initialized using pthread_attr_init(3) and related functions.
//如果 attr 为 NULL,则使用默认属性创建线程
If attr is NULL, then the thread is created with default attributes.
//在返回之前,成功调用 pthread_create() 会
Before returning, a successful call to pthread_create()
//将新线程的 ID 存储在线程指向的缓冲区中
stores the ID of the new thread in the buffer pointed to by thread;
//这个ID用于在后续调用其他线程函数时引用该线程
this identifier is used to refer to the thread in subsequent calls to other pthreads functions.
RETURN VALUE
//成功时,pthread_create() 返回 0
On success, pthread_create() returns 0;
// 如果出错,则返回错误编号,并且 thread 为野指针
on error, it returns an error number, and the contents of *thread are undefined.
java调用c
public class JniThread {
static{
System.loadLibrary("JniThread");
}
public static void main(String[] args) {
JniThread t = new JniThread();
t.start();
}
private native void start();
}
把这个JniThread.java文件(去掉package com.lry.basic.juc)用xshell和xftp上传到centos /usr/local/c 目录下
然后javac JniThread.java生成JniThread.class文件
再javah JniThread生成JniThread.h文件,这个很重要,因为c的代码要根据这个文件来写
JniThread.h 如下
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class JniThread */
#ifndef _Included_JniThread
#define _Included_JniThread
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: JniThread
* Method: start
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_JniThread_start
(JNIEnv *, jobject);//这里很重要
#ifdef __cplusplus
}
#endif
#endif
根据JniThread.h编写c代码 如下
#include<stdio.h>
#include<pthread.h>
#include "JniThread.h"//记得引入
void* run(void* args){
while(1){
usleep(1000);
printf("sub\n");
}
}
//从.h文件复制过来
JNIEXPORT void JNICALL Java_JniThread_start(JNIEnv *env, jobject c1){
pthread_t tid;
pthread_create(&tid,NULL,run,NULL);
while(1){
usleep(1000);
printf("main\n");
}
}
c编写好了,用xftp传到 /usr/local/c 下,我的.java,.class , .h和c文件全在这个路径下。
然后把c语言编译成so库文件,命令如下
gcc -fPIC -I/usr/local/java/jdk1.8.0_152/include/ -I/usr/local/java/jdk1.8.0_152/include/linux -shared -o libJniThread.so start.c
稍微说一下这个命令
-fPIC 是位置无关码,这个必须要
usr/local/java/jdk1.8.0_152这个是我的jdk路径
-I/usr/local/java/jdk1.8.0_152/include/为了引入jni.h
-I/usr/local/java/jdk1.8.0_152/include/linux 为了引入jni_md.h
-o 是输出
start.c自然是要编译的c文件
libJniThread.so这个命名必须libXX,XX是java代码的System.loadLibrary(“JniThread”);里面的字符串
so库生成好了,我们要把这个文件加入到java文件的path,这样java才能load到该文件
命令:export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/c/
然后 java JniThread
结果是两个线程交替执行(crtl+c 退出执行)
c回调java
public class JniThread {
static{
System.loadLibrary("JniThread");
}
public static void main(String[] args) {
JniThread t = new JniThread();
t.start();
}
//这个方法被c回调
public void run(){
System.out.println("im java thread");
}
//调用c
private native void start();
}
c代码
#include<stdio.h>
#include<pthread.h>
#include "JniThread.h"
JNIEXPORT void JNICALL Java_JniThread_start(JNIEnv *env, jobject c1){
jclass cls;
jobject obj;
jmethodID cid;
jmethodID rid;
jint ret = 0;
cls = (*env)->FindClass(env,"JniThread");
if(cls==NULL){
printf("find class error");
return;
}
//构造器
cid = (*env)->GetMethodID(env,cls,"<init>","()V");
if(cid==NULL){
printf("find constructor error");
return;
}
//new 对象
obj = (*env)->NewObject(env,cls,cid);
if(obj==NULL){
printf("new obj error");
return;
}
//run方法
rid = (*env)->GetMethodID(env,cls,"run","()V");
ret = (*env)->CallIntMethod(env,obj,rid,NULL);
printf("finish");
}
int main(){
return 0;
}
执行
1:gcc -o JniThread start.c -I /usr/local/java/jdk1.8.0_152/include -I/usr/local/java/jdk1.8.0_152/include/linux -L /usr/local/java/jdk1.8.0_152/jre/lib/amd64/server -ljvm -pthread(这个命令似乎不需要)
2:gcc -fPIC -I/usr/local/java/jdk1.8.0_152/include/ -I/usr/local/java/jdk1.8.0_152/include/linux -shared -o libJniThread.so start.c
3: javac JniThread.java
4:java JniThread
结果打印im java thread
线程调用链(个人理解)
java.start ->start0(native)->pthread_create(&tid,NULL,java_thread,NULL)
void java_thread(){
JNI反向调用java的run方法
}