Java对象锁

对象锁(monitor)
  机制是JDK 1.6 之前synchronized底层原理,又称为JDK 1.6重量级锁,
  线程的阻塞以及唤醒均需要由用户态切换到内核态,开销非常大,因此效率很低。
 
  Lock锁 - JDK 1.5(juc) - java语言层锁
  JDK 1.6之后对于内建锁的优化
 
1.CAS(Compare and Swap)
  • 悲观锁:线程获取锁(JDK 1.6之前内建锁)是一种悲观锁策略。假设每一次执行临届区代码(访问共享资源)都会产生冲突,所以当前线程获取1锁的同时也会阻塞其他未获取到锁的进程。
  • 乐观锁(CAS操作,无锁操作):假设所有线程访问共享资源时不会出现冲突,由于不会出现冲突自然就不会阻塞其他线程。因此线程就不会出现阻塞停顿状态。出现冲突时,无锁操作使用CAS(比较交换)来鉴别线程是否出现冲突,出现冲突就重试当前操作直到没有冲突为止。
 
1.1 CAS操作过程:
  CAS 可以理解为CAS(V,O,N): V:当前内存地址实际存放值   O:预期值(旧值)    N:更新的新值
 
  当V=O时,期望值与内存实际值相等,该值没有被任何其他线程修改过,即值O就是目前的最新值,因此可以将新值N赋值给V。
      反之,如果V!=O,表明该值已经被其他线程修改过了,因此O值并不是当前最新值,返回V,无法修改。
 
  当多个线程使用CAS操作时,只有一个线程会成功,其余线程均失败。失败的线程会重新尝试(自旋)或挂起线程(阻塞)。
 
  内建锁在老版本最大的问题在于:在存在线程竞争的情况下会出现线程的阻塞以及唤醒带来的性能问题,这是一种互斥同步(阻塞同步)。
  而CAS不是将线程挂起,当CAS失败后会进行一定的尝试操作并耗时将线程挂起,也叫做非阻塞同步。
 
1.2 CAS 的问题
 
  1.2.1 ABA问题
    解决:使用atomic ATomicStampedReference类来解决
    添加版本号   1A -> 2B -> 3A
 
  1.2.2 自旋会浪费大量CPU资源。
    与线程阻塞相比,自旋会浪费大量的处理器资源。因为当前线程仍处于运行状态,只不过跑的是无用指令。
    解决:自适应自旋(重量级锁的优化):根据以往自旋等待时能否获取锁,来动态调整自旋时间(循环次数)。如果自旋时获取到锁,则会稍微增加自旋时长;否则就会稍微减少下一次自旋时长。
 
  1.2.3 公平性
    内建锁无法实现公平机制,而Lock体系可以实现公平锁。
 
2.java对象头
 
  java对象头Mark Word 字段存放内容
  对象的Hashcode
  分代年龄
  锁标记位
 
  JDK 1.6 之后一共有四种状态的锁:
  无锁    0 01
  偏向锁    1 01 
  轻量级锁    00
  重量级锁    10
  根据竞争状态的激烈程度,锁会自动进行升级,锁不能降级(为了提高锁获取与释放的效率)。
 
3.偏向锁
  大多数情况下,锁不仅不存在多线程竞争,而且总是由一个线程多次获得。为了让线程获取锁的开销降低引入偏向锁。
  偏向锁是锁状态中最乐观的一种锁:从始至终只有一个线程请求一把锁。
 
  偏向锁的获取:当一个线程访问同步块并成功取得锁时,会在对象头和栈帧中的锁记录字段存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS来加锁和解锁,直接进入
 
  当线程访问同步块失败时,使用CAS竞争锁,并将偏向锁升级为轻量级锁。
 
  偏向锁头部Epoch字段值:表示此对象偏向锁的撤销次数 默认撤销40次以上,表示此对象不再适用于偏向锁,当下一次再次获取此对象锁时,直接变为轻量级锁。
 
  偏向锁只有一次CAS过程,出现在第一次加锁时。
 
  偏向锁的撤销:
  偏向锁使用了一种等待竞争出现才释放锁的机制,所以当其他线程竞争偏向锁时,持有偏向锁的线程才会释放偏向锁,并将锁膨胀为轻量级锁(持有偏向锁的线程依然存活的时候)。
 
  如果持有线程已经终止,则将锁对象的对象头设置成为无锁状态。
 
  JDK 6 之后偏向锁默认开启。
 
4.轻量级锁
  多个线程在不同的时间段请求同一把锁,也就是不存在锁竞争的情况,针对这种情况,JVM采用轻量级锁,来避免线程阻塞以及唤醒。
 
 
 
5,三种锁特点
  a,偏向锁只会在第一次请求锁时采用CAS操作并将锁对象的标记字段记录下来当前线程地址。在此后的运行过程中,持有偏向锁的线程必须加锁操作。
针对的是锁仅会被同一线程持有情况。
  b,轻量级锁采用CAS操作,将所对象标记字段替换为一个指针,只想当前线程栈上的一块空间,存储着锁对象原本的标记字段。
