首页 > 其他分享 >线程相关

线程相关

时间:2023-04-02 12:55:47浏览次数:26  
标签:调用 Thread 线程 内核 相关 执行 CPU

个人理解,如有错误,请海涵

多任务调度

大部分操作系统如Linux、Windos等,都是采用时间片轮转的抢占式调度方式来实现任务调度的。在这种调度方式下,每个进程执行一个任务都会在一短时间后暂停执行,切换其他进程执行任务。由于进程的上下文切换,CPU需要耗费大量的时间来保存该进程的内存资源以及状态,如果进程过多就会导致CPU用于处理进程上下文切换的时间过多导致系统执行效率降低。因此出现了线程,每个进程拥有多个线程,并且共享该进程的所有内存资源。CPU执行的最小单元从进程变为了线程。这样的好处就是当CPU进行任务调度的时候只需要保存线程所具有的内存资源,减少了很多CPU用于处理上下文切换的时间,这样的好处就是增加了CPU用于处理有效计算任务的时间,提高了系统性能。

多线程模型

  应用线程不能直接访问CPU资源,而是先映射到一个操作系统线程上再去访问CPU资源,所以在早期单核CPU计算机多对一的线程模型中,多个应用线程(用户线程)对应一个操作系统线程(内核线程),这些应用线程串行执行,如果其中一个线程出现阻塞就会一直占用这个内核线程,导致其他线程无法正常执行。

  一对一的线程模型解决了这一个问题,一个用户线程对应一个内核线程来执行任务,其中一个线程的阻塞对其他线程不会产生影响。但是这种线程模型当用户线程过多的时候,会频繁创建、销毁内核线程最终占用大量资源用于创建和销毁内核线程影响到系统性能。这种模型一般会设置内核线程创建数量上限,以免过度浪费资源。

  多对多的线程模型解决了这个问题,内核线程可以执行多个用户线程,用户线程可以在多个内核线程中切换执行,实现了一种多路复用的机制,避免了内核线程大量创建所带来的资源浪费。

数据一致性

  • 加锁
  • CAS机制

​ 同一个进程中的线程共享进程的主内存,而各个线程之间不能实现数据共享。当线程执行时,会从主内存中复制一份数据来进行操作,操作之后的数据在线程之间不具有可见性。

Thread类的使用

start()、run()

​ 每个Thread类的实例在执行时都会对应操作系统的一个内核线程执行,其中Thread类对象实例的执行需要调用Thread的start()方法,对应的内核线程会执行Thread的run()方法,默认这个run()方法不执行任何操作,所以在创建线程时,需要重写run()方法执行业务逻辑。

join()

当主线程调用 join() 方法时,会等待子线程执行完毕再执行下面的操作。

sleep()、yield()

​ 线程调用sleep() 方法时,会休眠指定时间,休眠期间会释放CPU线程资源,让其他线程(不包括调用sleep方法的线程)来抢占CPU资源,调用sleep()方法的线程可能会被中断产生异常,需要try..catch...

​ 线程调用yield()方法时,和sleep()相似,但是释放CPU线程资源后让其他线程抢占CPU线程资源时,包括调用yield()方法这个线程本身,所以有可能还是自己抢占到CPU资源继续执行。

wait()、notify()

​ 这两个方法的实现基于一种消费者和生产者模型,消费者在执行期间没有得到执行所需要的必要数据或其他资源,就调用wait()休眠,等待生产者来唤醒,生产者执行完任务后调用notify()方法唤醒消费者,此时消费者唤醒得到了执行需要的必要资源,线程继续执行。

Runnable接口的使用

​ 由于Java语言的单继承特性,使用继承Thread类创建线程的这种方式其子类不能再继承其他类来实现不同的功能,具有一定的局限性。Runnable应运而生,创建一个Runnable接口的实现类并重写run()方法,在 new Thread()时将这个Runnable接口的实现类作为构造函数的参数传入,调用start()方法启动线程之后,内核线程在调用run()方法时就不再调用Thread默认的run()方法,而是调用Runnable实现类的run()方法。这样Runnable接口的实现类就可以再继承或者实现其他类与接口来扩展功能。

