当前位置: 首页 > Linux

Java并发编程浅析-线程机制你真的了解多少?

时间:2023-04-06 20:37:09 Linux

天边,浩瀚之美,喧嚣之美;明心明性,善始善终,唯善善道!——赵进《朝槿兮年说》写在开头众所周知,在计算机操作系统中,进程(Process)是一个非常关键的概念,最本质的理解就是操作系统所执行的应用程序(ApplicationProgram)系统。与每个进程相关联的是地址空间(AddressSpace)。其中,描述是从某个最小值(通常为0)的存储位置到最大值的存储位置的列表。在这个地址空间中,进程可以进行读写操作。地址空间可以存放可执行程序,以及程序所需的数据和栈针。与每个进程关联的资源集合。通常包括寄存器(Registers)、打开文件列表、系统突发报警、相关进程列表等执行程序信息。其中,寄存器主要包括程序计数器(ProgramCounter)和堆栈指针(StackPointer)。在一定程度上,我们可以把进程看成是一个容器(Container),里面保存着一个程序运行的所有信息。操作系统可以用进程来描述程序的执行过程。进程拥有程序的所有数据(包括一些I/O分配、内存分配等),是程序的载体,所以进程有一个特点,资源分配的单位很重要。该进程的另一个特点是调度执行和交替执行以提高资源利用率。操作系统管理过程(创建、切换过程、分配和回收等)是非常昂贵的。比如创建进程时,需要创建PCB,分配内存的独立内部空间,建立映射表,创建资源,切换进程时需要切换资源,比如切换对应的内存映射表,需要在进程退出时释放资源。由此不难得出结论,每个进程都有一个地址空间(AddressSpace)和一个控制线程(ControlThread)。但是,为什么操作系统有进程的时候会出现线程呢?主要是虽然进程仍然是资源分配的单位,但是调度执行交给了线程,因为线程在进程内部,线程之间的切换不需要切换资源,不需要切换线程映射表,只需要简单的在进程中切换PC指针,内部保存一些寄存器即可,比较轻量(不同进程之间的线程切换无法避免)。基本概述线程(Thread)既保留了并发执行的优点,又避免了进程切换的代价。假设现在有一个web服务器,此时还没有线程的概念,服务器程序使用多进程,比如用一个进程监听客户端的请求,当客户端连接上时,它会派发(复制一个子-process)一个进程给用户(每个进程都有独立的资源)来监听和处理用户发送的数据(即多进程程序)。这时候想象一下,这多个进程来回切换,每次切换都需要切换资源,是不是很耗资源?这时引入线程后,web服务器的程序就是一个进程,进程就是用来承载程序的资源的。首先,进程中的一个线程用于监听请求,每次客户端连接时,分配一个线程给用户(多线程程序),此时处理器只需要在这些线程之间切换,以及线程切换不需要切换资源(进程中资源级切换,线程是指令级切换),那么多个线程只需要共享进程的资源就足够了,其运行速度和执行效率也有了得到改善。可见,操作系统引入线程后,调度和分派都是在线程上进行的,但是有些活动会影响到进程中的所有线程,所以这些活动必须在进程级别进行管理。比如挂起操作会挂起所有线程,因为所有线程共享进程的用户地址空间。线程的引入最重要的是以下两个方面:线程的创建、终止和调度。轻线程之间的通信不进入内核,不需要从用户态转换到内核态。但是,它同时也增加了程序的复杂度。发展困难。如果开发者对线程机制的把握和理解不够准确,也会陷入技术的迷茫。线程模型所有线程共享进程的状态和资源,因此线程都驻留在同一个地址空间,可以访问同一个数据。线程和非线程的区别主要体现在两个关键点:用户栈和内核栈:用户栈用于保存用户进程的子程序调用的参数、返回值、局部变量等信息(保存普通方法)的栈)内核栈是系统调用时程序在内核态调用方法时的栈;用户地址空间是存放进程的程序和数据的空间,线程没有自己的用户地址空间。一般来说,用户栈和内核栈在线程中已经是唯一的,这也证明了线程已经成为任务调度的基本单位。这些线程都共享进程持有的资源。线程控制块存储寄存器值、优先级、线程状态和其他信息。在操作系统层面,线程也有“生老病死”,专业术语称之为生命周期。虽然不同的开发语言对操作系统线程的封装不同,但是线程的生命周期基本是一样的。每个线程基本都有以下特点:和进程类似,线程也有执行状态(生命周期),因为线程也是一个执行进程线程的上下文,当执行时需要保护现场执行栈线程切换,保存系统调用时的一些参数和参数。少量中间结果,线程私有的局部变量存储空间,不再与进程中的其他线程共享大量存储空间。内存和资源访问线程控制块TCB,里面存放上下文切换信息,从PCB可以看出,对于有生命周期的东西,要学习掌握它,思路很简单,只要能看懂状态即可生命周期中各个节点的转换机制。线程分类线程分为用户级线程(User-LevelThread,ULT)和内核级线程(Kernel-LevelThread,KLT)。内核级线程也称为轻量级进程(Light-WeightProcess,LWP)。用户级线程(User-LevelThread,ULT)在纯ULT软件中,所有管理线程的工作都由应用程序完成。内核不知道线程的存在。线程完全由线程库提供,线程的创建、销毁、调度、线程间的消息传递等,以及上下文的保存都由它控制。如果可能的话,我们也可以实现自己的线程库,只要合理组织线程即可。但是,用户级线程的所有活动都发生在用户空间和一个进程中,系统无法感知用户级线程的存在,所以系统仍然以进程的形式对其进行调度。当线程1被系统调用等阻塞时,系统会认为进程被阻塞,操作系统会为其他进程分配CPU时间片。在此期间,根据线程库维护的数据结构,任意一个线程1虽然处于运行状态,但是从处理器执行的角度来看,线程2不处于运行状态,无法为其分配时间片。这也导致一旦用户级线程被阻塞,它会阻塞进程中的所有线程,阻止其他线程运行。使用用户级线程(ULT)有以下特点:优点:所有线程管理都在进程的用户空间,线程切换不需要内核态权限,不需要系统调用,从而节省了用户的开销-modetokernel-modeconversion线程的调度更加灵活,可以为每个不同的应用量身定制更合适的调度算法,因为这些调度算法可以自己实现,不需要改变操作系统的底层调度器ULT就可以运行在任何操作系统下,即使是不支持线程的操作系统也可以实现。线程库是所有应用程序共享的一组应用程序级函数缺点:在执行系统调用时,不仅会阻塞当前线程,还会阻塞进程中的所有线程ULT多处理技术无法使用,并且操作系统看不到线程,所以内核一次只能将一个进程分配给一个处理器,所以一个进程中的所有线程不能并行执行,只能并发执行,相当于一个进程内实现。多道程序解决这两个问题的方法是:将应用程序写成多进程程序,但这种方法消除了线程的主要优点Casing技术:将阻塞系统调用转换为非阻塞系统调用总结一下,User-LevelThread(ULT)适用于计算密集型,因为它不需要IO操作,不会阻塞整个进程。Kernel-LevelThread(KLT)在KLT软件中,所有管理线程的工作都是由内核完成的。应用层没有线程管理代码,内核线程只有一个API。内核维护进程级进程中所有线程的上下文信息,调度由内核基于线程来完成。这种方法克服了ULT的两个缺点。首先,内核可以将一个进程中的线程分配给多个处理器;其次,如果进程中的一个线程被阻塞,内核还可以调度同一个进程中的其他线程。缺点是当把控制权从一个线程转移到另一个进程的线程时,需要切换到内核态,开销较大。综上所述,KLT具有更好的并发性,适用于I/O操作较多的程序。Hybrid-ApproachThread(HAT)一些操作系统提供了ULT和KLT的混合:线程创建完全在用户空间完成,线程调度和同步也在应用程序中进行,一个应用程序中的多个线程会映射用户级线程一些(小于或等于用户级线程数)内核级线程。进程和线程的比例为N:M,N<=M,ULT中为1:N,KLT中为1:1。应用程序中的多个线程可以在多个处理器上并发运行,阻塞系统调用不会阻塞整个进程。综上所述,内核级线程(KLT)和用户级线程(User-LevelThread,ULT)的对比分析如下:线程生命周期线程的生命周期基本上可以用这个“五-状态模型”,主要有:初始状态、可运行状态、运行状态、休眠状态、终止状态。其中:初始状态,指线程已经创建,但不允许分配CPU执行。这种状态是编程语言特有的,但这里所谓的创建只是在编程语言层面创建,而在操作系统层面,真正的线程还没有创建。runnable状态意味着线程可以分配CPU来执行。在这个状态下,真正的操作系统线程已经成功创建,所以可以分配CPU执行。运行态:当有空闲CPU时,操作系统会将其分配给一个处于可运行态的线程,分配给CPU的线程的状态就会转换为运行态。休眠状态:如果处于运行状态的线程调用阻塞API(如以阻塞方式读取文件)或等待事件(如条件变量),则该线程的状态将转变为休眠状态,并且同时释放CPU使用权,进入休眠状态的线程永远没有机会获得CPU使用率。当等待事件发生时,线程将从睡眠状态转换到可运行状态。终止状态:线程在执行完或发生异常后将进入终止状态。处于终止状态的线程不会切换到任何其他状态。进入终止状态意味着线程的生命周期结束了。版权声明:本文为博主原创文章,遵循相关版权协议。如需转载或分享,请附上原文出处链接和链接出处。

猜你喜欢