Java 锁介绍

JVM中的锁

        每个对象都有一个对象头mark word标志信息,主要存储:对象hashcode值、gc年龄、偏向锁标识、锁标识等

        在使用synchronized关键字加锁时,根据竞争情况,会出现锁的一个升级过程。

        偏向锁(无竞争单线程时) -> 轻量级锁(多个线程竞争低并发) -> 重量级锁(高并发)

        未获取轻量级锁的其他线程可以进行短暂的自旋等待锁释放,如果短暂自旋以后未能获取到会升级为重量级锁。

     偏向锁

            偏向锁是一个线程获取锁以后设置对象头偏向锁id指向该线程,当该线程再次获取该锁, 不需要做monitor等判断, 只需要通过cas判断判断对象头偏向锁字段存储的线程id是否是本线程id,如果是则可以直接获取, 性能较高.
            偏向锁使用只在资源不存在或者存在十分小的竞争情况下使用,能有效提高性能,
            在资源竞争激烈(多线程)的情况下, 每次线程都试图获取偏向锁,但是每次都获取不到, 所以性能下降.
            偏向锁在jdk1.6开始默认启用, 参数为:-XX:+UseBiasedLocking  -XX:BiasedLockingStartupDelay=n    为偏向锁启用时间, 默认会在jvm启动几秒后启用, jvm团队认为 jvm刚启动时, 资源竞争高,所以会在后续几秒后启用.

            设计使用场景:单线程使用同步块代码时进一步提高性能。相比轻量级锁执行多次cas操作,偏向锁只执行一次cas操作对象头的偏向锁id(出现竞争撤销时,执行一次撤销操作)

        轻量级锁(BasicObjectLock)

            嵌入在线程栈中的对象BasicObjectLock有(BasicLock)对象头和指向持有该锁的对象指针组成.
            普通的锁性能不理想, 轻量级锁是一种快速锁定方法.
            使用轻量级锁, 在判断是否一个线程持有对象锁, 只需要通过cas操作判断对象头字段的轻量级锁指针是否指向该线程栈即可. 与偏向锁的区别是在无竞争情况下偏向锁只需执行一次cas操作置换偏向锁threadId(出现竞争时还有一次撤销操作),轻量级锁需要多次cas锁定与释放。
            
            轻量级锁失败,表示存在竞争, 升级为重量级锁(常规锁)
            在没有锁竞争情况下, 减少传统互斥量产生的性能消耗
            在激烈竞争时, 轻量级锁会多做额外操作, 导致性能下降

            设计使用场景:多线程交替执行同步代码块(同步代码块执行时间不能太长)时提高性能,当存在同时使用时就会升级为重量级锁。

           轻量级锁获取失败时会根据情况进行自旋,这个过程就是自旋锁

            在竞争存在时, 如果线程可以很快获得锁, 可以不在OS(操作系统)层挂起线程(性能损耗高), 可以多几个空操作,这就叫自旋。比如当获取轻量级锁失败时,会进行短暂的自旋,如果还是获取不到就升级为重量解锁。
            自旋锁jdk1.6 -XX:+UseSpinning    开启自旋锁, jdk 1.7中默认内置实现了自旋锁, 参数废弃.
            如果同步块很长,自选失败, 会降低系统性能
            如果同步块很短, 自旋成功, 节省挂起时的切换时间,提升性能

            设计应用场景:当同步代码块执行迅速时,等待锁的线程自旋一段短暂的时间就可以获取到锁,可以避免系统上下文切换的消耗,不过自旋需要占用一些cpu时间。
            
        偏向锁/轻量级锁/自旋锁不是java语言层锁优化方法,是JVM中的锁的优化方法基本步骤如下:
               1. 偏向锁可用会先尝试偏向锁
               2. 轻量级锁可用会先尝试轻量级锁
               3. 以上失败,会尝试自旋锁
               4. 再失败, 尝试普通锁, 使用OS互斥量在操作系统层线程挂起