​ 内核线程是如何通过Thread调用到Runnable实现类的run()方法的呢?其实Thread同样实现了Runnable接口,并在内部定义了一个Runnable对象属性,通过构造方法来给这个Runnable对象属性进行赋值。这种实现方式其实是一种静态代理模式,静态代理模式 的定义是与目标对象实现相同的接口并在内部包含一个目标对象的引用,通过构造方法传入实际对象并赋值。所以Thread是Runnable的代理类,代理增强的功能是使用一个子线程而不是使用主线程来执行这个任务。

线程状态

​ 由于线程开启后,并不能获得CPU时间片立即执行,需要等待操作系统调度,所以Thread类定义了一系列线程状态值来表示线程执行的情况。

NEW【新建状态】

当创建了Thread类的实例,但是还没有调用start方法时,该线程处于NEW状态

RUNNABLE【可运行状态】

Thread实例调用start()方法开启线程后,该线程由NEW状态转变为 RUNNABLE 状态,如果该线程立即获取到CPU时间片,内核线程的状态为运行中running,如果该线程没有立即获得时间片,则对应内核线程状态为ready,而这两种内核线程对应到应用线程状态为RUNNABLE

BLOCKED【阻塞状态】

当多个线程访问被锁保护的资源时,没有获取到锁而等待时的状态。当所资源被释放并该线程获取到锁后,从BLOCKED状态转为RUNNABLE状态

WAITING【等待状态】

当前线程调用了wait()方法或者join()方法时,该线程处于WAITING状态。当现车给处于WAITING状态时,该线程不能继续执行,并且如果该线程持有锁资源,就会自动释放锁资源

TIMED_WAITING【超时等待】

和WAITING状态类似,但是具备超时自动唤醒机制,当等待超过指定时间后,线程会自动唤醒并执行

TERMINATED【终止状态】

​ 当前线程执行完毕之后,线程状态为TERMINATED,处于该状态的线程不会再转换为其他状态,Thread对象会被回收

线程安全

由于一个进程的多个线程共享进程资源,当多个线程同时操作同一个资源数据时,会发生线程安全问题

synchronized与互斥锁

  1. sychronized可以使用在类的静态方法上、类的成员方法上、代码块

    • 当synchronized在类的静态方法上使用时,需要使用该类的对象本身作为监视器monitor

    • 当synchronized在类的成员方法上使用时,需要使用该类对象的实例作为监视器

    • 当synchronized在代码块上使用时,可以使用任意一个对象作为监视器

  2. synchronized关键字可以配合监视器对象的wait()、notify()、join()等方法实现线程协作

  3. synchronized使用简单,无须显式加锁和释放锁,由JVM自动加锁和释放

  4. synchronized修饰的范围越小,并发性能越好

    • 原因是修饰范围越大,竞争同一把锁的线程越多,阻塞线程越多,修饰范围越小,整个进程中被阻塞的线程越少
实现原理

​ 任何一个类被加载时,都需要编译为字节码文件,然后加载到JVM中执行,Thread类被加载时,如果执行到synchronized关键字,则在字节码文件的代码块头部和尾部分别加上monitorerentmonitorexit,也就是使用monitorerent 和 monitorexit 来包围方法或者代码块对应的字节码。

​ monitorerent指令:当每个线程执行到monitorerent时,会检查monitor的计数是否为0,如果是,则该线程称为monitoe的拥有者,也就是拿到了锁资源,当该线程内部的方法也使用了synchronized关键字,该线程再一次调用该monitor作为锁时,monitor的计数 +1,这也就是synchronized作为可重入锁的实现

​ monitorexit指令:当该线程每执行完一次同步方法或者代码块时,monitor的计数 -1,知道计数变为0,释放锁资源。

Volatile关键字和线程可见性

内存模型与线程可见

​ 线程不能直接访问主内存,而是具有自己的独立工作内存,线程执行时复制一份主内存数据作为自己的独立工作内存,每个线程只能访问自己的工作内存,在线程之间不具有可见性。

​ 为了解决线程之间某些数据的可见性,提供了Volatile关键字,工作内存中被Volatile修饰的变量在被修改时,会将这个变量的最新值同步到主内存中,同时其他线程中该变量的副本也会失效,从而需要重新从主内存中加载该变量的值,达到数据在线程之间可见。

Volatile不具备原子性

