【精华】JAVA基础知识——并发与多线程

一、基本概念

1.线程和进程

  进程: 是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
  线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
  总之,一个程序运行后至少有一个进程,一个进程中可以包含多个线程。

2.并行和并发

  并行: 两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。
  并发: 两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行。
在这里插入图片描述

3.锁和同步

1)同步synchronized是一个关键词,是来来修饰方法的,但是锁lock是一个实例变量,通过调用lock()方法来取得锁。
2)只能同步方法,而不能同步变量和类,锁也是一样。
3)同步无法保证线程取得方法执行的先后顺序,锁可以设置公平锁来确保。
4)不必同步类中所有的方法,类可以同时拥有同步和非同步方法。
5)如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制;锁也是一样。
6)线程睡眠时,它所持的任何锁都不会释放。
7)线程可以获得多个锁。比如,在一个对象的同步方法里面调用另外一个对象的同步方法,则获取了两个对象的同步锁。
8)同步损害并发性,应该尽可能缩小同步范围。同步不但可以同步整个方法,还可以同步方法中一部分代码块。
9)在使用同步代码块时候,应该指定在哪个对象上同步,也就是说要获取哪个对象的锁。
注:如果使用锁,要注意避免编码出现死锁。

二、线程基础知识

1.线程实现

  实现线程主要有3种方式:使用内核线程实现使用用户线程实现使用用户线程加轻量级进程混合实现

使用内核线程实现

  • 内核线程
  1. 内核线程是直接由操作系统内核支持的线程,由内核完成线程切换。
  2. 内核通过线程调度器(Scheduler)对线程调度并负责将线程的任务映射到各个处理器上。
  3. 每个内核线程(Kernel Level Thread)是内核的一个分身,支持多线程的内核叫多线程内核(Multi-Threads Kernel)。
  • 轻量级进程
  1. 轻量级进程(通常意义所讲的线程)是内核线程的一种高级接口,只有先支持内核线程,才能有轻量级进程。
  2. 这种轻量级进程与内核线程之间1:1的关系称为一对一的线程模型
  3. 由于内核线程的支持,每个轻量级进程都成为一个独立的调度单元,即使有一个轻量级进程在系统调用中阻塞了,也不会影响整个进程继续工作。

  轻量级进程的局限性:

  1. 轻量级进程基于内核线程实现,线程操作需要进行系统调用,而系统调用需要再用户态和内核态来回切换,调用代价高。
  2. 轻量级进程需要内核线程支持,要消耗一定内核资源(内核栈空间),因此系统支持轻量级进程的数量有限。

使用用户线程实现

  不支持多线程的操作系统DOS使用用户线程实现多线程。

  1. 从广义上来讲,一个线程只要不是内核线程,就可以认为是用户线程(User Thread,UT),因此上文提到的轻量级进程也属于用户线程。
  2. 狭义上的用户线程指的是完全建立在用户空间的线程库上,系统内核不能感知线程存在的实现。
  3. 用户线程的建立、同步、销毁和调度完全在用户态中完成,不要内核的帮助。部分高性能数据库中的多线程就是由用户线程实现的。
  4. 这种进程与用户线程之间1:N的关系称为一对多的线程模型

  优势: 不需要系统内核支援。
  劣势: 线程操作需要程序自己处理,实现复杂。难以使用内核提供的线程调度及处理器映射。

使用用户线程加轻量级进程混合实现

  Unix系列操作系统使用用户线程加轻量级进程混合实现。

  1. 用户线程还是完全建立在用户空间中,因此用户线程的创建、切换、析构等操作依然廉价,并且可以支持大规模的用户线程并发。
  2. 操作系统提供支持的轻量级进程则作为用户线程和内核线程之间的桥梁,这样可以使用内核提供的线程调度功能及处理器映射,并且用户线程的系统调用要通过轻量级线程来完成,大大降低了整个进程被完全阻塞的风险。
  3. 在这种混合模式中,用户线程和轻量级进程的数量比是不确定的,即为N:M的关系,这就是多对多的线程模型

1.JDK1.2 之前,使用用户线程实现,JDK1.2之后线程模型基于操作系统原生线程实现。
2. Windows、Linux版都是使用基于内核线程的轻量级进程来实现线程。

用户级线程和内核级线程的区别

  1. 内核支持线程是OS内核可感知的,而用户级线程是OS内核不可感知的。
  2. 用户级线程的创建、撤消和调度不需要OS内核的支持,是在语言(如Java)这一级处理的;而内核支持线程的创建、撤消和调度都需OS内核提供支持,而且与进程的创建、撤消和调度大体是相同的。
  3. 用户级线程执行系统调用指令时将导致其所属进程被中断,而内核支持线程执行系统调用指令时,只导致该线程被中断。
  4. 在只有用户级线程的系统内,CPU调度还是以进程为单位,处于运行状态的进程中的多个线程,由用户程序控制线程的轮换运行;在有内核支持线程的系统内,CPU调度则以线程为单位,由OS的线程调度程序负责线程的调度。
  5. 用户级线程的程序实体是运行在用户态下的程序,而内核支持线程的程序实体则是可以运行在任何状态下的程序。