Java语言层锁优化

        1. 减少锁持有时间(尽量再最小的区域内使用锁(synchronized)).

                就是只对需要同步块那部分代码使用同步块, 不必要代码不要放到同步块中

        2. 减小锁的粒度

                将需要锁定的大对象拆成小对象, 增加并行度, 较少锁竞争, 这样以来就可以增加偏向锁和轻量级锁的成功率
                该种实现例子就是ConcurrentHashMap
                HashMap实现同步可以通过Collections.synchronizedMap(map)获得一个同步SynchronizedMap,
                SynchronizedMap的方法进行了同步块实现
                ConcurrentHashMap的实现就是减小锁粒度
                        将内容分为若个segment片, 每个segment, 存储的k-v具体值.
                        在put操作时,先锁定某个segment块在进行put操作, 而不是锁定整个map.
                        减小锁粒度以后, ConcurrentHashMap就可以允许多个线程同时进入操作,只要多个线程
                        操作的是不同的segment.(segment锁实现在jdk1.7及之前,1.8之后使用cas原语重新实现了)

        3.锁分离

                根据功能分离锁
                如读写锁ReadWriteLock,在读多写少情况时, 能提高性能.
                允许多读,但是只能一个写, 只要有一个线程获取读锁,其他读线程可读.写线程等待
                在有一个线程写时, 其他读线程和写线程等待.               

                LinkedBlockingQueue  链表队列 头take,尾put, 头尾操作互不影响,

                可以一个线程put,一个take, 也是一种锁分离的应用

        4. 锁粗化

                通常情况下, 保证并发多线程有效, 要求每个线程持有锁的时间尽量少, 以便尽早释放锁, 其他线程得以获得.
                但是,如果对同一个锁频繁请求和释放过多也会导致资源消耗, 此时需要对锁进行粗化.
                比如: 下面的锁需要粗化, 放大同步块区域(synchronized时可重入锁)
                    public void method() {
                        synchronized(lock) {do ..}
                        synchronized(lock) {do ..}
                    }
                    public void method() {
                        for (x...) {
                            synchronized(lock) {do ..}
                        }
                    }

        5. 锁消除

                方法内不可能被多线程使用的变量, 但是出现了加锁, 此时编译器发现对象不可能被共享, 将会消除这些锁操作.
                例如: StringBuffer 是个线程安全的方法都有同步块, 但是此处不需要加锁, 编译器编译时会去掉锁操作
                    public String method(String s) {
                        StringBuffer sb = new StringBuffer();
                        sb.append(s);
                        return sb.toString();
                    }
                锁消除的JVM参数

                -XX:+DoEscapeAnalysis    逃逸分析, 锁对象是否逃逸(一个对象如果在方法内创建被使用, 但是此后方法外也有使用, 被称为逃逸)

                -XX:+EliminateLocks        启用锁消除 可以对上面方法循环2000000次计算耗时

        6.无锁

                锁是悲观操作, 悲观操作认为操作是多线程不安全的,存在竞争 ,所以使用锁
                无锁就是乐观操作, 乐观操作认为所有操作都是安全的不存在竞争关系
                无锁的实现方式      :CAS

CAS(Compare And Swap) 比较交换, 进来就比较并交换值, 不等待, 成功继续执行,否则重试或者放弃.
CAS有三个参数V(variable), E(expected), N(new),分别是V需要操作的变量, E期望变量的值, N操作后的新值, 
交换过程为变量V的值与预期值E相等, 则将变量V赋值为N新值. if (v == e) v = n;
CAS是cpu的一条指令, 能保证线程安全, 该种性能远远高于锁机制。通过该方式应用在具体实现中, 判断是否多线程干扰, 如果有就重试.
(j.u.c包)java.util.concurrent.atomic包内存在很多原子操作的对象就是采用CAS如:AtomicInteger

参考:   对象头-锁