针对的是多个线程你在不同时间段申请同一把锁情况
 
  c,重量级锁会阻塞,唤醒请求加锁线程,针对的是多个线程同时竞争同一把锁的情况,JVM采用自适应自旋,来避免在面对非常小的同步代码块时,扔回被阻塞和唤醒 的情况。
6.其他优化
 
  锁粗化
  锁粗化就是将多次链接在一起的加锁、解锁操作合并为一次操作。将多个联系的锁扩展为一个范围更大的锁。
 
  锁解除
  删除不必要的加锁操作。根据代码逃逸技术,如果判断一段代码中,堆上的数据不会逃逸当前线程,则认为此代码是线程安全的无需加锁。
 
7.Java.util.lock
 
  7.1 lock简介
 
 Lock lock = new ReentrantLock();
  try{
  lock.lock();
  //以下代码只有一个线程可进行
  ...
  }
  finally{
      lock.unlock();
  }

 7.2 lock常用API

    lock 体系拥有可中断的获取锁以及超时获取锁以及共享锁等内建锁不具备的特性。
    void lock();//获取锁
    void lockInterruptibly( )throws InterruptedException();//响应中断锁
    boolean tryLock();//获取锁返回true,反之返回false
    boolean tryLock(long time, TimeUnit unit);//超时获取锁,在规定时间内未获取到锁返回false
    Condition newCondition();//获取与Lock绑定的等待通知组件
    void unlock();//释放锁
    ReentrantLock中所有的方法实际上都是调用了其静态内部类Sync中的方法,而Sync继承了AbstQueuedSynchronizer(AQS——简称同步器)
 
7.3 AQS——同步器
 
    同步器是用来构建锁及其他的同步组件的基础框架,它的实现主要依赖一个int状态变量以及通过一个FIFO队列共同构成的同步队列。
    子类必须重写AQS的用proteced修饰的用来改变同步状态的方法,其他方法主要是实现了排队与阻塞机制,int状态的更新使用getState()、setStact()、以及compareAndSetState()。
    子类推荐使用静态内部类来继承AQS实现自己的同步语义。同步器及支持独占锁,也支持共享锁。
7.4 AQS的模板模式
  AQS使用模板方法模式,将一些与状态相关的核心方法开发给子类重写,而后AQS会使用子类重写的关于状态的方法进行线程排队、等待唤醒操作
 
  锁与AQS关系
    锁面对使用者,定义了使用者与锁交互接口
    同步器面向锁的实现者,简化了锁的实现方式屏蔽了同步状态的管理、线程排队、线程等待唤醒等底层操作。
 
7.5 AQS详解
  在同步组件中,AQS是最核心的部分,同步组件的实现依赖AQS提供的模板方法来实现同步组件语义。
  AQS实现了对同步状态管理,以及对阻塞线程进行排队,等待通知等底层实现
 
  AQS核心组成:同步队列,独占锁的获取与释放。共享锁的获取与释放、可中断锁、超时锁。这一系列功能的实现依赖AQS所实现的功能方法
 
 
独占锁:
  1.void acquire(int arg)
  独占式获取同步状态,如果获取失败插入同步队列进行等待。
  2.void acquireInteruptibly(int arg)
  在1的基础上,此方法可以在同步队列中响应中断
  3.boolean tryAcquirNanos(int arg,long nanosTimeOut)
  在2的基础上增加超时等待功能,到了预计时间还没获得锁直接返回
  4.boolean tryAcquire(int arg)
  获取锁成功返回true 否则返回false
  5.boolean release (int arg)
  释放同步状态,该方法会唤醒在同步队列的下一个结点
 
共享式锁
  1.void acquireShared(int arg )
  共享获取同步状态,同一时刻多个线程获取同步状态
  2.void acquireShareInterruptibly(int arg)在1的基础上增加响应中断
  3.boolean tryAcquirNanos(int arg,long nanosTimeOut)
  在2的基础上增加超时等待功能,
  4.boolean releaseShared (int arg)共享式释放同步状态
 
同步队列
  在AQS内部有一个静态内部类Node,这是同步对列每个具体的结点
  节点有如下属性:
  int waitStatus:结点状态
 
  Node prev :同步对列中的前驱结点
  Node next:
  Thread thread:当前结点包装线程对象
  Node nextWaiter:等待队列中下一个结点
 
 
结点状态值如下:
  int INITIAL = 0;  初始状态
  int CANCELLED =1;  当前结点从同步队列中取消
  int SIGNAL = -1; 后继结点处于等待状态,如果当前结点释放同步状态后会通知后继结点,使后继结点继续运行
   int CONITION = -2  结点处于等待队列中。当其他线程对Condition调用signal()方法后,该节点从等待队列进同步队列
   int PROPAGATE = -3 共享式同步状态会无条件传播
   AQS 同步队列采用带头结点的双向链表
 
原文地址:https://www.cnblogs.com/yishengPan/p/10550207.html