锁
锁的种类
线程要不要锁住同步资源?
- 悲观锁:认为自己在使用数据时的时候,一定有其他线程来修改数据。对资源加锁。 synchronized、Lock 的实现类都是悲观锁。
- 适合写操作多的场景
- 乐观锁:通过 CAS 实现的的,无锁编程。
- 适合读操作多的场景
- 可以采用 版本号 + CAS 来实现。
锁住同步资源失败,线程要不要阻塞?
- 自旋锁
- 适应性自旋锁
多个线程竞争同步资源的流程细节有没有区别?(synchronized)
- 无锁:不锁住资源,多个线程中只有一个能修改资源成功,其他线程会重试。 (CAS)
- 偏向锁:同一个线程执行同步 资源时自动获取资源
- 偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价。
- 轻量级锁:多个线程竞争同步资源时,没有获取资源的线程自旋等待锁释放
- 当锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。
- 重量级锁:多个线程竞争同步资源时,没有获取资源的线程阻塞等待唤醒
- 若当前只有一个等待线程,则该线程通过自旋进行等待。但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁升级为重量级锁。
多个线程竞争锁时要不要排队?
- 公平锁:多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁。
- 非公平锁:先尝试插队,插队失败再排队
- 多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待。
一个线程中的多个流程能不能获取同一把锁?
- 可重入锁
- 在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class)
- 非可重入锁
多个线程能不能共享一把锁?
- 共享锁:该锁可被多个线程所持有。如果线程T对数据A加上共享锁后,则其他线程只能对A再加共享锁,不能加排它锁。获得共享锁的线程只能读数据,不能修改数据。
- 排他锁:该锁一次只能被一个线程所持有。
锁使用的情景
在什么时候会用到自旋锁呢?
如果同步代码块的内容简单,此时阻塞和唤醒一个线程的耗费时间可能比执行同步代码块的时间还要长。此场景下适合使用自旋锁。
某线程尝试获取同步资源的锁失败时,不会立即阻塞,而是会自旋等待,等待一段时间后再尝试获取锁。这样可以减少线程阻塞的时间,提高性能。 因为阻塞或唤醒线程的耗时要比等待资源释放的时间长,所以在资源竞争不激烈的情况下,自旋等待是一种更好的选择。 自旋锁的缺点:如果锁被占用的时间很长,自旋等待只会浪费 CPU 资源。
适应性自旋锁:自旋的时间(次数)不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源。
死锁
- 多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
死锁必须具备以下四个条件:
- 互斥:该资源任意一个时刻只由一个线程占用。
- 请求与保持:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不可剥夺:线程已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
- 循环等待:若干进程之间形成一种头尾相接的循环等待资源关系。
如何避免线程死锁?
只要破坏产生死锁的四个条件中的其中一个就可以了
- 破坏互斥条件 这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)
- 破坏请求与保持条件
- 一次性申请所有的资源。
- 先申请必需的资源,在申请新的资源之前,释放已持有的资源
- 破坏不剥夺条件
- 占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
- 破坏循环等待条件 靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。
- 锁排序法:(必须回答出来的点) 指定获取锁的顺序,比如某个线程只有获得A锁和B锁,才能对某资源进行操作,在多线程条件下,如何避免死锁? 通过指定锁的获取顺序,比如规定,只有获得A锁的线程才有资格获取B锁,按顺序获取锁就可以避免死锁。这通常被认为是解决死锁很好的一种方法。
- 使用显式锁中的ReentrantLock.try(long,TimeUnit)来申请锁