syncronized的实现原理

相关概念

线程安全的主要诱因

  1. 存在共享数据(临界资源)。
  2. 多个线程共同操作共享数据。

解决问题的根本方法:
同一时刻只有一个线程操作共享数据,其他线程必须等待该线程操作完数据后再对共享资源进行操作。

互斥锁

特性
  1. 互斥性:同一时刻只有一个线程持有某个对象锁。也称操作原子性。
  2. 可见性:锁释放之前,对共享数据的修改,对于后一个获取该锁的线程是可见的。即后一个线程获取该锁时应获得共享数据的最新值。由于JMM,每个线程都有自己的工作内存,会保存主内存的数据副本。

syncronized

syncronized 是满足互斥锁的特性。
注意:syncronized锁的都不是代码,而是对象。

获取锁的方式

根据获取的锁来分类:对象锁和类锁。

获取对象锁的方式:

  1. 同步代码块(syncronized(this),syncronized(类实例对象))。锁的是()中的实例对象。
  2. 同步非静态方法(syncronized method),锁的是当前对象的实例对象。

获取类锁的方式:

  1. 同步代码块(syncronized(类.class))。锁的是()中的类对象(Class对象)。
  2. 同步非静态方法(syncronized static method),锁的是当前对象的类对象(Class对象)。

不同锁的不同表现

  1. 有线程访问对象的同步代码块时,另外的线程可以访问该对象的非同步代码块。
  2. 若锁住的同一个对象,一个线程在访问同步代码块时,另一个访问这个对象同步代码块的线程会被阻塞。
  3. 若锁住的同一个对象,一个线程在访问同步方法块时,另一个访问这个对象同步方法的线程会被阻塞。
  4. 若锁住的同一个对象,一个线程在访问同步方法块时,另一个访问这个对象同步代码块的线程会被阻塞。反之亦然。
       RunnaleDemo runnaleDemo = new RunnaleDemo();
      // 该场景,第二个线程会阻塞
       new Thread( runnaleDemo ,"a").start(); //该线程访问这个对象同步代码块

       new Thread(runnaleDemo ,"b").start() //该线程访问这个对象同步代码块
  1. 同一个类不同的对象,互相不受干扰。
       // 该场景,两个线程不会阻塞
       new Thread( new RunnaleDemo(),"a").start(); //该线程访问同步代码块

       new Thread( new RunnaleDemo(),"b").start() //该线程访问同步代码块
  1. 类锁也是一个特殊的对象锁,因此表现和上述1、2、3、4一致。而由于一个类只有一把对象锁,所以同一个类的对象使用类锁将会是同步的。
       // 该场景,第二个线程会阻塞
        new Thread( new RunnaleDemo(),"a").start(); //该线程访问静态同步代码块

        new Thread( new RunnaleDemo(),"b").start() //该线程访问静态同步代码块
  1. 类锁和对象锁互不干扰。若线程A锁住了一个对象,另一个线程B访问静态同步代码块并不会阻塞。
       // 该场景,两个线程不会阻塞
        new Thread( new RunnaleDemo(),"a").start(); //该线程访问同步代码块

        new Thread( new RunnaleDemo(),"b").start() //该线程访问静态同步代码块
       // 该场景,两个线程不会阻塞
        RunnaleDemo runnaleDemo = new RunnaleDemo();

        new Thread(runnaleDemo,"a").start(); //该线程访问同步代码块

        new Thread(runnaleDemo,"b").start(); //该线程访问静态同步代码块

底层实现

syncronized实现有两个基础

  1. Java对象头
  2. Monitor

对象头

对象在内存的布局:
- 对象头
- 实例数据
- 对其填充

对象头分两部分

对象头结构 说明
Mark Work 存储对象的hashcode,分代年龄,锁状态标识,线程持有的锁、偏向线程ID、是否偏向等信息。
Class Metadata Address 类型指针指向对象的类元数据,JVM通过这个确定这个对象属于哪个类。

Monitor

每个Java对象都有一个Monitor。
Monitor包含 锁池、等待池、owner、count 。
owner默认为NULL,当有线程获取该对象锁时,owner指向获取该对象锁的线程。
count默认0,用来计数。执行monitorenter获取则+1,释放锁monitorexit则-1。

syncronized关键字解析只会,会在同步代码块前后形成monitorenter和monitorexit这两个字节码。

Java线程是映射到操作系统的原生线程上的,如果要阻塞或唤起一个线程,都需要操作系统来帮忙,这就需要从用户态转换到核心态,因此线程状态转换需要消耗很多处理器时间。所以,syncronized是一个重量级锁。不过JDK6以后,JVM已对其做了优化,譬如自旋锁、自适应自旋锁、锁粗化、偏向锁、锁消除、轻量级锁等,使其性能得到很大的提升。

锁优化

自旋锁和自适应锁

自旋锁
通过让线程执行忙循环等待锁释放,不让出CPU资源,避免线程切换。
缺点:若锁被其他线程占用太长时间,会带来更大的性能开销,白白消耗CPU资源。
自适应自旋锁
  • 自旋次数不固定
  • 由前一次在同一个锁上的自旋时间和锁的拥有者状态来决定。

锁消除

JIT编译时,对运行上下文扫描,去除不可能存在竞争的锁。

锁粗化

通过扩大加锁范围,避免反复加锁和解锁。

syncronized的四种状态

  • 无锁
  • 偏向锁:减少同一个线程获取锁的代价。–>大多数情况下,锁不存在竞争,且总由同一个线程获取。
    一个线程获取锁,就进入偏向锁。此时对象头的Mard word就变成偏向结构,记录了该线程ID。当该线程再次请求锁时,只需检查mark Word是不是偏向锁以及线程ID是否与markword中一致。
  • 轻量级锁
    当有其他线程竞争偏向锁状态的对象时,偏向锁就升级成轻量级锁。
  • 重量级锁

你可能感兴趣的:(Java)