多线程中常见锁概述

乐观锁与悲观锁

  乐观锁与悲观锁引自数据库中概念,下面解释一下其在Java中的意义

  悲观锁指对数据被外界修改持悲观态度,认为数据很容易被外界或者其他线程修改,所以每次在对数据进行处理前,都要对数据进行加锁,并在整个过程中,都不会释放锁。悲观锁常依靠数据库提供的锁机制实现,即在数据库中,对数据进行操作前,给数据加上排他锁。如果线程获取数据失败,说明数据被其他线程占有,当前线程则挂起或者抛出异常。如果获取成功,则在提交事务后释放排他锁。

  乐观锁与悲观锁正好相反,对数据被外界或其他线程修改持乐观态度,认为数据在一般情况下不会造成冲突,所以在访问前不会加上排他锁,只有在对数据进行提交更新时,才会对数据进行冲突检测。乐观锁不会使用数据库的锁机制,一般靠在表中添加version字段或者使用业务状态来实现。乐观锁直到提交时才锁定,所以不会产生死锁。

公平锁与非公平锁

  公平锁表示线程获取锁的顺序是按照线程请求锁的时间早晚排序的,即先来先得。非公平锁则在运行时闯入,先来不一定先得。

  实现方式:

    Java中的ReentrantLock提供了实现,默认实现为非公平锁

    公平锁:ReentrantLock lock = new ReentrantLock(true)

    非公平锁:ReentrantLock lock = new ReentrantLock(false/null)

  在没有公平性的需求下,尽量使用非公平锁,因为公平锁会带来性能开销。

独占锁与共享锁

  独占锁保证任何时候只有一个线程能得到锁,上面的ReentrantLock就是以独占的方式实现。共享锁则可以同时由多个线程持有。

  独占锁是一种悲观锁,每次访问资源都会加上互斥锁,限制并发。共享锁是一种乐观锁,允许多个线程同时进行操作。

可重入锁

  如果一个线程再次获取已经被它自己获取过的锁时不会被阻塞,则该锁是可重入的,即只要该线程获取了该锁,则可以无限次重复进入被该锁锁住的代码。

  synchronized内部锁就是可重入锁

  原理:在锁内部维护一个线程标示,标示该锁目前被哪个线程占用,然后关联一个计数器。为0时标示锁没有被任何线程占用。当一个线程获取了该锁时,计数器就会等于1,此时如果其他线程再来获取该锁,则会被阻塞。但是如果此时是持有该锁的线程再次获取该锁,则计数器会+1,释放一次该锁,计数器-1,直到计数器为零,该锁内部的线程标示会重置为null,直到这时候,请求该锁的其他线程才会被唤醒。

自旋锁

  Java中的线程与操作系统中的线程一一对应,所以当一个线程获取锁失败后,会被切换到内核态被挂起,而当该线程获取到锁时,又需要将其切换到用户态来唤醒线程,而用户态和内核态之间的切换对性能的开销时比较大的(涉及到上下文的保存与恢复等)。

  自旋锁则是在线程获取锁失败之后,不会马上将自己挂起,而是之后进行多次尝试(默认10次),可能该锁在之后的尝试时被其他线程释放,从而获得锁。如果在指定次数尝试之后仍没有获取锁,则该线程还是会被挂起。

  可以看出,自旋锁是用CPU时间换取线程阻塞与调度的开销,这些CPU时间有可能是被浪费掉的。

猜你喜欢

转载自www.cnblogs.com/dwwzone/p/12957157.html