JUC(1)——线程

1.进程和线程

进程

概念

进程就是为了更好的描述和控制多道程序环境下程序的并发执行,实现操作系统的并发性和共享性。

进程是应用程序在内存中的运行过程,是系统进行资源分配和调度的一个独立单位,这里的系统资源指的是CPU,存储器及其他设备服务于某个进程的时间,因此进程一定是一个动态的、过程性的概念。

每个进程都有自己独立的一块内存空间,一个进程可以有多个线程,比如在Windows系统中,一个运行的xx.exe就是一个进程。

结构
  • PCB,即进程控制块,它是进程存在的唯一标识,其中包括进程描述信息,控制和管理信息、资源分配清单等;
  • 程序段,就被进程调度程序调度到CPU执行的程序代码段。
  • 数据段,进程对应的程序加工处理的原始数据,也可以是程序执行时产生的中间或最终结果。
特征
  • 动态性:动态性是进程的基本特征,进程是一次程序的执行,它具有生命周期,是动态的产生,变化和消亡的;
  • 并发性:并发性是进程的重要特征,也是操作系统的重要特征。多个进程可以同时存在于内存中,能在一段时间内同时运行。主要目的是使程序并发执行,提高资源利用率;
  • 独立性:进程实体是一个能独立运行、独立接受调度的基本单位;
  • 异步性:由于进程的相互制约,会使进程具有执行的间断性,即进程按各自独立的,不可预知的速度向前推进;
  • 结构性:从结构上看进程实体是由程序段、数据段和PCB组成的,每个进程都由一个PCB来对其进行描述。
进程通信
  • 共享存储:在通信的进程之间存在一块可以直接访问的共享空间,共享存储分为两种:低级的共享基于数据结构,高级的共享基于存储区。操作系统只负责为通信进程提供可共享的存储空间和同步互斥工具,数据交换由用户自己安排读写指令完成。
  • 消息传递:进程间的数据交换以格式化的消息为单位,进程提供系统提供的发送消息和接收消息两个原语进行数据交换。消息传递分为:直接通信方式,把消息挂在接收进程的消息缓存队列上。间接通信方式,发送进程把消息发送到某个中间实体,中间实体一般称作信箱,相应的通信系统为电子邮件系统。
  • 管道通信:消息传递的一种特殊方式,管道就是连接一个读进程和一个写进程来实现它们通信的一个共享文件。管道可以理解为共享存储的优化和发展,管道通信中存储空间优化为缓冲区,缓冲区只允许一边写入另一边读出,只要缓冲区有数据进程就能从缓冲区读出,只要有数据写进程就不会往缓冲区写数据,因此管道通信是半双工通信。

线程

概念

引入进程的目的是为了多道程序更好的并发执行,提高资源利用率和吞吐量

**引入线程的目的是为了减少程序在并发执行时的时间和空间开销,提高操作系统的并发性能。 **

一个进程中至少有一个线程,线程是一个基本的CPU执行单位,也是程序执行流的最小单元,是OS独立调度和分配的基本单位,由线程ID、程序计数器、寄存器集合和堆栈组成。线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它与同一进程下的其他线程共享进程的全部资源

2.进程和线程的区别

  • 包含关系:线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。

  • 调度:进程是拥有系统资源的基本单位,而线程是独立调度的基本单位。

    在同一进程中,线程的切换不会引起进程的切换;在不同进程中线程的切换会引起进程切换。

  • 拥有资源:进程是拥有资源的基本单位,线程不拥有系统资源,只有一点运行中必不可少的资源。如果线程也是拥有资源的单位,那么切换线程就需要较大的时空开销,它的引入就没有意义。

  • 系统开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。

  • 内存分配:同一进程中的线程共享该进程的地址空间和资源,不同进程间的地址空间和资源相互独立。

  • 通信:进程间通信需要同步和互斥手段的辅助,保证数据一致性。线程可以直接读写进程数据段(全局变量)来进行通信。

  • 影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响;但一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。

  • 执行过程:每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行

3.多线程?

概念

多线程:在一个程序中可以同时运行多个不同的线程来执行不同的任务。

好处:可以提高 CPU 的利用率。在多线程程序中,一个线程必须等待的时候,CPU 可以运行其它的线程而不是等待,这样就大大提高了程序的效率。也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。

