Java锁学习(一)

本文详细探讨了Java中的锁机制,包括synchronized关键字的使用、CAS(CompareandSwap)概念及其应用、公平锁与非公平锁的区别,以及锁优化策略,如偏向锁、轻量级锁和重量级锁的转换。
摘要由CSDN通过智能技术生成

Java锁学习

[参考资料-B站课程]

一 基本锁类型

1.1 synchronized关键字

  1. 使用位置
    • 可在方法上、代码块上
  2. 所属类型
    • 互斥锁
    • 悲观锁
    • 重量级锁(在JDK1.6版本之前,因为涉及到其他线程被阻塞、线程上线文切换、线程的唤醒。所以OS的线程阻塞恢复的调度成本较高)
  3. 其他名称
    • 同步锁
  4. 使用原理
    • JDK1.6 之前版本,存在线程A B C ,若A已经获得锁,则B C 就会被加入到一个同步队列中阻塞等待(JDK 1.6版本之前是这个原理)

1.2 CAS 的概念

  1. CAS 比较并交换
    • java的实现:compareAndSet ,compareAndSwap
    • 又称为:无锁、乐观锁、自旋锁、轻量级锁
// CAS 实现方式之一, 自旋重试
public class AtomicInteger02 {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger();
        while (true) {
            int oldValue = atomicInteger.get();
            int newValue = oldValue + 1;
            if (atomicInteger.compareAndSet(oldValue, newValue)) {
                break;
            }
        }
    }
    
}
  1. 特点

    • 可见 没有线程发生阻塞,只是一直在循环重试,
  2. CAS的问题

    • 原子性问题
   compareAndSet:
   底层至少应该: 
   a.compare
   b. set值
   假如在步骤a 和b之间,有另一个线程执行了更新至的操作,则就会出问题了

在这里插入图片描述
该截图为X86平台下实现原子性比较并更新至的源码;
可见该底层是通过两条汇编指令:lock 和cmpxchgl实现的,“lock 和cmpxchgl”二者联合起来就是一条原子命令,不会被其他线程打断。 所以无锁的原子操作在底层的硬件级别(汇编指令)还是有lock的,只是成本低了。

  • ABA问题(ABA 现象)
    • 线程1:将数值从A 改成B ,由改成了A;
    • 线程2:CAS时认为值没有改变,但是值本身已经改变了
    • 总结:这是一种现象,不一定会引发问题,看具体情况而定;
    • 解决方式:添加版本号,任何操作均增加版本号,设置标志位;
  • 注意:轻量级锁不一定比重量级锁性能高。因为若存在大量线程循环CAS处理,高度并发,CAS一直无法处理成功,相当于弯路走的过多反而不如阻塞等待执行的效率高了。

1.3 锁优化

  1. synchronized 在1.6版本之后做了很多的优化
    • 锁具有了状态的逐渐变化过程
      (1). 无状态
      (2). 偏向锁:偏向锁启用,一个线程加锁,将线程ID写入对象头的markword;
      (3). 状态流转示意图
      在这里插入图片描述
  2. 场景1,虽然加了synchronized,但是大部分场景下只有一个线程来请求,存在资源争抢的情况占比较少。所以JDK1.6之后引入了偏向锁,即不使用复杂的底层机制。只将线程ID存到加锁对象内部暂存,即每次有方法调用时,只需检查存入的线程ID和当前请求调用的线程ID是否一致即可,若是同一个线程ID,则直接可调用方法; 若线程ID不一致,则锁升级;
  3. 偏向锁存在时应对大多数只有一个线程的情况,若出现了多个线程来请求执行同步方法,则升级到轻量级锁,轻量级锁的底层是通过自旋来实现的。但是自旋是很消耗CPU的,若自旋次数过多还不能获取到锁,则升级到重量级锁,会导致线程苏泽。
  4. 场景2:轻量级—> 升级到重量级锁。 弯路过度曲折则导致自旋处理耗时过长,或者永远无法成功了。即大量线程来加锁时,导致的大量的CPU进行while 空转自旋。还不如将多余的线程进入阻塞队列,直接切换到重量级锁。
  5. 总结:synchronized锁的优化就是锁根据情况自适应升级膨胀的过程。

1.4 锁优化升级二

  1. JAVA 对象的结构
    - HostSpot虚拟机中,对象在内存中存储布局分为3块:
    • 对象头 Header
    • 实例数据 Instance Data
    • 对齐填充 Padding
      在这里插入图片描述
  2. 在字节码层面,有monitorenter,monitorexit两个 方法;
  3. LongAdder 实现了分段CAS优化
    • 实现一个线程一个初始值,最终再合并最终结果数值
    • 最终返回结果时将所有值的结果进行汇总。
      在这里插入图片描述

1.3 公平锁与非公平锁

  1. 代码示例
 new Thread(() -> {
            reentrantLock.lock();
            try {
                drayMoney();
            } finally {
                reentrantLock.unlock();
            }
        }, "T1").start();
## 解释
reentrantLock.lock(); // 尝试获取锁,会使得线程阻塞尝试获取锁;
a. 让线程阻塞的方式:wait(), sleep(),park(),while(true)
b. 

  1. LockSupport.park()
  2. ReentrantLock源码
ReentrantLock reentrantLock = new ReentrantLock();
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        // 若线程加锁未成功,则进行排队并且将该排队线程进行park()处理,
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
// tryAcquire(arg) 表示尝试加锁成功, 即只有尝试加锁失败才执行后续逻辑
// 先看 FairSync 的实现
static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

final void lock() {
     acquire(1);
 }

/**
 公平锁的源码
*/
     protected final boolean tryAcquire(int acquires) {
         final Thread current = Thread.currentThread();
         int c = getState();
         // 当前锁是空闲状态,则执行该逻辑
         if (c == 0) {
             // 锁是空闲状态,且当前没有排队的等待线程;即查看是否已有人在排队
             if (!hasQueuedPredecessors() &&
                 compareAndSetState(0, acquires)) {
                 setExclusiveOwnerThread(current);
                 return true;
             }
         }
         // 若state不为0,则判断已获得锁的线程和当前线程是否相同;即该处就是重入的关键逻辑
         else if (current == getExclusiveOwnerThread()) {
             int nextc = c + acquires;
             if (nextc < 0)
                 throw new Error("Maximum lock count exceeded");
             // 若同一个线程 两次获取了锁,则将state值+1;    
             setState(nextc);
             return true;
         }
         // 返回false表示没有加上锁
         return false;
     }
 }
 }

在这里插入图片描述
4. 执行优先级 vs 提交优先级

  • 提交优先级: 核心线程,存入队列,非核心线程
  • 执行优先级:核心线程执行,非核心线程执行任务,去队列中执行任务;
  1. execute方法执行内部的逻辑为:
    在这里插入图片描述
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值