进程与线程——面试必备

并发 与 并行
  • 并行:多个处理器同时处理多个任务
  • 并发:一个处理器快速循环执行多个任务
线程的生命周期(5个)
  • 创建:新建线程对象
  • 就绪:线程创建后,调用 start 方法,进入就绪状态,等待 CPU 使用权
  • 运行:就绪状态的线程获得了 CPU ,开始执行程序。
  • 阻塞:
    • 等待阻塞:运行的线程执行 wait 方法(属于 Object),线程释放所有资源, jvm 会把该线程放入等待池,它不能被自动唤醒,需要依靠 notify 或 notifyAll 唤醒
    • 同步阻塞:运行时线程在获取同步锁时,若该同步锁被别的线程占用,则 jvm 会把该线程放入锁池
    • 其他阻塞:运行时线程执行 sleep(属于Thread类)或 join 方法,或发出了 I/O 请求时,jvm 会把该线程置为阻塞状态。当 sleep 状态超时、join 等待线程终止时,线程重新进入就绪状态。
  • 死亡:线程执行完毕或因异常退出 run 方法,结束了生命周期
线程安全
  • 线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。

  • 保证线程安全方法:

    • 使用线程安全的类
    • 使用synchronized同步代码块,使两个并发线程访问同一个对象 object 中的这个 synchronized(this) 同步代码块时,一个时间内只能有一个线程得到执行。 另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
    • 多线程并发情况下,线程共享的变量改为方法局部级变量
线程池
  • 使用线程池的目的:

    • 降低资源消耗、提高响应速度
    • 提高线程的可管理性
  • 线程池参数(6个):

    • 核心线程数:正常情况下创建的线程数,不会被消除
    • 最大线程数:最大允许被创建的线程数
    • 线程存活时间:超出核心线程数的部分线程(额外创建的线程)空闲一定时间会被消除
    • 任务队列:存放待执行任务
    • 线程工厂:一个接口,用来创建线程
    • 任务拒绝策略:执行队列与任务队列都满了,就使用拒绝策略。
  • 流程图:

    img

  • 线程池的阻塞队列:

    • 一般队列只能保证一个有线长度缓冲区,超出部分则无法保留,而阻塞队列可以保留当前想要进入队列的任务
    • 阻塞队列可以保证任务队列没有任务时阻塞获取人物的线程,使得线程进入 wait 状态,释放 CPU 资源
  • 有新任务时为什么先添加到队列而不是先创建线程?

    答:在创建线程时,要获取全局锁,这个时候其他的就会阻塞,会影响整体效率

  • 线程池各个参数的设置原则:

    • 核心线程数:
      • 考虑因素:核心线程会一直存活、当线程数小于核心线程数时不论有无线程空闲,都会创建新线程处理
      • 分析类型:
        • CPU 密集型:系统的硬盘、内存性能相对CPU要好很多,I/O在很短的时间就可以完成,而CPU还有许多运算要处理——CPU 核数+1
        • IO 密集型:系统的CPU性能相对硬盘、内存要好很多,任务本身需要大量I/O操作——CPU 核数*2
    • 最大线程数:一般设置成与核心线程数一样,这样可以减少在处理过程中创建线程的开销。
  • 线程池拒绝策略

    • 丢弃任务并抛出RejectedExecutionException异常(默认):在任务不能再提交的时候,抛出异常,及时反馈程序运行状态。用于比较关键的业务,在系统不能承载更大的并发量的时候,能够及时的通过异常发现。
    • 丢弃任务,但是不抛出异常:如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃。
    • 丢弃队列最前面的任务,然后重新提交被拒绝的任务:根据实际业务是否允许丢弃老任务来认真衡量。
    • 由调用线程(提交任务的线程)处理该任务:由提交任务的线程直接执行被丢弃的任务。
run() 和 start()
  • 系统通过调用线程类的 start() 方法来启动一个线程,此时线程处于就绪状态,接着通过 JVM 调用线程类的 run() 方法来完成实际操作
  • 直接调用 run() 方法是在当前线程中执行函数。