3.线程调度

  线程调度:系统为线程分配处理器使用权的过程,主要调度方式有两种,分别是协同式线程调度(Cooperative Threads-Scheduling)和抢占式线程调度(Preemptive Threads-Scheduling)。

  • 协同式线程调度【lua的协同例程】

  线程的执行时间由线程本身来控制,线程执行完之后要主动通知系统切换到另一个线程。

  • 抢占式线程调度【Java的线程调度】

  每个线程由系统分配执行时间,线程切换不由线程本身决决定(Thread.yield()可以让出时间,但是无法获取执行时间)
  线程执行时间由系统控制,程序只能通过设置线程优先级建议系统给线程分配不同时间。

4.线程优先级

  虽然Java线程调度是系统自动完成的,但是我们还是可以“建议”系统给某些线程多分配一点执行时间,另外的一些线程则可以少分配一点——这项操作可以通过设置线程优先级来完成。
  每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。Java语言一共设置了10个级别的线程优先级,Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY )-10(Thread.MAX_PRIORITY )。默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。
  在两个线程同时处于Ready状态时,优先级越高的线程越容易被系统选择执行。不过,线程优先级并不是太靠谱,原因是Java的线程是通过映射到系统的原生线程上来实现的,所以线程调度最终还是取决于操作系统。

三、多线程模型

1.生命周期

  Java 线程的生命周期包括创建,就绪,运行,阻塞,死亡 5 个状态。一个 Java 线程总是处于这 5 个生命周期状态之一,并在一定条件下可以在不同状态之间进行转换 。
在这里插入图片描述

  • 新建状态

  使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。

  • 就绪状态

  当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。

  • 运行状态

  如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。

  • 阻塞状态

  如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:

  等待阻塞: 运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
  同步阻塞: 线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
  其他阻塞: 通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。

  • 死亡状态

  一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

2.线程模型

  Future模型、Fork&Join 模型、Actor消息模型、生产者消费者模型、Master-Worker模型

  • Future模型

  Future模型通常在使用的时候需要结合Callable接口配合使用。Future是把结果放在将来获取,当前主线程并不急于获取处理结果。允许子线程先进行处理一段时间,处理结束之后就把结果保存下来,当主线程需要使用的时候再向子线程索取。

  Callable是类似于Runnable的接口,其中call方法类似于run方法,所不同的是run方法不能抛出受检异常没有返回值,而call方法则可以抛出受检异常并可设置返回值。两者的方法体都是线程执行体。

  • Fork&Join 模型

  该模型是jdk中提供的线程模型。该模型包含递归思想和回溯思想,递归用来拆分任务,回溯用合并结果。 可以用来处理一些可以进行拆分的大任务。

  其主要是把一个大任务逐级拆分为多个子任务,然后分别在子线程中执行,当每个子线程执行结束之后逐级回溯,返回结果进行汇总合并,最终得出想要的结果。这里模拟一个摘苹果的场景:有100棵苹果树,每棵苹果树有10个苹果,现在要把他们摘下来。为了节约时间,规定每个线程最多只能摘10棵苹树以便于节约时间。各个线程摘完之后汇总计算总苹果树。

  • Actor消息模型

  actor模型属于一种基于消息传递机制并行任务处理思想,它以消息的形式来进行线程间数据传输,避免了全局变量的使用,进而避免了数据同步错误的隐患。actor在接受到消息之后可以自己进行处理,也可以继续传递(分发)给其它actor进行处理。在使用actor模型的时候需要使用第三方Akka提供的框架。

  • 生产者消费者模型

  生产者消费者模型都比较熟悉,其核心是使用一个缓存来保存任务
  开启一个或多个线程来生产任务,然后再开启一个或多个来从缓存中取出任务进行处理。这样的好处是任务的生成和处理分隔开,生产者不需要处理任务,只负责向生成任务然后保存到缓存。而消费者只需要从缓存中取出任务进行处理。使用的时候可以根据任务的生成情况和处理情况开启不同的线程来处理。比如,生成的任务速度较快,那么就可以灵活的多开启几个消费者线程进行处理,这样就可以避免任务的处理响应缓慢的问题。

Master-Worker模型
  master-worker模型类似于任务分发策略,开启一个master线程接收任务,然后在master中根据任务的具体情况进行分发给其它worker子线程,然后由子线程处理任务。如需返回结果,则worker处理结束之后把处理结果返回给master。使用的时候也可以使用java Thread来实现该模型。

四、多线程的实现

发布了123 篇原创文章 · 获赞 119 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/cbwem/article/details/103433482
今日推荐