Java中的锁

Java中的锁

乐观锁和悲观锁

乐观锁预测锁冲突的概率不高,认为共享资源每次被访问的时候不会出现问题,线程可以不停地执行,无需加锁也无需等待,只是在提交修改的时候去验证对应的资源(也就是数据)是否被其它线程修改了(具体方法可以使用版本号机制或 CAS 算法)。

悲观锁预测锁冲突的概率较高,认为共享资源每次被访问的时候就会出现问题(比如共享数据被修改),所以每次在获取资源操作的时候都会上锁,这样其他线程想拿到这个资源就会阻塞直到锁被上一个持有者释放。也就是说,共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程

悲观锁通常多用于写比较多的情况下(多写场景,竞争激烈),这样可以避免频繁失败和重试影响性能,悲观锁的开销是固定的。不过,如果乐观锁解决了频繁失败和重试这个问题的话(比如LongAdder),也是可以考虑使用乐观锁的,要视实际情况而定。
乐观锁通常多于写比较少的情况下(多读场景,竞争较少),这样可以避免频繁加锁影响性能。不过,乐观锁主要针对的对象是单个共享变量(参考java.util.concurrent.atomic包下面的原子变量类)。

自旋锁和挂起等待锁

自旋锁

所谓自旋锁,就是当获取锁失败的时候,不会挂起等待,而是不断询问锁是否被释放,直到拿到锁

当任务普遍运行时间较短时适合使用,因为没必要把线程放到等待队列,再唤醒,这些操作都会消耗资源

但如果任务普遍耗时长,那么不断询问势必会大量消耗CPU资源

挂起等待锁

很好理解,就是获取锁失败,就会挂起等待,即加入到等待队列等待,当锁被释放,会被唤醒,加入到锁的竞争

公平锁和非公平锁

公平锁

公平锁的公平在于多个线程访问资源对象时,会按照请求顺序将线程排队,并按照队列顺序,以此获得锁的访问权,避免了线程饥饿

适用于对锁请求频繁分配的情况,如线程池和多个线程共享某个资源的情况 但是, 公平锁可能导致线程间切换的过多,因此也会带来额外的性能开销。

非公平锁

无视请求顺序,无序地分配锁的使用权 即使一个线程已经持有锁,其他线程也可以请求并获得该锁。

这种锁的主要优势在于可以提高系统的吞吐量,因为它可以避免很多线程切换的产生,从而减少CPU上下文切换的开销。

非公平锁的使用场景:非公平锁适合用于处理资源访问速度较快的场景,同时对资源竞争较小时也更加适合使用非公平锁。它可以提高系统的吞吐量,并且可以避免一些线程切换的产生。但是, 非公平锁可能导致某些线程长时间地被饿死,无法获取到锁访问权的情况。

可重入锁和不可重入锁

可重入锁和不可重入锁是两种不同类型的锁,它们的主要区别在于同一个线程是否可以多次获得该锁。

  1. 不可重入锁: 不可重入锁也称为独占锁,一旦有一个线程获得了该锁,其他线程就无法获得该锁。如果同一个线程再次请求该锁,它将被阻塞住,直到该锁被释放。

不可重入锁的使用场景:不可重入锁适合用于对某个共享资源进行独占式访问的情况,例如文件、数据库等。因为这些资源只能被一个线程访问, 否则会出现数据的不一致性问题。

2.可重入锁: 可重入锁也叫递归锁,允许同一个线程多次获得该锁,而且每次加锁必须要配对地释放锁才能被其他线程获取。当同一个线程请求同一个锁时,只有第一次请求需要获得锁,后续的请求只需要增加锁计数器即可。当锁计数器为0时,锁被完全释放。

可重入锁的使用场景:可重入锁适用于需要对某个共享资源进行多层级访问的情况,例如多个方法之间互相调用,并且这些方法都需要访问某个共享资源。

需要注意的是,在使用可重入锁时,必须保证每次加锁和释放锁的数量相等,否则可能会导致死锁或者其它不可预测的行为。

你可能感兴趣的:(java)