本文主要将线程与锁的重点进行梳理与总结,从而能对并发基础有一个更好的掌握。
我们按照如下流程来进行一个大致的梳理。
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规则