likes
comments
collection
share

锁的艺术:Java 中的锁类型与欢乐杂谈

作者站长头像
站长
· 阅读数 10

嗨,大家好,欢迎来到程序猿漠然公众号,我是漠然。

亲爱的代码侠们,今天我们将走进Java的锁世界,探索那些锁定的秘密。在这个过程中,我会尽量让你们在笑声中学习,所以请准备好你的笑脸和耳朵,因为这是一场关于锁的喜剧之旅。

乐观锁:假设世界是美好的

乐观锁适用于更新操作频繁且冲突较少的情况。它通过CAS(Compare And Swap)操作来实现,如果更新成功,则直接进行修改;如果更新失败,则重新尝试。

想象一下,乐观锁就像是一个总是看到世界美好的一面的人。在Java中,这可以通过Atomic类实现,比如AtomicInteger。它会在更新数值时,先假设不会有其他线程来打扰它,然后再去检查一下是不是真的没有人抢它的风头。

import java.util.concurrent.atomic.AtomicInteger;
public class OptimisticLockerDemo {
    private AtomicInteger count = new AtomicInteger(0);
    public void addCount() {
        int current;
        do {
            current = count.get();
            // 假设没有人修改过计数器
        } while (!count.compareAndSet(current, current + 1));
        // 如果有竞争,这里会重试
    }
}

悲观锁:小心翼翼的守门员

悲观锁适用于读写操作频繁且可能发生冲突的情况。它通过独占锁来确保同一时间只有一个线程可以访问资源。

悲观锁,就像是那个总是担心门外的世界充满了危险的人。在Java中,synchronized关键字和ReentrantLock就是这种锁的代表。它们在给你开门之前,会先确认一下是否有人在外面等着闯进来。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class PessimisticLockerDemo {
    private final Lock lock = new ReentrantLock();
    public void accessResource() {
        lock.lock();
        try {
            // 这里是安全的代码区
        } finally {
            lock.unlock();
        }
    }
}

读写锁:有的放矢的守卫

读写锁(ReadWriteLock)允许多个读线程同时访问资源,但写线程在写入时需要独占访问权。这样,既可以提高读操作的并发性,又可以保证写操作的一致性。

读写锁就像是那个知道如何区分访客的人。在读写锁面前,读取动作是自由的,任何人都可以读,但写入动作则需要得到允许。

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockerDemo {
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    public void readData() {
        readWriteLock.readLock().lock();
        try {
            // 读操作
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
    public void writeData() {
        readWriteLock.writeLock().lock();
        try {
            // 写操作
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }
}

偏向锁:偏心的守候者

偏向锁旨在减少不必要的锁竞争开销。当锁主要被一个线程持有时,启用偏向模式可以提高性能,因为它会避免不必要的锁竞争检测。

偏向锁就像是那个只喜欢某个特定客户的服务员。在偏向锁的管理下,锁会偏爱第一个访问它的线程,就像服务员总是优先服务他最喜欢的顾客一样。

import java.util.concurrent.atomic.AtomicReference;
public class BiasedLockerDemo {
    private final AtomicReference<Thread> owner = new AtomicReference<>();
    public void doWork() {
        Thread currentThread = Thread.currentThread();
        if (owner.get() == null || owner.get() == currentThread) {
            owner.set(currentThread);
            // 偏向锁:当前线程独占锁
        } else {
            // 如果有其他线程竞争,则释放锁并重新竞争
            owner.compareAndSet(currentThread, null);
            owner.set(currentThread);
        }
        // 执行操作
    }
}

轻量级锁:灵活的舞者

轻量级锁适用于竞争不激烈或短暂的锁需求。当没有竞争时,它通过CAS操作避免使用重量级的操作系统互斥量。

轻量级锁就像是那个在舞蹈中轻盈跳跃的舞者。当没有竞争时,它通过CAS操作旋转跳跃,就像是在跳一场只有一个人的华尔兹。

import java.util.concurrent.atomic.AtomicReference;
public class LightweightLockerDemo {
    private final AtomicReference<Thread> owner = new AtomicReference<>();
    public void doWork() {
        Thread currentThread = Thread.currentThread();
        ```java
        if (owner.get() == null) {
            owner.compareAndSet(null, currentThread);
        } else if (owner.get() != currentThread) {
            // 轻量级锁:如果锁被其他线程持有,则尝试通过自旋等待
            while (!owner.compareAndSet(currentThread, currentThread)) {
                // 自旋等待
            }
        }
        // 执行操作
    }
}

自旋锁:不停转的舞者

自旋锁适用于锁被占用时间非常短的情况。线程在等待锁时不会进入等待状态,而是会循环检查锁的状态,这样可以减少线程状态切换的开销

自旋锁就像是那个在等待时也不会闲着的人。当线程尝试获取锁时,它不会直接放弃,而是会围着锁转来转去,就像是在跳一场不停旋转的探戈。

import java.util.concurrent.locks.LockSupport;
public class SpinningLockerDemo {
    private final AtomicReference<Thread> owner = new AtomicReference<>();
    public void doWork() {
        Thread currentThread = Thread.currentThread();
        while (!owner.compareAndSet(null, currentThread)) {
            // 自旋等待
            LockSupport.parkNanos(1); // 稍作停顿,然后继续自旋
        }
        // 执行操作
        owner.compareAndSet(currentThread, null); // 释放锁
    }
}

分段锁:聪明的物业管理者

分段锁适用于高度并发的场景,通过将数据分成多个段,可以减少锁竞争,提高系统的并发性能。

分段锁就像是那个把大楼分成不同区域,并为每个区域分配一个守门人的物业管理者。这样,每个守门人只负责自己的区域,避免了所有的访客都在大门口挤来挤去。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.HashMap;
import java.util.Map;
public class SegmentedLockerDemo {
    private final Map<Integer, Lock> locks = new HashMap<>();
    public void accessResource(int segment) {
        Lock lock = locks.computeIfAbsent(segment, k -> new ReentrantLock());
        lock.lock();
        try {
            // 这里是安全的代码区
        } finally {
            lock.unlock();
        }
    }
}

希望这次关于Java锁的幽默之旅能让你们在笑声中学习到Java中锁的艺术。记住,锁的世界就像是一部复杂的芭蕾舞剧,每个角色都有自己的舞蹈,而我们的任务就是确保每个人都能跳得开心。

今天的分享就到这里,如果觉得对你有帮助,感谢点赞、分享、关注一波,你的认可是我创造的最大动力。

更多内容请关注公众号:程序猿漠然,一个分享有趣后端知识的公众号。

转载自:https://juejin.cn/post/7322842829776158783
评论
请登录