并发基础之线程与锁重点梳理与总结

77 阅读6分钟

本文主要将线程与锁的重点进行梳理与总结,从而能对并发基础有一个更好的掌握。

我们按照如下流程来进行一个大致的梳理。

image-20220529203251325

1. 并发?并行?

首先我们来点老生常谈的话题,什么是并发?什么是并行?相信这个大家应该都很了解了。

  • 并发:在同一时间段上同时执行,比如在十毫米这个时间段内,前五毫秒做一件事,后五毫秒做一件事。
  • 并行:在同一时间点上同时执行,要实现并行必须是多核CPU才能完成。

那我们如何实现并发呢?

这个问题实际上很简单,无非就是我们使用多个线程去实现并发。

2. 线程?进程?

上文我们提到了线程,那么什么是线程呢?

  • 线程:运行程序种的一个顺序控制流程,也是CPU中的最小调度单位(能独立自主的完成一件事情)。
  • 进程:运行中的应用程序。

3. 怎么使用线程?

初中级面试题:线程的创建方式你知道几种?

上面的面试题答案大家知道吗?没错,正常情况下,我相信大家都会说有四种方式可以用来创建线程。

  • new Thread

    • 继承Thread类

    • 实现Run方法

    • 通过new创建对象

    • 调用start方法

  • new Runnable

    • 实现runnable接口
    • 实现run方法
    • 通过new关键字创建对象
    • 创建一个Thread对象用来执行任务
    • 调用线程的start方法
  • new Callable

    • 实现callable接口
    • 实现call方法
    • 通过new创建任务对象
    • 通过一个线程池去承载上面的任务
    • 通过线程池的submit去提交任务
  • 线程池

接下来我们需要搞清楚其中的一些本质区别。

首先是Thread和Runnable的本质区别:

  • Thread是类,Runnable是接口。
  • Thread底层会真正的创建线程,但是Runnable不是线程,他只是一个任务。要执行任务,还需要Thread去执行。

其次是Runnable和Callable的本质区别:

  • Callable可以抛出异常
  • Callable可以有返回值

了解了上述创建方式之后,重新来看这个面试题,我觉得可能需要换一个高手答案了(当然,不是说上面的回答不对)。

创建线程的方式只有一种,就是继承Thread类,因为其他三种最终都是通过创建Thread去实现。

除了高手答案,我们还有一个大佬答案。(想不到吧?)

JDK没有创建线程的能力,JDK是通过调用native方法start()通知底层操作系统创建线程。

4. 线程的一生

面试题:线程的生命周期包括那些?

  • new :初始化状态
  • runnable:运行状态
    • start:就绪状态,只是通知底层操作系统,我已经准备就绪了,你可以给我分配时间片了
    • running:运行中状态
  • timed_waiting:限期等待;wait,join,
  • waiting:无限期等待;sleep(long),wait(long),join(lomg)
  • blocked:阻塞状态,该状态只有在遇到synchronized的时候,且没有抢占到锁才会进入
  • terminated:终止状态

5. 线程的启动和停止

  • 启动:start

  • 暴力停止:stop

  • 优雅停止:interrupt

    • interrupt :更改中断标记,唤醒处于阻塞状态下的线程
    • interrupted:返回中断标记,然后复位
    • isInterrupted:返回中断标记

6. 线程的问题

如果我们是单线程的话,基本上不会有啥问题,但是如果是多个呢?所以我们说线程安全问题的本质是并发。

那我们如何解决安全问题呢?没错就是通过加锁的方式来处理。

我们有如下几类安全性问题:

  • 原子性问题
  • 有序性问题
  • 可见性问题

7. synchronized

我们一般可以通过synchronized来解决线程安全的问题,这里我们稍微说明一下,如果需要对其详细了解可以查看主页中的其他并发博客。

关于synchronized的使用,我们有如下三种方式:

  • 修饰普通方法:锁的是对象
  • 修饰静态方法:锁的是类
  • 修饰代码块:可以锁对象也可以锁类

了解其使用后,我们可以了解一下他的状态,关于其转换过程可以查看该文 synchronized详解

  • 无锁
  • 偏向锁,不存在线程竞争
  • 轻量级锁(执行死循环自旋锁,尝试抢占锁)
  • 重量级锁

8. 锁的分类

面试题:你知道那些锁?

  • 乐观锁和悲观锁
    • CAS操作就是乐观锁
  • 公平锁和非公平锁
  • 共享锁和独占锁
  • 可重入锁和不可重入锁
  • 自旋锁
  • 分段锁
  • 间隙锁等

9. Reentrantlock

面试题:Reentrantlock 和 synchronized区别?

  • 底层实现层面

    • synchronized 是JVM层面的锁 ,是JAVA关键字
    • reentrantlock是JUC下面的一个类,是JAVA实现的
  • 是否可手动释放

    synchronized不需要手动释放,reentrantlock需要手动释放

  • 是否可以中断

    synchronized不可中断,reentrantlock可以中断

  • 锁的层面

    reentrantlock的对象就是锁本身,synchronized锁的别的对象

  • 类别层面

    reentrantlock可以是公平也可以是非公平,通过构造函数指定,synchronized是非公平锁

面试题:要你去设计一把锁,你会怎么设计?

设计一把锁,首先我们要知道锁的实现原理。

  • 要有一个共享变量标志锁的状态
  • 可重入锁的话,要有一个成员变量去存储当前是那个线程占用了锁
  • 还要有一个队列去存储阻塞的线程

10. AQS

面试题:谈谈你对AQS的理解

  • AQS即AbstractQueuedSynchronizer,是用来实现锁和线程同步的一个工具类。大部分操作都是基于CAS和FIFO队列来实现。
  • JUC中的许多并发工具类都依赖AQS,例如Reentrantlock,CountDownLatch等。
  • AQS定义了一个锁实现的内部流程,而如何加锁和解锁则在各个子类中实现,这是典型的模板方法。
  • 里面包含了许多并法包的公告功能,例如加锁,解锁,入队,出队,阻塞和唤醒,
  • AQS内部维护了一个FIFO的队列(底层就是双向链表),通过该队列来实现线程的并发访问控制。

11. 其他解决不安全性问题的方法——volatile

  • 解决可见性问题
  • 解决有序性问题
  • 不能解决原子性问题

12. Happens-Before原则

  • 程序顺序规则
  • 传递性规则
  • volatile变量规则
  • 监视器锁规则
  • start规则
  • join规则

13. 思维导图

assets.processon.com/chart_image…