这次我们来看锁。说到锁,我们往往会想到生活中的锁。到锁。比如我们生活中的手机锁、电脑锁、门锁,这些都是锁。锁的作用是什么?说了这么多,还是不清楚锁有什么用。这就是我们使用锁的原因。我们使用手机锁来保护我们的隐私和安全,使用门锁来保护我们的安全。财产安全,准确的说,我们用锁是为了安全。所以在生活中,我们可以加装锁来保护我们的隐私和财产安全。Java中的锁有什么用?Java中的锁Java中的锁恰恰是为了保证安全,但不同的是,在Java中需要锁来保证并发。所以Java中加锁正是为了保证并发安全,也是为了解决内存中的一致性、原子性和顺序这三个问题。Java中提供了各种锁,每种锁都有自己的特点和适用范围。因此,我们必须熟悉锁具的区别和原理,才能正确使用。乐观锁和悲观锁悲观锁乐观锁和悲观锁刚开始写的时候写过相关的文章,这里重新介绍一下。悲观锁,顾名思义,就是悲观锁。它认为每次访问数据都可能被其他人(线程)修改,所以在访问资源时会加锁。该方法用于确保访问资源时访问资源。不会被其他线程修改。在这种情况下,如果其他线程想要获取资源,只能阻塞,等待当前线程释放锁后再获取。Java中悲观锁的实现有synchronized关键字,Lock的实现类就是悲观锁。我们来看看悲观锁是如何实现的。线程A抢占资源后,线程B被阻塞,然后等待线程A释放资源。线程A释放资源后,线程B获取锁,开始操作资源。悲观锁保证同一时刻只有一个线程可以操作资源。乐观锁与悲观锁相反。乐观锁认为在访问数据的时候不会有人修改(所以是乐观的),所以在访问资源的时候不会加锁,而是回过头来判断是否有人提交了当前数据被修改了,我们可以使用版本号在数据库中实现它。在Java中我们使用CSA来实现。下面看一下乐观锁的执行过程。CASCAS(CompareAndSwap)算法是Java提供的一种无锁算法,是一种非阻塞的原子操作。多线程下的同步是在不使用锁的情况下实现的。并发包(java.util.concurrent)中的原子类使用CAS实现乐观锁。CAS通过硬件保证比较更新的原子性。Unsafe在JDK中提供了一系列的compareAndSwap*方法,这里不深入研究Unsafe类。CAS运算过程就是将内存中待修改的数据与期望值进行比较。如果两个值相等,则修改后的值为新值,否则不进行任何操作。也就是说,CAS需要三个运算值:待修改的内存B中的期望值AV简单来说,CAS就是一个死循环。在循环中判断期望值是否等于内存中的值。如果它们相等,它们将被修改。如果不相等,则继续循环。,直到执行成功退出。CAS的问题CAS虽然很强大,但也存在一些问题,比如ABA问题。比如内存中有一个共享变量X,它的值为A,这时候出现一个变量,想要修改变量X的值,首先会得到X的值。这时候就获取到了A,然后通过CAS操作将X变量修改为B。这样貌似没什么问题,那么如果线程1获取到变量X之后,在执行CAS之前,线程2改了值X的到B,然后CAS操作执行变成了A,虽然最终执行结果共享变量的值为A但是这个A已经不是线程1得到的A了,这就是经典的ABA问题。ABA问题的出现是因为变量的状态值经历了循环转换。A可以去B,B也可以去A,如果A去B,B去C,就不会出现这种问题。解决方法:JDK1.5后加入AtomicStampedReference方法,为每个变量加上时间戳,避免ABA问题。同时CAS也存在循环开销高的问题,因为它会一直循环下去,直到expected和memory相等,修改成功。同时,还存在只有一个共享变量才能保证原子性的问题。但是在JDK1.5之后加入了AtomicReference类来保证被引用对象之间的原子性。使用悲观锁和乐观锁,可以使用synchronized关键字实现悲观锁,乐观锁可以使用union包下提供的原子类。公平锁和非公平锁上面说了悲观锁和乐观锁,现在我们来看一下公平锁和非公平锁。锁也有公平和不公平。顾名思义,公平锁强调的是公平性,所以如果多个线程同时申请锁,线程会被放入一个队列中,队列中第一个进入队列的线程可以在获取锁资源时,强调先到先得。比如我们在学校食堂吃饭的时候,我记得同学们一放学就去食堂排队,这样可以尽快上饭,不会有人吃不上。排队的时候吃。这时候,食堂阿姨说大家都排队有饭吃很公平,跟帖也是。非公平锁可以这样理解。我的同学去食堂排队取餐,结果有人插队了。食堂小姐姐不公平,直接给插队的人送饭,也不给他打电话。你说你生气不是不公平吗?关注不公平的锁。先到先得不一定。然而,公平锁也有缺点。当一个线程获取资源时,队列中的其他线程只能被阻塞。由于CPU的原因,公平锁的效率远低于非公平锁。因为CPU唤醒阻塞线程的开销比非公平锁要大。我们来看一个例子:ReentrantLock在Java中提供了公平锁和非公平锁的实现。下面来看看ReentrantLock是如何实现公平锁和非公平锁的。使用公平锁和非公平锁ReentrantLock默认是非公平锁。我们来看一个公平锁的例子:公平锁。看输出:我们可以在输出中看到公平锁输出结果是有顺序的,先到先得。我们来看一下非公平锁的例子:非公平锁例子的输出结果:输出结果,我们可以看到,如果使用了非公平锁,最后输出的结果是完全乱序的,先到先得不一定先服务。所以使用公平锁时,线程1获得锁后,如果线程2请求锁,就会挂掉,等待线程1释放锁,线程2才能获得锁。如果另一个线程3要申请锁,如果此时使用了非公平锁,那么线程2和线程3中的一个就会获得锁。在公平锁的情况下,线程3只能先挂起。等待线程2获取锁资源,释放后再获取。什么时候使用公平锁和非公平锁在需要公平资源的场景下使用公平锁。如果不需要特别公平对待,尽量使用非公平锁,因为公平锁会带来性能开销。独占锁和共享锁看到独占锁和共享锁你会怎么想?右独占锁是指同一时间只有一个线程可以占用锁资源,而其他线程只能等待当前获取锁资源的线程释放锁,然后再获取锁,上面的ReentrantLock就是独占锁,这么看来排他锁不是悲观锁?因为悲观锁抢占资源后,只能等待其他线程释放,重新获取锁资源。其实准确的说排他锁也是悲观锁。说到共享锁,共享锁其实就是乐观锁,放宽了锁策略,允许多个线程同时获取锁。ReadWriteLock是并发包中典型的共享锁。它允许通过多个读取操作或单个写入操作访问资源,但不能同时进行。什么是自旋锁?自旋锁其实就是当一个线程获得锁的时候,这个锁已经被其他人获得了,所以线程不会马上挂掉,而是会挂掉,不会放弃CPU的使用权。再次尝试获取锁资源,默认次数为10次,可以使用-XX:PreBlockSpinsh设置次数。如果自旋锁获取锁的时间过长,会导致后续线程CPU资源耗尽释放。自旋锁是不公平的。优点自旋锁不会切换线程状态,一直处于用户态,即线程一直处于活动状态;不会使线程进入阻塞状态,减少不必要的上下文切换,执行速度快。
