java多线程(四) java中的锁-synchronized关

2019-10-08  本文已影响0人  只会写一点点代码

一、写作议题:

Thread safety is a computer programming concept applicable to multi-threaded code. 
Thread-safe code only manipulates shared data structures in a manner that ensures that all threads behave properly and fulfill their design specifications without unintended interaction. There are various strategies for making thread-safe data structures.

A program may execute code in several threads simultaneously in a shared address space where each of those threads has access to virtually all of the memory of every other thread. Thread safety is a property that allows code to run in multithreaded environments by re-establishing some of the correspondences between the actual flow of control and the text of the program, by means of synchronization

二、java中的锁机制

三、深入理解synchronized关键字。

public class Syn {
    private static final Object lock=new Object();
    private static int a=10;
   public static void main(String[] args) {
        int a=0;
        synchronized (lock){
            a++;
        }
        System.out.println("Result"+a);
    }
    public static synchronized  int getNum(int increse){
        a=increse+a;
        return a;
    }

现在使用javap命令了解一下,针对同步的部分生成的字节码;

      7: **monitorenter**
       8: iinc          1, 1
      11: aload_2
      12: **monitorexit**
      13: goto          21
      16: astore_3
      17: aload_2
      18: **monitorexit**

具体monitorenter和monitorexit到底干了什么事?jvm需要保证monitorenter和monitorexit一一配对,任何一个对象都有一个monitor与之关联,当且一个monitor被持有以后,它将处于锁定的状态,线程执行到monitorenter指令以后,将会尝试获取对象所对应的monitor的所有权,即尝试获取对象的锁,有兴趣的同学可以了解一下jdk的源码。找到这几个函数主要干了什么工作。

step1:
//首先引入jol的maven依赖
  <dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.9</version>
        </dependency>
step2:
//编写一个简单的java类,调用jol的方法,打印对象的内存分布
public class JavaObject {
    public static void main(String[] args) {
        System.out.println(VM.current().details());
        System.out.println(ClassLayout.parseClass(A.class).toPrintable());
    }
    public static class A{
        public boolean f;
        public Double d;
    }
}
step3:
分析运行结果:

运行结果截图:


控制台输出

4种锁,对象内存的分布情况。


在这里插入图片描述
通过截图可以看出,JavaObejct对象的对象头占12个字节,f字段为boolen类型字段,占一个字节,因为java是8字节对齐(Objects are 8 bytes aligned),所以需要3字节的填充字段,Double类型占占4个字节,加上填充字段4个字节,所以这个对象的整体长度为24字节。

2、unlocked and unbiased but biasable object
初始化锁定:(偏向锁的一个获取)
这个时候如果有一个线程来获取锁此时这个对象就马上变成了偏置状态,其实对比上一个状态,改变的就是在对象头的栈帧中多了线程的线程ID,其实也就是官宣了,说明我这个对象目前已经是名花有主的人了。当相同的线程再次来获取这个锁的时候,其实这里就不需要在执行CAS操作,只需要一次指针的对比,而指针的对比是很轻量级的,如果对比成功,就继续往下执行,并该线程持有这个锁,如果这个时候有另外一根线程来竞争这个锁,指针的对比就失败那么就发生以下两种情况。结合上面的图我们可以分析;

一、假如持有锁的为线程1,线程2此时来竞争锁,当线程2到达的时候,要么线程1已经释放了锁,处于一个解锁的状态,此时对象的状态就是偏置1,未锁定。但是偏向锁还是存在的,并且线程的id可能还是线程1。

二、如果此时线程1正好持有这个锁,那么此时对象的状态为偏置1,已锁定。

我们结合上图分析一个这两种情况,情况一,如果当前锁对象的状态为不可偏置,未锁定,虚拟机将会执行CAS操作,尝试更新指向线程2,也就是撤销偏向的操作。如果成功的话,那么线程2就持有这个锁对象。
并且后面的标志位变为00,表示轻量级锁定。

3、情况二、说明目前的锁是处于锁定的状态,这时候仍然会升级为轻量级锁,但是此时线程2会获取锁失败,因此这个锁会进行膨胀操作,变为一个重量级锁。
总结:在一个线程轻量级锁定某个对象的时候,如果出现竞争也会导致锁定膨胀,变成重量级锁,也就是没有竞争的偏向锁,没有竞争或者少量竞争就是轻量级锁,大量竞争就会变成重量级锁。

轻量级锁升级为重量级锁的流程图:


流程图

三、锁的分类

1、概念上的锁分类:乐观锁和悲观锁我常常听见,现在来个名次解释说明一下,也是记录一下自己对此的理解:

四、总结:

上述介绍的锁,不是说越轻量级就性能越好,对于不同的锁场景,选择其有点的锁,会大大增加效率。
1、对于偏向锁,加速和解锁不需要额外的消耗,但是如果线程间存在锁的竞争,会带来额外的锁撤销消耗:
2、对于轻量级锁,竞争的线程不会阻塞,但是一个线程始终得不到锁,会一直自旋消耗cpu;
3、重量级锁:线程竞争不会使用自旋,但是线程阻塞,相应时间很慢。

五、参考文章:

1、锁优化技术
2、openJDK
3、infoQ

感谢各位大佬的无私分享

上一篇 下一篇

猜你喜欢

热点阅读