并发编程中有两个重要的概念:
线程,锁
多线程是一把双刃剑,在提高程序性能的同时,
也带来了代码复杂性,对开发者的要求也提高了一个档次。
锁的出现就是为了保证多线程在同时操作一组资源时的数据一致性。
死锁是指两个线程同时占用两个资源,又在彼此等待对方释放资源
锁的概念不止存在于java语言中
比如乐观锁和悲观锁很早以前就存在于数据库中了。
锁相关的面试题:
什么是乐观锁和悲观锁,他们的应用有哪些?乐观锁有什么问题?
悲观锁
对外界的修改采取保守策略
他认为线程很容易会把数据修改掉
因此在整个数据被修改的过程中都会采取锁定状态
直到一个线程使用完,其他线程才能继续使用
乐观锁
一般情况下数据在修改时不会出现冲突
所以在数据访问之前不会加锁
只是在数据提交更改时,才会对数据进行检测
Java中的乐观锁大部分是通过CAS操作实现的
CAS是一个多线程同步的原子指令
CAS操作包含三个重要信息:内存位置、预期原值、新值
Java中Lock是乐观锁的典型案例(底层通过CAS)
CAS有可能出现ABA问题
ABA问题:
线程拿到了最初的预期值A,然而在将要进行CAS的时候,被其他线程抢占了执行权,把此值从A变成了B
然后其他的线程又将此值从B改成A,而此时的A值已经并非原来的A值了
但当初的线程在执行的时候并不知道这个情况,在他进行CAS的时候只对比了预期原值是A,就进行了修改
ABA问题的常见处理方式是增加版本号,每次修改之后都更新版本号
JDK在1.5时提供了AtomicStampedReference类,也可以解决ABA问题,此类维护了一个版本号Stamp,每次在比较时不仅比较值,还会比较版本号,就解决了ABA问题
乐观锁是在提交的时候才进行锁定的,所以不会造成死锁
什么是可重入锁?用代码如何实现?他的实现原理是什么?
可重入锁
也叫递归锁
同一线程,如果外面的函数拥有此锁后,内层的函数也可以继续获取该锁。
ReentrantLock和Synchronized都是可重入锁
可重入锁的实现原理是,在锁内部存储了一个线程标识,用于判断当前锁属于那个线程,并锁内部还有一个计数器,当线程空闲时,计数器值为0,当线程占用或重入时,计数器+1,当释放锁时,计数器-1,当计数器=0时,说明锁为空闲状态。
什么是共享锁?什么是独占锁?
独占锁
只能被单线程持有,ReentrantLock就是独占锁
可以理解为悲观锁
共享锁
可以被多线程持有,ReadWriteLock读写锁就可以允许多线程持有
可以理解为乐观锁