sleep() 和 wait()
  • sleep() 是 Thread 类静态方法,用于控制自身流程,暂停一段时间自动进入就绪状态;wait() 是 Object 类的方法,用于多线程同步下的线程通信,需要用 notify() / notifyAll() 方法使线程就绪
  • sleep() 不会释放线程上的锁,wait() 会释放它占用的锁
sleep() 和 yield()
  • sleep()给其他线程运行机会时,不考虑线程的优先级,因此会给低优先级的线程以运行的机会,而yield()方法只会给相同优先级或更高优先级的线程以运行的机会
  • sleep()方法会转入阻塞状态,所以,执行sleep()方法的线程在指定的时间内不会被执行,而yield()方法只是使当前线程重新回到可执行状态,所以执行yield()方法的线程很可能在进入到可执行状态后马上又被执行。
join() 方法
  • join()方法的作用是让调用该方法的线程在执行完run()方法后,再执行join方法后面的代码
线程与进程
进程和线程
  • 进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1 — n个线程。多进程是指操作系统能同时运行多个任务(程序)
  • 线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。多线程是指在同一程序中有多个顺序流在执行。
  • 区别:
    • 调度:线程是调度和分配的基本单位,进程是拥有资源的基本单位
    • 并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行
    • 拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问属于进程的资源
    • 系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销
  • 关系:
    • 资源分配给进程,同一进程的所有线程共享该进程的所有资源
    • 真正在处理机上运行的是线程
    • 不同进程的线程间要利用消息通信的办法实现同步
同步和异步
  • 在多线程的环境中,通常会遇到数据共享问题,为了确保共享资源的正确性和安全性,就必须对共享数据进行同步处理(也就是锁机制)
  • Java语言在同步机制中可以通过使用synchronize关键字来实现同步,但该方法是以很大的系统开销作为代价的,有时候甚至可能造成死锁
线程相关API
  • 获取当前线程的名字 Thread.currentThread().getName()
  • start() 启动当前线程
  • 调用线程中的run方法
  • currentThread():静态方法,返回执行当前代码的线程
  • getName():获取当前线程的名字
  • setName():设置当前线程的名字
  • yield() 暂停当前正在执行的线程对象,并执行其他线程
  • join() 在线程中插入执行另一个线程,该线程被阻塞,直到插入执行的线程完全执行完毕以后,该线程才继续执行下去
  • stop():过时方法。当执行此方法时,强制结束当前线程。
  • sleep(long millitime):线程休眠一段时间
  • isAlive():判断当前线程是否存活
创建多线程
方式1:继承于Thread类

1.创建一个集成于Thread类的子类 (通过ctrl+o(override)输入run查找run方法)
2.重写Thread类的run()方法
3.创建Thread子类的对象
4.通过此对象调用start()方法

方式2:实现Runable接口

1.创建一个实现了Runable接口的类
2.实现类去实现Runnable中的抽象方法:run()
3.创建实现类的对象
4.将此对象作为参数传递到Thread类中的构造器中,创建Thread类的对象
5.通过Thread类的对象调用start()

方式3:实现 Callable 接口

  1. 重写 call() 方法
  2. 包装成 java.util.concurrent.FutureTask
  3. 包装成 Thread

三种方式的比较:

  • Thread: 继承方式, 因为Java是单继承的,继承了Thread就没办法继承其它类了,不够灵活
  • Runnable: 实现接口,比Thread类更加灵活,没有单继承的限制
  • Callable: Thread 和 Runnable 都是重写的 run() 方法并且没有返回值,Callable 是重写的 call() 方法并且有返回值并可以借助 FutureTask 类来判断线程是否已经执行完毕或者取消线程执行
  • 当线程不需要返回值时使用 Runnable,需要返回值时就使用 Callable,一般情况下不直接把线程体代码放到 Thread 类中,一般通过 Thread 类来启动线程
  • Thread类是实现Runnable,Callable封装成FutureTask,FutureTask实现RunnableFuture,RunnableFuture继承Runnable,所以Callable也算是一种Runnable,所以三种实现方式本质上都是Runnable实现
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值