1.定义
所谓死锁,是指两个或者以上线程并行执行的时候,争夺资源而互相等待造成的。当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。
2.死锁产生的四个必要条件?
死锁只有同时满足以下四个条件才会发生:
3.解决死锁的基本方法
避免死锁问题,就是要破坏其中一个条件即可,最常用的方法就是使用资源有序分配法来破坏环路等待条件。
多线程访问共享资源的时候,避免不了资源竞争而导致数据错乱的问题,所以我们通常为了解决这一问题,都会在访问共享资源之前加锁。加锁的目的就是保证共享资源在任意时间里,只有一个线程访问,这样就可以避免多线程导致共享数据错乱的问题。
悲观锁认为多个线程访问同一个共享变量冲突的概率较大, 会在每次访问共享变量之前都去真正加 锁.。乐观锁认为多个线程访问同一个共享变量冲突的概率不大. 并不会真的加锁, 而是直接尝试访问数据. 在访问的同时识别当前的数据是否出现访问冲突。
悲观锁的实现就是先加锁(比如借助操作系统提供的 mutex), 获取到锁再操作数据. 获取不到锁就 等待. 乐观锁的实现可以引入一个版本号. 借助版本号识别出当前的数据访问是否冲突.
Synchronized 初始使用乐观锁策略. 当发现锁竞争比较频繁的时候, 就会自动切换成悲观锁策略. 就好比同学 C 开始认为 "老师比较闲的", 问问题都会直接去找老师. 但是直接来找两次老师之后, 发现老师都挺忙的, 于是下次再来问问题, 就先发个消息问问老师忙不 忙, 再决定是否来问问题.
读写锁就是把读操作和写操作分别进行加锁. 读锁和读锁之间不互斥. 写锁和写锁之间互斥. 写锁和读锁之间互斥. 读写锁最主要用在 "频繁读, 不频繁写" 的场景中.Synchronized 不是读写锁.
相比于挂起等待锁, 优点: 没有放弃 CPU 资源, 一旦锁被释放就能第一时间获取到锁, 更高效. 在锁持有时间比较短的场 景下非常有用. 缺点: 如果锁的持有时间较长, 就会浪费 CPU 资源.
如果你能确定被锁住的代码执行时间很短,就不应该用互斥锁,而应该选用自旋锁,否则使用互斥锁。
互斥锁加锁失败时,会从用户态陷入到内核态,让内核帮我们切换线程,虽然简化了使用锁的难度,但是存在一定的性能开销成本。自旋锁是通过 CPU 提供的 CAS 函数(Compare And Swap),在「用户态」完成加锁和解锁操作,不会主动产生线程上下文切换,所以相比互斥锁来说,会快一些,开销也小一些。
是可重入锁. 可重入锁指的就是连续两次加锁不会导致死锁,允许同一个线程多次获取同一把锁。 实现的方式是在锁中记录该锁持有的线程身份, 以及一个计数器(记录加锁次数). 如果发现当前加锁 的线程就是持有锁的线程, 则直接计数自增.
Java里只要以Reentrant开头命名的锁都是可重入锁,而且JDK提供的所有现成的Lock实现类,包括 synchronized关键字锁都是可重入的。 而 Linux 系统提供的 mutex 是不可重入锁.
全称 Compare and swap, 即 "比较并交换". 相当于通过一个原子的操作, 同时完成 "读取内存, 比 较是否相等, 修改内存" 这三个步骤. 本质上需要 CPU 指令的支撑.
ABA问题(好比, 我们买一个手机, 无法判定这个手机是刚出厂的新手机, 还是别人用旧了, 又翻新过的手 机.)给要修改的数据引入版本号. 在 CAS 比较数据当前值和旧值的同时, 也要比较版本号是否符合预期. 如果发现当前版本号和之前读到的版本号一致, 就真正执行修改操作, 并让版本号自增; 如果发现当 前版本号比之前读到的版本号大, 就认为操作失败
1.什么是synchronized关键字?
在多线程的环境下,多个线程同时访问共享资源会出现一些问题,而synchronized关键字则是用来保证线程同步的。
synchronized关键字的使用方式主要有三种:
修饰普通同步方法、修饰静态同步方法、修饰同步方法块。
面试时经常拿synchronized关键字和volatile关键字的特性进行对比,synchronized关键字可以保证并发编程的三大特性:原子性、可见性、有序性,而volatile关键字只能保证可见性和有序性,不能保证原子性,也称为是轻量级的synchronized。
synchronized关键字可以保证只有一个线程拿到锁,访问共享资源。synchronized时,会对应执行 lock、unlock原子操作,保证可见性。synchronized关键字实现的是悲观锁,每次访问共享资源时都会上锁。synchronized关键字实现的是非公平锁,即线程获取锁的顺序并不一定是按照线程阻塞的顺序。synchronized关键字实现的是可重入锁,即已经获取锁的线程可以再次获取锁。synchronized关键字实现的是独占锁,即该锁只能被一个线程所持有,其他线程均被阻塞。volatile主要是保证内存的可见性,即变量在寄存器中的内存是不确定的,需要从主存中读取。synchronized主要是解决多个线程访问资源的同步性。volatile作用于变量,synchronized作用于代码块或者方法。volatile仅可以保证数据的可见性,不能保证数据的原子性。synchronized可以保证数据的可见性和原子性。volatile不会造成线程的阻塞,synchronized会造成线程的阻塞。