java锁机制
1,什么是锁,在并发环境下,多个线程会对同一个资源进行争抢那么就有可能导致数据不一致的问题,所以我们就引入了锁的机制,对资源进行锁定?
简单来说,在java中每个object,也就是每个对象都拥有一把锁,这把锁存放在当前对象的头中锁记录了当前对象被那个线程占用呢?
我们了解一下对象的结构吧
对象由对象头,实例数据(实例化对象的时候设定的属性和状态),对齐填充字节(为满足java的对象大小必须为8比特的倍数而设计的)
对象头!重点 包含了俩部分 mark word(emmm锁状态呀,年龄呀。gc标记啥的)和class pointer(指向当前对象的类型所在方法区的内存数据)相对于实例数据呢~他们被设定为额外的开销,所以当初设计的时候被设计的极小用以提高效率
所以说对象中的锁就存在mark word中
我们一会再说锁的四种状态吧
大家也许都知道java中农synchronized关键字可以用来同步线程,那么synchronized在被编译后会生成mointorenter和mointorexit两个指令(字节码),来自于jvm编程思想(其实是自己想的)
Monitor是管程或者监视器的意思,想象为只能容纳一个人的房间,而其他线程想象为想进入房间的客人,所以当一个客人进入了monitor后其他的线程只能等待,只有当线程退出其他的线程才能进入,假如A线程进入monitor那他现在就是活动状态,如果此时他有一个判断条件需要暂时让出执行权,那他就是进图wait set状态,状态也会变成wait,此时entry set的线程就有机会进图monitor,此时我们再假设B进入monitor并且顺利完成任务,此时就可以通过notify的形式来唤醒wait set中的线程A,让A在此进入线程执行任务直到退出。这即使synchronized的同步机制,但是synchronized在编译之后是一个mointorenter和mointorexit俩字节码指令而monitor是依赖于操作系统的mutex lock来实现的(其实即是java线程实际上是通过对操作系统线程的映射)所以每当挂起或者唤醒一个线程都要切换操作系统的内核态,是重量级的在某些情况下甚至切换的时间甚至会超出线程执行任务的时间,这样往往得不偿失。
所以我们引入主题,在java6之后引入了偏向锁,轻量级锁,所以锁呀一共有四种状态,分别是无锁,轻量级锁,偏向锁,重量级锁。对应mark word的四种状态,!!!噢噢锁只能升级不能降级。
无锁,顾名思义,就是没有对资源进行锁定,所有的线程都能对资源进行改变,若某对象不出现在多线程环境或者在多线程环境不出现竞争的情况也就相安无事,直接给调用就行。若是存在竞争,我又不想锁定,那我们咋办?我们就要通过某种机制限制只有一个线程能修改资源成功,其他修改失败的线程都需要不断重试(自旋)知道成功这就是著名的CAS,其实cas在操作系统中是通过一条指令来实现的所以他能保证原子性,所以我们可以通过诸如cas的这种方式进行无锁编程
偏向锁:假设一个对象被枷锁了,但是实际上在实际运行中只有一改线程会获取这个对象锁,那么我们最理想的方式就是不通过线程状态的切换,也不要通过cas来获得锁,因为这样多多少少也要耗费一些资源,如果对象能认识这个线程是不是只要这个线程过来,我就把锁给他 我们即可认为这个锁偏爱这个线程 那么我们如何判断呢“”?在mark word是锁标志位为01时我们判断他倒数第三个比特是不是1若是1则是偏向锁否则为无锁,若是偏向锁我们再去读mark word的前23个比特,也就是线程id通过线程id来确认当前想要获得对象锁的这个线程是不是老朋友,假如情况发生了变化。对象发行目前不止有一个线程而是有多个线程同时在争抢锁,那偏向锁就会升级为轻量级锁。
轻量级锁:轻量级锁如何判断线程和锁之间的绑定关系呢?变化线程id值吗?不至于不至于~我们会变成前30个bit也就是指向栈中锁记录的指针?这是咋回事呢?我们来思考一下,当一个线程想要获得某个锁的对象时,假如看到锁标志位为00就知道了,原理你是轻量级锁,这时线程会在自己的虚拟机栈中开辟一块被称为Lock Record 的空间(线程私有) Lock Record存放的其实就是mark word的副本以及owner的指针,线程通过cas尝试获取锁,一旦获得就会复制该对象头中的md到lock record中并且将lock record中的owner指针指向该对象,另外对象头前30位也会生成一个指针指向线程虚拟机栈中的lock record,这样就实现了线程和对象锁的绑定,他们就知道了对方的存在。此时这个对象就被锁定了,获取了这个对象锁的线程就可以执行任务。那此时其他线程也要该对象执行某些任务呢?此时其他线程将会自旋等待!解释一下,自旋就是轮询,线程自己在不断的循环尝试着去看一下目标对象的锁有没有被释放呀,释放了就获取,没有我就过会再问。这个方式区别于被操作系统挂起阻塞,因为若是对象的锁很快就被释放的话自旋就不用进行系统的中断和现场恢复了所以他的效率更高!噢噢哦忘记说了,自旋相当于cpu在空转长时间自旋会浪费cpu的资源。所以出现了一种叫做适应性自旋的优化,其实就是自旋的时间不再固定了(又是操作系统里面的知识)而是又上一次同一个锁上的自旋时间以及锁状态来判断的。若是等待的线程超过一个,那就会从轻量级锁变成重量级锁了,若被标记为重量级锁,那就和前面一样被完全锁定资源。