锁的概念

一、为什么要用锁?

锁-是为了解决并发操作引起的脏读、数据不一致的问题。

二、锁实现的基本原理

1、volatile

java允许线程访问共享变量, 为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量。volatile在多处理器开发中保证了共享变量的“ 可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。

结论:如果volatile变量修饰符使用恰当的话,它比synchronized的使用和执行成本更低,因为它不会引起线程上下文的切换和调度。

2、synchronized

synchronized通过锁机制实现同步,当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。具体表现为以下3种形式。

  • 对于普通同步方法,锁是当前实例对象。
  • 对于静态同步方法,锁是当前类的Class对象。
  • 对于同步方法块,锁是Synchonized括号里配置的对象。

synchronized实现的锁是可重入锁,当前线程可以可以再次获得锁。于是可细分为:

对象锁:使用 synchronized 修饰非静态的方法以及 synchronized(this) 同步代码块使用的锁是对象锁。

类锁:使用 synchronized 修饰静态的方法以及 synchronized(class) 同步代码块使用的锁是类锁。

私有锁:在类内部声明一个私有属性如private Object lock,在需要加锁的同步块使用 synchronized(lock)

它们的特性:

  • 对象锁具有可重入性。
  • 当一个线程获得了某个对象的对象锁,则该线程仍然可以调用其他任何需要该对象锁的 synchronized 方法或 synchronized(this) 同步代码块。
  • 当一个线程访问某个对象的一个 synchronized(this) 同步代码块时,其他线程对该对象中所有其它 synchronized(this) 同步代码块的访问将被阻塞,因为访问的是同一个对象锁。
  • 每个类只有一个类锁,但是类可以实例化成对象,因此每一个对象对应一个对象锁。
  • 类锁和对象锁不会产生竞争。
  • 私有锁和对象锁也不会产生竞争。
  • 使用私有锁可以减小锁的细粒度,减少由锁产生的开销。

3、ReentrantLock

ReentrantLock 是一个独占/排他锁。相对于 synchronized,它更加灵活。但是需要自己写出加锁和解锁的过程。它的灵活性在于它拥有很多特性。

ReentrantLock 需要显示地进行释放锁。特别是在程序异常时,synchronized 会自动释放锁,而 ReentrantLock 并不会自动释放锁,所以必须在 finally 中进行释放锁。

它的特性:

  • 公平性:支持公平锁和非公平锁。默认使用了非公平锁。
  • 可重入.
  • 可中断:相对于 synchronized,它是可中断的锁,能够对中断作出响应。
  • 超时机制:超时后不能获得锁,因此不会造成死锁。

ReentrantLock 是很多类的基础,例如 ConcurrentHashMap 内部使用的 Segment 就是继承 ReentrantLock,CopyOnWriteArrayList 也使用了 ReentrantLock。比如子类 ReentrantReadWriteLock,继承并实现了锁降级。(先获取写锁,再获取读锁,然后再释放写锁的过程。锁降级是为了保证数据的可见性)

4、CAS

上面提到的 ReentrantLock、ReentrantReadWriteLock 都是基于 AbstractQueuedSynchronizer (AQS),而 AQS 又是基于 CAS。CAS 的全称是 Compare And Swap(比较与交换),它是一种无锁算法。

synchronized、Lock 都采用了悲观锁的机制,而 CAS 是一种乐观锁的实现。

CAS 的特性:

  • 通过调用 JNI 的代码实现
  • 非阻塞算法
  • 非独占锁

CAS 存在的问题:

  • ABA
  • 循环时间长开销大
  • 只能保证一个共享变量的原子操作

Condition

Condition 用于替代传统的 Object 的 wait()、notify() 实现线程间的协作。Condition 必须要配合 Lock 一起使用,一个 Condition 的实例必须与一个 Lock 绑定。

Semaphore

Semaphore、CountDownLatch、CyclicBarrier 都是并发工具类。
Semaphore 可以指定多个线程同时访问某个资源,而 synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源。由于 Semaphore 适用于限制访问某些资源的线程数目,因此可以使用它来做限流。特别注意,Semaphore 并不会实现数据的同步,数据的同步还是需要使用 synchronized、Lock 等实现。

CountDownLatch

CountDownLatch 可以看成是一个倒计数器,它允许一个或多个线程等待其他线程完成操作。因此,CountDownLatch 是共享锁。
CountDownLatch 的 countDown() 方法将计数器减1,await() 方法会阻塞当前线程直到计数器变为0。

文章作者: gqsu
文章链接: http://www.ipdax.com/2018/09/17/study-锁的概念/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 技术笔记分享
支付宝打赏
微信打赏