Java中锁的问题

在java中。锁是一种多线程编程的重要概念,用于保护共享资源的一致性。

1、乐观锁与悲观锁

        乐观锁假设并发冲突不太常见,因此在访问共享资源时不会加锁,在更新资源时才会比较数据(主内存中的值和当前线程中的值),如果不一致则重新操作。

        CAS(比较并替换)是乐观锁的一种机制,其操作是原子性的,包括三个步骤:比较当前值、判断是否相等、如果相等则进行更新操作。CAS避免了使用传统锁机制所带来的线程阻塞和上下文转换开销,在高并发的条件下可以提高性能。

        为避免ABA问题,则使用了版本号来标识资源的状态;如果线程操作失败则会一直自旋尝试,直到成功或达到一定次数;对于多变量操作,CAS不太适用。

        悲观锁是一种比较保守的策略,它假设会发生并发冲突,因此在访问资源前会先获取锁,确保同一时间只有一个线程能够访问该资源。synchronized关键字和ReentrantLock都属于悲观锁的实现。

2、自旋锁

        自旋锁是一种基于忙等待的锁机制,用于多线程环境下控制对共享资源的访问。与传统的互斥锁不同,自旋锁不会使线程进入阻塞状态,而是进入忙等待状态,不断重试获取锁,直到获取成功为止。

        自旋锁适用于:

        ①线程占用锁的时间很短:如果线程只需要短暂的访问共享资源,使用自旋锁可以避免线程切换的开销,从而提高性能。

        ②线程竞争相对较轻:如果获取锁的线程很快就会释放锁,那么自选等待的时间就不会太长。

        ③多核处理器:线程在等待锁的过程可以继续执行,利用了多核处理器的并行性。

3、可重入锁

        可重入锁也被称为递归锁,是一种支持同一线程多次获取锁的机制。

        可重入锁允许同一个线程在持有锁的情况下多次获取锁,而不会被自己锁持有的锁阻塞。这种机制是的线程可以提柜的调用同步方法。它会维护一个持有锁的计数器,每次获取锁计数器加一,解锁时,计数器减一。只有当计数器为0时,其他线程才能获取锁。Java中的ReetrantLock和synchronized都可以实现可重入锁。

        注意:加锁和解锁要成对出现,获取和释放锁的次数相匹配。

4、读写锁

        读写锁包含两个锁:读锁(ReadLock)和写锁(WriteLock)。多个线程可以同时持有读锁,只允许一个线程持有写锁。当线程持有写锁时,其他线程无法同时获取读锁和写锁。

        多个线程可以同时持有读锁,这样可以实现并发读取,提高性能。写锁独占,保证原子性和一致性。

5、公平锁和非公平锁

        公平锁是一种思想,多个线程按照申请锁的顺序来获取锁,在并发环境中,每个线程会先查看此锁维护的等待队列,如果当前等待队列为空,则占有锁,如果不为空则加入到等待队列的末尾。

        非公平锁:当线程尝试获取锁,如果获取不到,则再进入等待。

        synchronized是非公平锁,ReentrantLock通过构造函数指定该锁是公平的还是非公平的。

ReentrantLock lock = new ReentrantLock(true);//创建公平锁

6、互斥锁和共享锁

        互斥锁是一种独占锁,当一个线程尝试获取互斥锁时,如果锁已经被其他线程占用,该线程则被阻塞。

        共享锁和乐观锁、读写锁同义。多个线程同时持有锁。

7、重量级锁

        线程在获取重量级锁时,若锁已被其他线程持有,则当前线程会被阻塞。

        重量级锁使用操作系统的底层机制,如互斥量或信号量,需要进行用户态和核心态的切换,因此锁的释放和获取会有较多的系统开销。

        由于重量级锁设计上下文的切换和系统调用等较高的开销,所以在高并发环境下,使用重量级锁会导致性能下降。synchronized是重量级锁。

8、轻量级锁

        轻量级锁使用CAS操作来实现锁的获取和释放,避免了系统调用和线程阻塞的开销。

        如果其它线程也尝试获取同一个对象的轻量级锁,那么会发生自旋,如果等待时间过长,则膨胀为重量级锁。

9、偏向锁

        偏向锁是无竞争情况下提高性能的一种锁优化技术。当一个线程访问一个同步块并获取锁时,JVM会将对象头中标志位设置为偏向锁,将当前线程的Tread Id记录在对象头中,在后续的访问中,如果没有其他线程访问该同步块,那么持有偏向锁的线程可直接进入临界区。如果其他线程尝试获取同一个对象的锁,那么偏向锁就会自动撤销,升级为轻量级锁或重量级锁。

        适用于大多数情况下,只有一个线程访问同步块的场景。