劣势:1>线程需要占用内存,线程越多占用内存也越多;

​ 2>多线程需要协调和管理,所以需要 CPU 时间跟踪线程;

​ 3>线程之间对共享资源的访问会相互影响,必须解决争抢共享资源的问题。

线程上下文切换

什么是线程上下文切换?

一般在多线程编程中线程的个数都大于 CPU 个数,但一个 CPU 在任意时刻只能被一个线程使用,为了让所有线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。

概括来说就是:在当前任务执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换

上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的 CPU 时间,并且可能是操作系统中时间消耗最大的操作。Linux 相比与其他操作系统(包括其他类 Unix 系统)有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少。

减少上下文切换的方法?
  • 无锁并发编程:多线程竞争锁时会引起上下文切换,所以多线程处理数据时,可以通过一些方法来避免使用锁,例如将数据的id按照hash算法取模分段,不同的线程处理不同数据段的数据。

  • CAS算法:Java的atomic包使用CAS算法来更新数据而不需要加锁。

  • 使用最少线程:避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,这样会造成大量线程都处于等待状态。

  • 协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。

守护线程与用户线程

  • 用户线程:运行在前台,执行具体的任务,如程序的主线程、连接网络的子线程等;

  • 守护线程:运行在后台,为其他前台线程服务。一旦所有用户线程都结束运行,守护线程会随 JVM 一起结束工作。

比如main函数所在的线程就是一个用户线程,在它启动的同时JVM 内部还启动了其他守护线程,比如垃圾回收线程。

比较明显的区别就是用户线程结束,JVM 退出,不管这个时候有没有守护线程运行。而守护线程不会影响 JVM 的退出。

注意

  • setDaemon方法必须在start()方法前执行,否则会抛出 IllegalThreadStateException 异常
  • 在守护线程中产生的新线程也是守护线程
  • 不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑
  • 守护线程中不能依靠 finally 块的内容来确保执行关闭或清理资源的逻辑。因为我们上面也说过了一旦所有用户线程都结束运行,守护线程会随 JVM 一起结束工作,所以守护线程中的 finally 语句块可能无法被执行。

创建线程的四种方式

  • 继承 Thread 类;

    1. 定义一个Thread类的子类,重写run方法,将相关逻辑实现,run()方法就是线程要执行的业务逻辑方法
    2. 创建自定义的线程子类对象
    3. 调用子类实例的star()方法来启动线程
  • 实现 Runnable 接口;

    1. 定义Runnable接口实现类MyRunnable,并重写run()方法
    2. 创建MyRunnable实例myRunnable,以myRunnable作为target创建Thead对象,该Thread对象才是真正的线程对象
    3. 调用线程对象的start()方法
  • 实现 Callable 接口;

    1. 创建实现Callable接口的类myCallable
    2. 以myCallable为参数创建FutureTask对象
    3. 将FutureTask作为参数创建Thread对象
    4. 调用线程对象的start()方法
  • 自定义线程池。

线程的生命周期

在这里插入图片描述
在这里插入图片描述

新生状态-运行状态-阻塞状态-等待状态-超时等待-终止状态

1.新建(new):新创建了一个线程对象。

2.可运行(runnable):线程对象创建后,当调用线程对象的 start()方法,该线程处于就绪状态,等待被线程调度选中,获取cpu的使用权。

3.运行(running):可运行状态(runnable)的线程获得了cpu时间片(timeslice),执行程序代码。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;

4.阻塞(block):处于运行状态中的线程由于某种原因,暂时放弃对 CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被 CPU 调用以进入到运行状态。

阻塞的情况分三种:

  • 等待阻塞:运行状态中的线程执行 wait()方法,JVM会把该线程放入等待队列(waitting queue)中,使本线程进入到等待阻塞状态;

  • 同步阻塞:线程在获取 synchronized 同步锁失败(因为锁被其它线程所占用),则JVM会把该线程放入锁池(lock pool)中,线程会进入同步阻塞状态;

  • 其他阻塞: 通过调用线程的 sleep()或 join()或发出了 I/O 请求时,线程会进入到阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入就绪状态。

5.死亡(dead):线程run()、main()方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SONNIE在路上

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值