线程中的各种锁概念(死锁,共享锁,公平锁,乐观锁等)

什么是线程死锁?

线程死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象。若无外力作用,它们都将无法推进下去。

线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方持有的资源,所以这两个线程就会互相等待而进入死锁状态。

产生死锁的四个必要条件是什么?

  1. 互斥资源必须处于非共享模式,即一次只有一个进程可以使用。如果另一进程申请该资源,那么必须等待直到该资源被释放为止。
  2. 占有并等待:一个进程至少应该占有一个资源,并等待另一资源,而该资源被其他进程所占有。
  3. 非抢占资源不能被抢占。只能在持有资源的进程完成任务后,该资源才会被释放。
  4. 循环等待:有一组等待进程 {P0, P1,..., Pn}P0 等待的资源被 P1 占有,P1 等待的资源被 P2 占有,Pn 等待的资源被 P0 占有。

解决死锁的方法

互斥条件:使得资源是可以同时访问的,但是有很多资源是不能同时访问的 ,所以这种做法在大多数的情况下不能使用

占有并等待条件:静态分配策略可以破坏该条件。静态分配策略,指一个进程必须在执行前就申请到它所需要的全部资源,并且知道它所要的资源都得到满足之后才开始执行。进程要么占有所有的资源然后开始执行,要么不占有资源,不会出现占有一些资源等待一些资源的情况

破坏第三个条件 非抢占:也就是说可以采用 剥夺式调度算法让资源变得可剥夺

层次分配策略破坏了产生死锁的第四个条件(循环等待)。在层次分配策略下,所有的资源被分成了多个层次,一个进程得到某一次的一个资源后,它只能再申请较高一层的资源

公平锁与非公平锁

公平锁:每个线程获取锁的顺序是按照线程访问锁的先后顺序获取的,最前面的线程总是最先获取到锁。
非公平锁:每个线程能否获取锁是随机的,并不会遵循先来先得的规则,所有线程会竞争获取锁。

共享锁与独占锁

独占锁也叫排他锁,是指该锁一次只能被一个线程所持有。如果一个数据A被加上排他锁,则不能再对A加任何类型的锁。获得排它锁的线程即能读数据又能修改数据

共享锁是指该锁可被多个线程所持有。如果一个数据A被加上共享锁,则只能再对A共享锁,不能加排他锁。获得共享锁的线程只能读数据,不能修改数据

悲观锁与乐观锁

悲观锁总是假设最坏的情况,认为共享资源每次被访问的时候就会出现问题,所以每次在获取资源操作的时候都会上锁。其他线程想拿到这个资源就会被阻塞直到锁被上一个持有者释放。也就是说,共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程
乐观锁总是假设最好的情况,认为共享资源每次被访问的时候不会出现问题,只会在操作完成后提交数据的时候去验证对应的数据是否有被其它线程修改
高并发的场景下,乐观锁相比悲观锁来说,不存在锁竞争造成线程阻塞,也不会有死锁的问题,在性能上往往会更胜一筹。但是,如果冲突频繁发生(写占比非常多的情况),会频繁失败和重试,这样同样会非常影响性能,导致 CPU 飙升。
适用场景:
悲观锁适合写操作多的场景。
乐观锁适合读操作多的场景,不加锁可以提升读操作的性能。

如何实现乐观锁?

乐观锁一般会使用版本号机制CAS 算法实现,CAS更为常用一些。

版本号机制

一般是在数据表中加上一个数据版本号 version 字段,表示数据被修改的次数。当数据被修改时,version 值会加一。当线程 A 要更新数据值时,在读取数据的同时也会读取 version 值,在提交更新时,若刚才读取到的 version 值为当前数据库中的 version 值相等时才更新,否则重试更新操作,直到更新成功。

什么是CAS?

CAS全称Compare And Swap,比较与交换,是乐观锁的主要实现方式。CAS在不使用锁的情况下实现多线程之间的变量同步
CAS算法涉及到三个操作数:
需要读写的内存值V。
进行比较的值A。
要写入的新值B。
只有当V的值等于A时,才会使用原子方式用新值B来更新V的值,否则会继续重试直到成功更新值。
一个简单的例子:线程 A 要修改变量 i 的值为 6,i 原值为 1(V = 1,E=1,N=6,假设不存在 ABA 问题)。

  1. i 与 1 进行比较,如果相等, 则说明没被其他线程修改,可以被设置为 6 。
  2. i 与 1 进行比较,如果不相等,则说明被其他线程修改,当前线程放弃更新,CAS 操作失败。

CAS存在的问题?

CAS 三大问题:

  1. ABA问题。CAS需要在操作值的时候检查内存值是否发生变化,没有发生变化才会更新内存值。但是如果内存值原来是A,后来变成了B,然后又变成了A,那么CAS进行检查时会认为值没有发生变化,但是实际上是有变化的。ABA问题的解决思路就是在变量前面添加版本号(version),每次变量更新的时候都把版本号加一,这样变化过程就从A-B-A变成了1A-2B-3A。

  2. 循环时间长开销大。CAS操作如果长时间不成功,会导致其一直自旋,给CPU带来非常大的开销。

  3. 只能保证一个共享变量的原子操作。对一个共享变量执行操作时,CAS能够保证原子操作,但是对多个共享变量操作时,CAS是无法保证操作的原子性的。

你可能感兴趣的:(java)