对于共享变量的复合操作,Volatile并不能保证整个操作的原子性和安全性。如果想要保证原子性,则需要Java并发包下的原子类来实现。

Volatile禁止指令重排

在CPU执行指令时,会将没有依赖关系的指令重新调整执行位置,以提高CPU执行效率。指令重排在单线程中可以完美执行,但在多线程中,可能会因为指令顺序的调整导致整个代码执行出错,所以Volatile禁止指令chong'pai

标签:调用,Thread,线程,内核,相关,执行,CPU
From: https://www.cnblogs.com/dev-lurun/p/17280263.html

相关文章

  • Android Camera相关知识整理
    View相关原文:SerfaceView与TextureView的区别区别:Sureface有自己的Serface(由屏幕显示内容合成器(screencompositor)所管理的原生缓冲器的句柄)是一个单独的View,会在WMS中创建单独的窗口,有自己的渲染进程,不受UI层的控制,因此不能与其他UI组合在一起,不能进行平移缩放等变换。而Tex......
  • Django笔记十三之select_for_update等选择和更新等相关操作
    本篇笔记将介绍update和create的一些其他用法,目录如下:get_or_createupdate_or_createselect_for_updatebulk_createbulk_update1、get_or_create前面我们介绍过get()和create()的用法,那么get_or_create()的意思很简单,就是获取或者创建,如果存在就返回,不存在就......
  • NonBlocking 非阻塞IO 状态下的实现单线程协程socket通信
    #服务器端#-*-coding:utf-8-*-importtimefromsocketimport*server=socket(AF_INET,SOCK_STREAM)server.bind(('127.0.0.1',8081))server.listen(5)server.setblocking(False)#至关重要的一步!!!conn_l=[]print('waiting...')whileTrue:......
  • 第2章 线程同步精要
    第2章线程同步精要线程同步的四项原则,按重要性排列:1.首要原则是尽量最低限度地共享对象,减少需要同步的场合。一个对象能不暴露给别的线程就不要暴露;如果要暴露,优先设置对象不可更改;实在不行才暴露可修改的对象,并用同步措施来充分保护它。2.其次是使用高级的并发编程构件,如TaskQ......
  • 实现Callable接口创建线程
    ​ 通过实现Callable接口创建线程与实现Runnable接口创建线程类似,不同之处在于Callable的call()方法可以返回一个结果,并且可以抛出异常。以下是通过实现Callable接口创建线程的示例代码:importjava.util.concurrent.Callable;publicclassMyCallableimplementsCallable<Str......
  • 通过线程池的方式获取线程
    ​ 使用线程池可以更好地管理线程的数量,避免线程数量过多导致系统性能下降的问题。Java中提供了Executor框架,可以很方便地创建和管理线程池。以下是使用线程池的示例代码:importjava.util.concurrent.ExecutorService;importjava.util.concurrent.Executors;publicclassMa......
  • Java创建线程的三种方式
    创建线程的三种方式1.继承Thread类,重写run方法publicclassMyThreadextendsThread{publicvoidrun(){System.out.println("HellofromMyThread!");}}publicclassMain{publicstaticvoidmain(String[]args){MyThreadthread......
  • Java线程:wait()和notify()
    一、wait()和notify()含义二、标准代码示例创建两个线程Thread0和Thread1。代码实现:运行流程详解三、什么时候释放锁—wait()、notify()四、用生活故事讲懂线程的等待唤醒1.老王和老李(专家程序员):2.王哥和李哥(普通程序员):3.小王和小李(新手程序员):五、问题理解1、执行wait()的......
  • 多线程
    内容什么是线程如何创建线程线程的调度线程的一个设计模式:生产消费者模型线程池线程集合对象(侧重点)一、什么是线程进程:运行中的程序才可以称为进程,一个程序一个进程。宏观并行,微观串行。线程:1.任何一个程序都至少拥有一个线程,即主线程。但是java程序默认有两个线......
  • C# 直接在子线程中对窗体上的控件操作是会出现异常
    https://www.bbsmax.com/A/MAzA8klpd9/ Form1里privatedelegatevoidDispMSGDelegate(intindex,stringMSG);publicvoidDispMsg(intiIndex,stringstrMsg){if(this.richTextBox1.InvokeRequired==false)......