JAVA锁
前言
JAVA中常用的锁其实是有很多的,但是,一般来说我们常见到的可能就是几种
下面用一个图来简单表示一下
下面来简单介绍一下
一,synchronized
synchronized关键字的用法有很多,最常用的可能就是在一个方法上加上这个关键字,然后就锁住了,那么它的原理是什么呢?
我们知道JAVA对象其实分为对象头,对象体,对齐填充。
先解释一下这三个东西
1,对象头
这里面有什么东西呢?都是一些隐性的必须品
你一个对象要放在内存,你要有地址吧?那么有个东西叫内存地址,也就是哈希码
你一个对象是哪个线程的,你要知道吧?那么有个东西是threadId。
你一个对象有锁吧?那么有个东西是锁
你一个锁有状态吧?那么有个东西是锁状态
你一个对象要GC吧?那么又个东西叫GC标志
等等。
2,对象体
你一个对象有数据吧?都在里面
3,对齐填充
这玩意儿其实就是对齐用的,相信大家学JAVA的时候都学过基本数据类型int,long什么的,他们占多少字节,对吧?这东西,大家学过,但是很容易就忘记了,因为敲代码没什么用。那么一个对象占多少字节呢?
一般来说是8的倍数,那么对齐填充就是来补位的。也就是对象头+对象体+对齐填充=n*8。
在对象头里有个东西叫Monitor,synchronized能锁住,其实就是靠这个东西。
那么加了锁,就要释放,那么释放又是什么呢?是由JVM来做的。
synchronized的底层其实是什么呢,其实是Mutex,这个东西是什么呢?大家都知道JVM是C和汇编弄出来的。Mutex其实是这个层面的东西,比如对应在汇编里有个东西叫tsl(大家百度一下)。
那么一个synchronized的执行过程大概是什么呢?
1. 检测Mark Word里面是不是当前线程的ID,如果是,表示当前线程处于偏向锁
2. 如果不是,则使用CAS将当前线程的ID替换Mard Word,如果成功则表示当前线程获得偏向锁,置偏向标志位1
3. 如果失败,则说明发生竞争,撤销偏向锁,进而升级为轻量级锁。
4. 当前线程使用CAS将对象头的Mark Word替换为锁记录指针,如果成功,当前线程获得锁
5. 如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。
6. 如果自旋成功则依然处于轻量级状态。
7. 如果自旋失败,则升级为重量级锁。
二,AQS
这玩意儿叫同步器,一般来说,你写代码的时候,没碰过这东西。因为这是个父类,但是你写多线程的时候肯定用过它的实现类,比如ReentrantLock;Countdownlatch等。这两个东西怎么用就不说了。
他们的底层实现都是AQS。
复杂的源码分析就不说了。
简单点说就是下面这个图
AQS就是个链表构成的队列加上volatile关键字。
在AQS中有个属性
private volatile int state;
举个例子
单线程的时候:
假设state=0,你用ReentrantLock的时候,锁住了一个方法,当一个线程来的时候进入队列,state变成了1,OK,方法往下走,走完了state=0,没问题。
多线程的时候:
假设state=0,你用ReentrantLock的时候,锁住了一个方法,当N个线程进来的时候进入队列,队列里第一个线程拿到了锁,state变成了1,OK,方法往下走;队列里第二个线程去拿锁,发现state=1,失败了,就等待,后面的可以简单认为也是这样。当第一个线程走完了,释放了锁,出队列了;第二个线程变成第一个了,拿到了锁,那么第二个就执行,以此类推。
三,CAS
这个玩意儿,一般来说,写代码的时候,你应该是没有用过的。如果你用IDEA,用了lambda表达式的流处理并且想改变里面的int一类类型的值的话,IDEA会提示你用AtomicInteger或数组。
AtomicInteger(原子类)这一类的东西底层就是CAS,中文就是比较和交换。他的底层是啥呢?
volatile,要了解这个东西,其实要知道JVM的线程模型,如下图
JVM的线程模型和操作系统是对应的,当一个线程过来的时候是需要到主存里去拿东西的。
举个例子
单线程的时候:
在主存里有X,线程A要修改X,那么来拿X,拿到之后,把X记录在线程A里,然后把X修改成Y,完了以后,就有2个东西了,一个X(旧),一个是X(新),现在要把X(新)放回主存,那么就要先拿X(旧)和主存里的X比较一下,看看是不是一样的,如果是就把主存里的X变成X(新),如果不是一样的,就失败了。
多线程的时候:
在主存里有X,线程A要修改X,线程B也要修改X,那么都来拿X,拿到之后,A把X记录在线程A里,B把X记录在线程B里,然后A把X修改成Y,B把X修改成Z,完了以后,就都有2个东西了,一个X(旧),一个是X(新),现在要把X(新)放回主存,那么就要先拿X(旧)和主存里的X比较一下,看看是不是一样的,如果是就把主存里的X变成X(新),如果不是一样的,就失败了,那么A和B肯定会有一个成功一个失败。
怎么样?是不是有点熟悉的感觉。CAS这个东西简单点说就是乐观锁 。
这里提一下,在内存里有个东西叫缓存一致性协议,这个东西其实是volatile能实现的原因。
下面还有一些经常听见,但是其实你模糊的一些锁
1,自旋锁
这玩意其实是个优化功能,你写代码是不会写到它的,都是源码实现的。就是线程的唤醒,休眠什么的耗性能,所以弄了个这个东西出来,他是怎么实现的呢?其实就是for循环。。。
2,偏向锁
这玩意其实也是个优化功能,你写代码是不会写到它的,都是源码实现的。就是一个锁,第一个线程进来的时候,会记录一下这个线程,如果下一个也是他的话,就会比较快。。。
3,可重入锁
这玩意,你写代码是不会写到它的,都是源码实现的。其实就是一个线程拿到锁之后,他自己还可以拿第二次,第三次。。。代码的原理其实是有一个status的值记录一下,拿一次就+1,用完了就-1,再底层一点就是有个东西叫程序计数器。。。
4,乐观锁
CAS
5,悲观锁
synchronized