多线程-从os层面理解常见概念

### 如何创建一个线程

  1. 在Linux系统中有一个方法,他有四个参数,其中第一个参数是利用指针传入,后期如果被修改也会同步修改,第三个参数和自己定义的run方法有关,后面会详细说。
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, 
void *(*start_routine) (void *), void *arg); 

1682414016166

2.对比linux上和java启动一个线程的区别,首先是linux,其次是Java,linux就不必说了直接调用pthread_create,主要看Java(这里涉及到jni,下面有解释),thread.start->native start0();这里主要是Java通过jni调用c的方法并将其转化成c能理解的方式,然后然后传给pthread_create的回调函数中,在这个回调函数中,jni调用Java的run,线程就会执行我们的代码。

//头文件
#include <pthread.h>
#include <stdio.h>
//定义一个变量,接受创建线程后的线程id
pthread_t pid;
//定义线程的主体函数
假设有了上面知识的铺垫,那么可以试想一下java的线程模型到底是什么情况呢?
在java代码里启动一个线程的代码
这里启动的线程和上面我们通过linux的pthread_create函数启动的线程有什么关系呢?只能去可以查
看start()的源码了,看看java的start()到底干了什么事才能对比出来。start方法的源码的部分截图
void* thread_entity(void* arg) {
printf("i am new Thread! from c");
}
//main方法,程序入口,main和java的main一样会产生一个进程,继而产生一个main线程
int main() {
//调用操作系统的函数创建线程,注意四个参数
pthread_create(&pid,NULL,thread_entity,NULL);
//usleep是睡眠的意思,那么这里的睡眠是让谁睡眠呢?
//为什么需要睡眠?如果不睡眠会出现什么情况
usleep(100);
printf("main\n");
return 0;
}
public class Example4Start {
public static void main(String[] args) {
Thread thread = new Thread(){
@Override
public void run() {
System.out.println("i am new Thread! from java ");
}
};
thread.start();
}
}
JNI(Java Native Interface)是Java语言中的一种机制,它允许Java与其他非Java程序进行交互。JNI为Java应用程序提供了本地方法调用机制,这意味着Java程序可以调用由非Java编写的本地库中的函数。

在使用JNI时,需要使用Java编程语言和C/C++编程语言进行配合,并按照特定的格式定义本地方法。然后使用Java的工具将Java源代码编译成字节码,并使用C/C++编译器将本地方法编译为共享库,最后在Java程序中加载并调用该共享库。

JNI被广泛应用于跨平台开发中,例如在Android开发中使用JNI可以调用本地特定平台的API,或者将Java应用程序与C/C++模块混合使用等等。
jni: 在Java中,`Runnable`接口(或继承`Thread`类)的`run`方法就是一段我们想要让线程执行的代码。当Java线程启动后,运行的就是其`run`方法。

而在C/C++中,在调用`pthread_create`函数时需要传递一个回调函数,该回调函数会在新线程中运行。从Java的角度看,我们可以将实现了`Runnable`接口的Java对象作为参数传入C/C++的回调函数中,在回调函数中再调用该Java对象中的`run`方法。

具体地说,我们可以实现一个Java类,在其中添加`run`方法,该方法用于存放我们要在线程中运行的代码。然后创建一个实例化对象,并将该对象作为参数传递给`pthread_create`函数所调用的C/C++回调函数中。在该回调函数中,我们可以通过JNI调用Java对象的`run`方法,从而在新线程中执行我们期望运行的代码。

总之,Java中的`run`方法和C/C++中`pthread_create`函数的回调参数之间不存在直接的映射关系,我们需要通过JNI进行函数调用,将Java对象转换成C/C++能够理解的形式,并手动调用其中的特定方法(如`run`方法)。

Java的线程模型:os层面分用户线程,内核线程

1.用户线程是运行内核线程上的,至于它们之间的对应关系有四种,1:1,1:n,n:n,n:1,至于具体什么样得具体分析,jvm是1:1,优点就是线程模型简单,缺点就是自己写东西容易引进用户态内核态频繁切换,或者弄出来大量线程,很影响系统性能;

2.如何确定什么时候是内核态什么时候是用户态

首先进程是运行在内存中的,但是内存有限大,需要运行在虚拟地址上,实际执行在运行在物理地址上就是通过mmu(具体解释看下main)转换的;然后cpu有四种不同的执行级别,0是内核态,3是用户态,另外俩不知道是啥,当你调用系统的方法就会进入内核态,比如park,sleep,mutex(具体原因看下面);cas不会引起切换,因为他只是一条cpu指令很快

3.至于cpu上下文切换

cpu上下文是cpu计数器和cpu寄存器,cpu上下文切换就是cpu加载这些任务,然后通过计数器在这些任务里切换(专业术语看下面),这里面涉及到进程之间,线程之间,

CPU使用虚拟地址向内存寻址,通过专用的内存管理单元(MMU)硬件把虚拟地址转换为真实的物理
地址
在Java中,`park`和`await`方法都可以使当前线程挂起(暂停执行),并等待其他线程的通知或者特定条件的满足。而`sleep`方法则是让当前线程进入休眠状态,在一段时间后再唤醒线程。

在Java的实现中,`park`、`await`和`sleep`方法都会涉及到操作系统的相应操作,因此它们内部的实现代码必然会有切换到内核态的操作。具体来说,当调用这些方法时,JVM会使用底层操作系统的原语来进行同步和线程调度,这些原语往往需要在内核态下执行。

而对于`Mutex`,它是一种互斥锁机制,用于控制多个线程对共享资源的访问,防止出现资源竞争和数据不一致等问题。在Java中,`Mutex`可以由`synchronized`关键字实现,也可以通过显式创建`ReentrantLock`对象来实现。

当使用`synchronized`关键字时,其内部实现会直接利用Java虚拟机的监视器锁机制,避免了上下文切换和用户态到内核态的转换。当使用`ReentrantLock`对象时,其内部实现则采用了基于CAS操作的自旋锁机制,也能够在多线程环境下保证共享资源的安全访问,但是使用自旋锁机制会导致CPU持续消耗大量的资源。

因此,在Java中执行`park`、`await`、`sleep`和`Mutex`的不同形式都可能涉及到操作系统的原语,从而需要在内核态下进行相应操作。
什么是 CPU 上下文切换
就是先把前一个任务的 CPU 上下文(也就是 CPU 寄存器和程序计数器)保存起来,然后加载新任务的
上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。
而这些保存下来的上下文,会存储在系统内核中,并在任务重新调度执行时再次加载进来。这样就能保
证任务原来的状态不受影响,让任务看起来还是连续运行

如何确定一个锁是什么锁

1.在os中的一种同步机制为了保护临界资源,常见的是mutex 和 semaphore

2.在jvm的mark word中会标记当前的锁,看下面的几个例子看如何判断锁

01偏向锁或者无锁

00 轻量锁

10重量锁(看到有的人写11是重量锁,默认他写错了)

11gc

第一个可能是无锁,或者是偏向锁,前提我没有打印hash,看第一个8位二进制的后俩位 01,有的资料说再往前一位是记录 当前锁是否可以是偏向锁的标志位比如101 ,没模出来

1682416446511

第二个重量锁

1682416637196

第三个轻量锁

1682416666861

3.sychronize 是什么锁

他三种锁都可能是,这个下一次看源码说

posted @ 2023-04-25 18:00  小傻孩丶儿  阅读(29)  评论(4编辑  收藏  举报