<think>好的,我现在需要回答用户关于Java中Lock接口的介绍使用方法,包括教程示例代码。首先,我得回忆一下自己对Java并发编程的理解,特别是Lock接口的相关知识。 用户提到了几个引用,其中引用[3]详细说明了Lock接口的主要方法使用示例,这可能是一个重要的参考。根据引用[3],Lock接口提供了比synchronized更灵活的机制,主要实现类是ReentrantLock。需要介绍Lock接口的主要方法,比如lock(), unlock(), tryLock()等,并且给出示例代码。 接下来,我需要考虑用户的需求:他们可能希望了解Lock接口的基本概念、如何正确使用它,以及具体的代码示例。还需要注意系统级指令中的要求,比如中文回答、生成相关问题、引用标记等。 首先,我应该先介绍Lock接口的基本概念,对比synchronized关键字的区别,这样用户能明白为什么需要Lock接口。然后,分点讲解主要方法,比如lock()unlock()的使用,确保在finally块中释放,避免死。接着是tryLock()方法,说明其非阻塞特性,以及带超时的tryLock(long, TimeUnit)方法的应用场景。此外,Condition的使用也是一个重要部分,需要提到如何通过newCondition()方法创建条件变量,用于线程间的协调。 在代码示例方面,引用[1]给出了一个ReentrantLock的简单例子,可以借鉴这个结构。需要确保代码正确,比如在increment方法中获取,并在finally块中释放。还可以补充其他方法的示例,比如tryLock()的使用场景,比如避免死的情况。 另外,用户提供的引用[2]提到Lock接口的常见实现包括ReentrantLockReadWriteLock,可能需要在相关内容中简要提及,但用户主要问的是Lock接口,所以重点放在ReentrantLock上。 需要注意系统指令中的格式要求:行内数学公式用$...$,但这里可能用不上。独立公式可能需要使用$$...$$,但Java代码示例应该用代码块包裹,并正确标注语言类型,如python或java。 生成的相关问题需要与Lock接口相关,可能包括性能比较、最佳实践、与synchronized的区别、ReadWriteLock的使用等。需要确保问题有针对性,能帮助用户进一步学习。 最后,确保回答结构清晰,分步骤讲解,引用标记正确添加,比如在提到主要方法时引用[3],提到ReentrantLock示例时引用[1]等。同时,检查是否有遗漏点,比如Lock接口的其他实现,或者更高级的功能如公平等,但根据用户的问题,可能不需要深入这些细节,保持回答简洁实用即可。</think>### Java中Lock接口的介绍与使用教程 #### 一、Lock接口概述 `Lock`接口是Java并发包(`java.util.concurrent.locks`)的核心组件之一,提供了比传统`synchronized`关键字更灵活的机制[^3]。 - **核心优势**:支持可中断、超时、非阻塞,以及多条件变量(`Condition`)。 - **常见实现类**:`ReentrantLock`(可重入`ReentrantReadWriteLock`(读写)[^2]。 --- #### 二、Lock接口核心方法 1. **`void lock()`** 获取。若被其他线程占用,则当前线程阻塞直到获取[^3]。 2. **`void unlock()`** 释放,**必须**在`finally`块中调用以避免死。 3. **`boolean tryLock()`** 尝试非阻塞获取:成功返回`true`,否则`false`。 4. **`boolean tryLock(long time, TimeUnit unit)`** 支持超时等待,避免无限阻塞。 5. **`Condition newCondition()`** 创建条件变量,用于线程间协调(如生产者-消费者模型)。 --- #### 三、使用示例 ##### 示例1:基础操作(`ReentrantLock`) ```java import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Counter { private int count = 0; private Lock lock = new ReentrantLock(); public void increment() { lock.lock(); // 获取 try { count++; } finally { lock.unlock(); // 确保释放 } } } ``` **说明**:通过`lock()``unlock()`显式控制临界区,保证`count++`的原子性[^1]。 --- ##### 示例2:非阻塞(`tryLock`) ```java public class ResourceAccess { private Lock lock = new ReentrantLock(); public void accessResource() { if (lock.tryLock()) { // 尝试获取 try { System.out.println("资源访问成功"); } finally { lock.unlock(); } } else { System.out.println("资源正忙,稍后重试"); } } } ``` **场景**:适用于需要快速失败(fail-fast)的逻辑。 --- ##### 示例3:条件变量(`Condition`) ```java import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class BoundedBuffer { private final Lock lock = new ReentrantLock(); private final Condition notFull = lock.newCondition(); // 条件变量1 private final Condition notEmpty = lock.newCondition(); // 条件变量2 private final Object[] items = new Object[100]; private int putPtr, takePtr, count; public void put(Object x) throws InterruptedException { lock.lock(); try { while (count == items.length) { notFull.await(); // 等待“非满”条件 } items[putPtr] = x; if (++putPtr == items.length) putPtr = 0; count++; notEmpty.signal(); // 通知“非空”条件 } finally { lock.unlock(); } } } ``` **说明**:通过`Condition`实现线程间精确通信(如生产者唤醒消费者)[^3]。 --- #### 四、Lock vs. synchronized | 特性 | Lock接口 | synchronized | |--------------------|-----------------------------|----------------------| | 获取方式 | 显式调用`lock()`/`unlock()` | 隐式(代码块/方法) | | 可中断性 | 支持(`lockInterruptibly`) | 不支持 | | 超时机制 | 支持(`tryLock`) | 不支持 | | 公平性 | 可配置(公平/非公平) | 非公平 | | 条件变量 | 支持多条件(`Condition`) | 单一条件(`wait`/`notify`) | --- #### 五、最佳实践 1. **始终在`finally`中释放**,防止异常导致死。 2. **避免嵌套**,复杂逻辑易引发死。 3. **优先使用`tryLock`**,尤其在需要超时或非阻塞场景。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值