10、分段锁

        分段锁是一种机制,它将一个共享资源划分为很多个段,每个段都有自己的锁,不同的线程可以访问不同的段,从而减少了锁的竞争范围,提高了并发性能。

        段的数量越多,允许并发访问的线程数量就会越多,但也会带来更多内存的开销和锁管理的复杂性。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class StripedLockExample {
    private final int SEGMENT_COUNT = 16;
    private final Lock[] locks = new Lock[SEGMENT_COUNT];
    private final Object[] data = new Object[SEGMENT_COUNT];

    public StripedLockExample() {
        for (int i = 0; i < SEGMENT_COUNT; i++) {
            locks[i] = new ReentrantLock();
            data[i] = new Object();
        }
    }

    public void performOperation(int index) {
        Lock lock = locks[index % SEGMENT_COUNT];
        lock.lock();
        try {
            // 访问对应段的共享资源
            // ...
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        StripedLockExample example = new StripedLockExample();

        // 创建多个线程并发访问共享资源
        for (int i = 0; i < 10; i++) {
            final int index = i;
            new Thread(() -> {
                example.performOperation(index);
            }).start();
        }
    }
}

11、死锁

        死锁是因为两个或多个线程因为彼此慈幼的资源而无法继续执行,陷入无限的等待。

        死锁发生的必要条件:

        ①互斥条件:一个资源每次只能被一个线程占用。

        ②请求和保持条件:线程每次申请它所需要的一部分资源,在申请新的资源的同时,继续占用已分配到的资源。

        ③不可剥夺条件:线程在未使用完毕资源前,不被其他线程强行剥夺。

        ④循环等待:各线程等待的资源形成一个循环链。

        尽量避免出现循环,可有效避免死锁。

12、锁粗化和锁消除

        锁粗化是指在编译器或运行时环境中对锁的优化技术,用于减少锁竞争时的开销。将多个连续的细颗粒度的锁合并为一个更大的锁,减少了锁的获取和释放的次数。

        锁消除用于在程序中自动消除不必要的锁操作,从而提高性能。

 13、syncronized

        它是java中的关键字,属于独占锁、悲观锁、可重入锁、非公平锁。

       sychronized的原理涉及java中的监视器锁,每个java对象都可以作为一个锁,当使用其修饰代码块或方法时,实际上是在获取该对象的锁。

        在底层实现上,每个Java对象都有一个与之关联的监视器,当一个线程获得对象的锁时,它会在对象头中记录自己的标识,并将对象的锁计数器加一。如果另一个线程尝试获取同一个对象的锁,但锁已被持有,该线程将进入阻塞队列。

        当一个线程释放锁时,它会将所有对共享变量的修改刷新到主内存中,这样确保了可见性

14、Lock和synchronized的区别

        ①使用方式

                syncronized是Java关键字,通过在方法或代码块上添加关键字来实现同步。

                lock是接口,并使用lock()和unlock()方法来手动的获取和释放锁。

        ②锁的获取和释放控制

                synchronized由Java虚拟机自动控制锁的获取和释放,当出现异常时,会自动的释放。Lock需要手动调用lock()方法来获取锁,但是发生异常时,并不会自动释放,所以一般需要将unLock()方法卸载finally块中。

        ③锁的灵活性

                lock可以根据需要选择适当的锁实现类,如ReentrantLock、ReadWriteLock等。

        ④锁获取结果

                lock可以在获取锁时抛出异常,通过捕获异常来获取锁获取失败或成功。而synchronized无法知道。

        ⑤可中断性

                lock可以让等待锁的线程响应中断,即在获取锁的过程中如果线程被中断,可以捕获InterruptedException异常处理中断事件;而synchronized不行,线程会一直阻塞等待获取锁。

15、ReentrantLock 

        它实现了Lock,可重入锁、悲观锁、独占锁、互斥锁。

        ReentrantLock默认非公平锁,可以实现公平锁。其内部维护了一个等待队列,遵循先进先出原则,避免了线程饥饿问题,但是增加了开销。

16、分布式锁

        分布式锁是用于在分布式系统中实现互斥访问的机制。实现分布式锁的三种方式:

        ①基于数据库:使用数据库的唯一索引来实现分布式锁,通过在数据库中创建一张表,使用某个唯一键标识锁的状态,当节点需要获取锁时,尝试往表中插入一条数据,插入成功则表示获取成功,否则标识锁已被其他占用,释放锁时,删除数据。

        ②基于缓存:可以使用Redis是实现分布式锁,在Redis中可以使用SETNX命令来设置一个键,并设置过期时间。使用唯一的Key键来当锁,如果key不存在,就表示获取到了锁,并设置key,value表示竞争者的id。过期时间可以保证竞争者加锁之后挂了,不会产生死锁问题,value可以确定只有加锁者才可以释放锁。

        ③基于ZooKeeper:Zookeeper提供一个多层级的节点命名空间(节点称为znode)。每个参与分布式锁的节点在Zookeeper上创建一个临时有序的节点,节点路径克表示为lockNode --> lock0001、lockNode --> lock0002,这些节点也是按照创建的先后顺序排列的。节点尝试获取锁时,会检查自己创建的节点是否为当前序号的最小节点,如果是,表示获取到锁,否则监听前一个节点的删除事件,这样恰好形成了一个等待队列。释放锁时,就删除自己创建的节点。加锁时可以将主机和线程信息写入节点中,下一次申请锁时和最小节点对比,可以解决可重入性问题。

        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

伊人秋采唐

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

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

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

打赏作者

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

抵扣说明:

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

余额充值