java中的锁

本文详细介绍了 Java 中的锁机制,包括内部锁 synchronized 的使用、可重入性以及其修饰代码的不同方式。同时,文章讨论了 java.util.concurrent.locks.Lock 接口,特别是 ReentrantLock 和 ReadWriteLock,强调了它们提供的高级特性和在并发编程中的应用场景。通过对各种锁的分析,展示了 Java 并发控制的灵活性和控制粒度的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

java中有哪些锁

这个问题在我看了一遍<java并发编程>后尽然无法回答,说明自己对于锁的概念了解的不够。于是再次翻看了一下书里的内容,突然有点打开脑门的感觉。看来确实是要学习的最好方式是要带着问题去学,并且解决问题。

在java中锁主要两类:内部锁synchronized和显示锁java.util.concurrent.locks.Lock。但细细想这貌似总结的也不太对。应该是由java内置的锁和concurrent实现的一系列锁。

为什么这说,因为在java中一切都是对象,而java对每个对象都内置了一个锁,也可以称为对象锁/内部锁。通过synchronized来完成相关的锁操作。

而因为synchronized的实现有些缺陷以及并发场景的复杂性,有人开发了一种显式的锁,而这些锁都是由java.util.concurrent.locks.Lock派生出来的。当然目前已经内置到了JDK1.5及之后的版本中。

synchronized

首先来看看用的比较多的synchronized,我的日常工作中大多用的也是它。synchronized是用于为某个代码块的提供锁机制,在java的对象中会隐式的拥有一个锁,这个锁被称为内置锁(intrinsic)或监视器锁(monitor locks)。线程在进入被synchronized保护的块之前自动获得这个锁,直到完成代码后(也可能是异常)自动释放锁。内置锁是互斥的,一个锁同时只能被一个线程持有,这也就会导致多线程下,锁被持有后后面的线程会阻塞。正因此实现了对代码的线程安全保证了原子性。

可重入

既然java内置锁是互斥的而且后面的线程会导致阻塞,那么如果持有锁的线程再次进入试图获得这个锁时会如何呢?比如下面的一种情况:

public class BaseClass {
    public synchronized void do() {
        System.out.println("is base");
    }
}
public class SonClass extends BaseClass {
    public synchronized void do() {
      System.out.println("is son");
      super.do();
    }
}
SonClass son = new SonClass();
son.do();

此时派生类的do方法除了会首先会持有一次锁,然后在调用super.do()的时候又会再一次进入锁并去持有,如果锁是互斥的话此时就应该死锁了。

但结果却不是这样的,这是因为内部锁是具有可重入的特性,也就是锁实现了一个重入机制,引用计数管理。当线程1持有了对象的锁a,此时会对锁a的引用计算加1。然后当线程1再次获得锁a时,线程1还是持有锁a的那么计算会加1。当然每次退出同步块时会减1,直到为0时释放锁。

synchronized的一些特点

修饰代码的方式

  • 修饰方法
    public class BaseClass {
        public synchronized void do() {
            System.out.println("is base");
        }
    }

    这种就是直接对某个方法进行加锁,进入这个方法块时需要获得锁。

  • 修饰代码块
    public class BaseClass {
        private static Object lock = new Object();
        public void do() {
            synchronized (lock) {
                System.out.println("is base");
            }
        }
    }

    这里就将锁的范围减少到了方法中的部分代码块,这对于锁的灵活性就提高了,毕竟锁的粒度控制也是锁的一个关键问题。

    对象锁的类型

    经常

### Java 中的机制概述 Java 提供了多种机制以支持多线程环境下的同步操作。这些机制主要包括内置(synchronized 关键字)、显示(ReentrantLock 类)以及一些高级特性如读写(ReadWriteLock)。以下是关于它们的具体介绍: --- ### 内置(Intrinsic Lock) Java 的 synchronized 关键字提供了一种简单的方式来进行线程间的同步。每个对象都有一个与之关联的监视器(monitor),当一个线程进入由 synchronized 修饰的方法或代码块时,它会自动获得该对象的监视器。 ```java public class Counter { private int count = 0; public synchronized void increment() { count++; } public synchronized int getCount() { return count; } } ``` 上述代码展示了如何使用 synchronized 方法来确保多个线程不会同时修改 `count` 变量[^1]。 --- ### 显示(Explicit Lock) 相比于内置,显示提供了更大的灵活性。`ReentrantLock` 是最常用的显示实现之一,它可以手动控制的获取和释放,并且支持可中断等待、超时尝试等功能。 #### 使用示例 ```java import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Worker { private final Lock lock = new ReentrantLock(); private int value = 0; public void updateValue(int newValue) throws InterruptedException { boolean isLocked = false; try { isLocked = lock.tryLock(1000, java.util.concurrent.TimeUnit.MILLISECONDS); if (isLocked) { value = newValue; } else { System.out.println("Failed to acquire the lock."); } } finally { if (isLocked) { lock.unlock(); } } } public int getValue() { lock.lock(); try { return value; } finally { lock.unlock(); } } } ``` 此代码片段演示了如何利用 `tryLock()` 方法在一定时间内尝试获取,如果无法获取,则可以选择其他逻辑路径[^1]。 --- ### 偏向(Biased Locking) 偏向是一种优化技术,旨在减少无竞争情况下的操作开销。它的基本思想是在第一次访问某个时将其绑定到当前线程,后续再次访问时不需额外验证即可直接执行。 然而,一旦检测到有其他线程尝试获取同一个JVM 将撤销偏向并升级为轻量级甚至重量级。这一过程发生在全局安全点处,因为需要暂停所有活动线程以重新评估的状态[^3]。 --- ### 轻量级(Lightweight Locking) 轻量级通过自旋的方式来避免线程切换带来的性能损失。具体来说,当第一个线程请求时,它会在栈帧中保存指向记录的信息;第二个线程到达时发现已被占用,则启动循环不断检查是否可用。只有当自旋次数达到上限仍未能成功时,才会退化成重量级[^4]。 --- ### 解决 ABA 问题 在高并发场景下可能出现一种称为 ABA 的现象:某一变量先后经历了两次相同值的变化,但其间可能发生了不可忽略的动作。为此引入了带版本号的原子引用类 `AtomicStampedReference` 来加以防范[^5]。 ```java import java.util.concurrent.atomic.AtomicStampedReference; public class ABADemo { static AtomicStampedReference<Integer> atomicRef = new AtomicStampedReference<>(100, 0); public static void main(String[] args) { new Thread(() -> { int stamp = atomicRef.getStamp(); try {Thread.sleep(1);} catch (Exception ignored){} atomicRef.compareAndSet(100, 101, stamp, stamp + 1); atomicRef.compareAndSet(101, 100, stamp + 1, stamp + 2); }).start(); new Thread(() -> { int stamp = atomicRef.getStamp(); try {Thread.sleep(2);} catch (Exception ignored){} boolean result = atomicRef.compareAndSet(100, 101, stamp, stamp + 1); System.out.println(result ? "Success" : "Failure"); }).start(); } } ``` 以上例子表明即使目标数值看似未变,但由于附加了时间戳标记,依然能有效区分实际发生的改动序列[^5]。 --- ### 高效实践建议 - 对于短期定需求优先考虑内置; - 如果涉及复杂业务流程推荐选用显式以便更好地掌控资源分配状况; - 注意合理配置粒度大小以免造成不必要的争用瓶颈。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lmr廖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值