(三)synchronize关键字和Java内置锁

134 阅读3分钟

临界区资源

多个线程共享临界区资源(数据,设备等),但任意一个时刻内,只能有一个线程访问临界区资源

竞态条件

Race Condition,可能是由于临界区资源没有互斥访问而导致的异常条件

如果多个线程在临界区代码段并发执行的结果可能因为执行的顺序不同而不同,我们就说临界区出现了竞态条件问题

临界区代码段

访问临界区资源的代码段

Java对象

Java对象可以分为两类

  • 实例对象

  • Class对象

  • 每个对象都有一把监视锁,任何Java对象都可以作为synchronized的同步锁

Class对象

  • 每个类运行时的信息用Class对象表示

  • 含有:类名称,继承关系,字段,方法有关的信息

  • JVM将每个类加载到方法区内存时,会为其创建Class对象

  • 一个类的Class对象是唯一的

  • 没有公共的构造方法,不能显示初始化Class对象

  • 懒加载:第一次使用该类时才使用类加载器加载

synchronize关键字

修饰实例方法(同步方法)

  • public void synchronized method()

  • 需要获取的是当前实例对象(Object对象)的锁

  • Object对象的监视锁叫对象锁

修饰静态方法(静态同步方法)

  • public static void synchronized method()

  • 需要获取的是当前类对象(Class对象)的锁

  • Class对象的监视锁叫类锁

修饰代码块

  • synchronized(locko){ // some code }

  • 需要获取的是 locko 对象的的监视锁 (locko 对象可以是实例对象也可以是类对象)

  • 同步的粒度较细

synchronized 的 锁 何时释放?

  • 正确执行完同步方法或同步代码块时

  • 非正常退出同步方法或代码块时

实现原理

  • synchonized关键字修饰方法时,在字节码文件里的那个被修饰的方法上会被标记为ACC_SYNCHRONIZED

  • synchonized关键字修饰代码块时,在字节码文件里的那个被修饰的代码块的前后会被标记为monitorentermonitorexit

  • 本质上都是对监视锁的获取

偏向锁原理

  • 在mark word内维护偏向的线程ID

  • 如果不存在线程竞争的一个线程获得了锁,那么锁就进入偏向状态

  • 如果有一线程想要获得该对象的监视锁,直接判断偏向ID是否相等,相等的话直接进入同步块,不相等的话使用CAS操作,如果CAS操作失败,膨胀为轻量级锁

轻量级锁原理

  • 轻量级锁就是一种自旋锁

  • mark word中维护指向锁记录的指针

  • CAS自旋的实现方式:

    • 普通自旋

    • 适应性自旋

  • 如果超过自旋最大时间则膨胀为重量级锁

重量级锁原理

基于操作系统的互斥锁来实现,涉及到用户态和内核态的切换,效率很低

线程间的通信

对象的wait()方法和notify()方法

wait()

阻塞当前线程直到被唤醒

  • 当前线程加入对象监视器的WaitSet

  • 放弃Owner权利

  • 线程状态变为Waiting

notify()

  • 唤醒对象监视器的WaitSet中的线程

  • 从监视器的WaitSet移动到EntrySet

  • Waiting 变为 Blocked

  • 具备抢锁资格,重新抢锁