多线程最基本的概念理解

背景

多线程入门知识挺多的,尤其是JMM需要学习。JVM内存模型也需要学习。很多概念需要搞明白,比如进程,线程,协程,守护线程,用户线程,线程上下文切换,共享变量,局部变量,用户空间,内核空间,单例,多例等等。线程间通信表达什么意思。线程间如何交换数据。业务逻辑和线程的隔离(Runnable, Thread)。线程的生命周期。


核心概念理解
  • 进程
  1. 比如Java程序一旦起来,其实就是Java进程,就会分配相应的内存结构。也有可能有多个进程,比如一些守护进程(system.gc)。
  2. 在同一个JVM上可以跑很多个Java应用程序,其实就是Java进程,而且这些进程间是相互独立的。
  • 线程 在《深入理解计算机系统》中有详细的描述
  1. 在HotSpot VM线程模型中,Java线程(java.lang.Thread)被一对一映射为本地操作系统线程。Java线程启动时会创建一个本地操作系统线程;当该Java线程终止时,这个操作系统线程也会被回收。操作系统会调度所有线程并将它们分配给可用的CPU。线程是程序执行的基本单位,是CPU时间片调度的基本单位。
  • 协程
  1. Kotlin编程语言中有协程的概念。协程开销小,无抢占式的调度。协程也是线程。协程调用切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量。所以上下文切换非常快。
  2. Java编程语言是没有协程概念的,但是也是可以支持的。使用quasar.jar包。
  3. Golang编程语言中的goroutine就是轻量级的线程。与创建线程相比,创建成本和开销都很小,每个goroutine的堆栈只有几kb。
  • 守护线程
    守护线程有一定的保护、协助和辅助的含义。它是建立子用户线程之上的。如果用户线程全部销毁了,那么守护线程也会被销毁。Java中一个守护线程GC线程。

  • 用户线程
    当Java应用程序启动后,Java进程存活于JVM中。应用程序通过java.lang.Thread类创建的线程就是用户线程。比如Executor框架中的线程池中的线程就是用户线程。

  • 线程上下文切换
    操作系统采用时间片的方式对线程进行调度。同一个核心每个时刻只有一个线程在执行,多颗核心,同时就会有多个线程执行。当线程切换的时候,当前线程的状态就会保存到寄存器中,如果线程抢占到资源的时候,在恢复线程状态,并执行。

  • 共享变量
    共享变量是需要考量线程安全的,比如JVM内存模型中的堆上内存,这个堆上分配的引用类型就是共享变量,同时可能会被多个线程访问,所以需要我们保证线程安全。

  • 局部变量
    局部变量是线程安全的,能够为每个线程创建一份。

  • 用户空间
    像Java进程一样,当启动Java应用程序后,会给Java进程就是用户空间。在这个用户空间中有分配的内存大小,有创建的用户线程。

  • 内核空间
    是操作系统的概念。区别用户空间。

  • 单例
    单例是线程不安全的,每个线程都会访问到同一个变量。如果单例的属性都是finall修饰的,那么是线程安全。如果不是线程安全的话,需要使用保证线程安全的知识。比如ThreadLocal, volatile, synchronized,final的运用。

  • 多例
    多例是线程安全的,是为每个线程都会创建一个实例。相当于一个局部变量一样。

  • 轻量级的线程

  1. 少一些时间片的抢占。
  2. 少一些上下文的切换。
  3. 执行栈直接去寄存器中拿操作指令,而不关心其他操作,比如锁之类的。
  • Java应用程序启动线程说明
  1. 当非守护线程main启动的时候,后台会启动很多其他守护线程,比如Reference Handler句柄引用的守护线程,Finalizer垃圾回收的守护线程,Signal Dispatcher 信号的守护线程比如kill -9 给操作系统发送一个信号。
  2. 当我们另启一个自定义线程叫Custom-Thread,这个线程跟main线程是独立的。
  3. 当main线程中的逻辑执行完成后,main线程就销毁了。Custom-Thread线程正在执行。通过线程sleep时间进行调节。
  4. 而当Custom-Thread线程执行完成后,Custom-Thread线程就被销毁了,main线程依然在执行。通过sleep时间进行调节。
  5. main是入口线程,在这个main线程中启动几个自定义线程,但是main的逻辑执行完成后,main线程就被销毁掉。然后其他线程继续执行。之所以能够继续执行是因为这个Java进程还在。为什么进程还在呢?是因为main在启动过程中还有启动了很多守护线程或守护进程。一旦线程全部执行完成了,则这些守护线程也没有存在的意义了,也会被销毁掉。
  • 为什么要使用多线程?
  1. 现代计算机几乎都是多核的。使用多线程可以避免硬件资源的浪费。
  2. 在一个功能中。我可以启动一个线程去读文件,在启动一个线程去发出HTTP请求。解耦业务逻辑,提高程序执行速度。
  3. 核心目的就是:加快程序执行速度。
  • 并发与并行
  1. 并发,描述的是系统中同时有多少线程在运行比如100w个线程之类的,计算机依然是需要在线程间进行上下文切换,对应CPU核来说,它依然是同一个时间点只有一个线程被执行。
  2. 并行,描述的是系统中的线程是同时运行。如果要支持同时运行,则我们的CPU必须是多核心的,不然永远也无法并行。哪怕我们的CPU使用了超线程技术,它依然只能在同一个时间点,也只能执行一个线程,另一个线程还是被挂起的。
  • 线程的生命周期
  1. new Thread(“MyThread“),创建一个线程。
  2. 调用start()后,线程存活在JVM中,但是线程并不会立即执行。而只是具备可执行的状态。runnable
  3. CPU(Dispatch)分配时间片执行。
  4. running
  5. blocked
  6. terminated
  7. 在CPU进行线程切换的时候,就是在running和runnable之间状态不停切换。任何一个状态都有可能变成死亡状态。是JVM调用run()方法。main线程去启动其他线程。如果是在main中直接调用线程的run方法的话,那么是不会有新的线程出现的,所以调用start方法才是启动一个新的线程。
  • 如何理解run()方法
  1. 其实Thread使用了模板方法设计模式,并预留出run()方法。因为Thread并不知道用户的具体实现逻辑是怎样的,用户需要自己继承Thread并且重写run方法。
  2. 并且这个模板方法是必须定义为final类型的,这样子类是不能复写这个方法。
  3. 源码底层有native方法采用C++写的,start0()。
  • 线程其他基础信息
  1. 只要Java涉及到的线程,都只有一个Thread类。Thread类实现了Runnable接口。
  2. 无论是线程池还是Java并发包下面的所有的一些关于线程的实现,都是需要使用Thread类。
  3. 在实践的应用。Netty中NioEventLoop类就持有Thread类的引用。
  4. 也就说,当说到线程,一定是指的java.lang.Thread。
小结
  1. Java应用程序的main函数是一个线程,是被JVM启动的时候调用,线程的名字叫main。
  2. 实现一个线程,必须创建Thread实例, override run 方法,并且调用start方法。
  3. 在JVM启动后,实际上有多个线程,但是至少有一个非守护线程。
  4. 当调用一个线程start方法的时候,此时至少有两个线程,一个是启动线程的线程,还有一个是执行run方法的线程,当线程被执行了start()方法后。
  5. 线程的生命周期分为:new, runnable, running, block, terminate。熟悉状态之间的转换条件。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值