一, 概念
java 中通过利用锁的机制来实现同步.
锁机制有两种特性:
1. 互斥性: 即在同一时间内只允许一个线程持有某个对象锁, 通过这种特性来实现多线程总的协调机制, 这样在同一时间内只有一个线程对需要同步的代码块进行访问.
2. 可见性: 必须确保在锁释放前, 对共享变量所作的修改, 对于随后获得该锁的另一线程是可见的 (即在获得锁时应获得最新共享变量的值), 否则另一线程可能实在本地缓存的某个副本上继续操作 而引起不一致.
二, synchronized 的用法
对于普通的同步方法, 锁是当前的实例对象.
对于静态的同步方法, 锁是当前类的 Class 对象.
对于同步方法块, 锁是 Synchronized 括号中配置的对象.
在 java 中, 每个对象都会有一个 monitor 对象, 监视器.
1) 某一个线程占有这个对象的时候, 先看 monitor 的计数器是不是 0, 如果是 0 表示还没有线程占有, 这个时候该线程占有这个对象, 并且对这个对象的 monitor 加 1; 如果不为 0, 表示这个锁已经被其他线程 占 有, 这个线程需要等待锁释放. 当线程对这个对向释放占有权的时候, monitor-1;
2) 同一个线程可以对同一个对象进行多次加锁,+1.
三, synchronized 原理
从 JVM 规范中可以看到 Synchronized 在 JVM 中的实现原理, JVM 基于进入和退出Monitor对象来实现方法的同步和代码块的同步.
代码块的同步是通过使用 monitorenter 和 monitorexit 两个指令来实现的. monitorenter 指令是在编译后插入到同步代码块开始的位置, 而 monitorexit 是插入到方法的结束处和异常处. 当线程执行到 monitorenter 指令时, 将会尝试获取对象所对应的 monitor 的所有权, 即尝试获取对象的锁.
同步方法是通过 ACC_SYNCHRONIZED 来实现的.
四, java 虚拟机对 synchronized 的优化
java1.6 为了减少获得锁和释放锁带来的性能消耗, 引入了偏向锁和轻量级锁. 因此锁有四种状态, 级别从高到低依次是: 无锁状态, 偏向锁, 轻量级锁, 重量级锁.
synchronized 用的锁是存在 java 对象头了的. 一个对象实例包含: 对象头, 实例变量, 填充数据.
五, 锁的升级和对比
1. 偏向锁
存在原因: 大多数情况下, 锁不仅不存在多线程竞争, 而且总是又同一线程获取, 为了让线程获得锁的代价更低而引入了偏向锁.
当一个线程访问同步代码块时, 会在对象头和栈帧的锁记录中存储偏向的线程 ID, 以后该线程在进入和退出同步块时不需要进行 CAS 操作来加锁和解锁, 只需简单的测试一下对象头的 Mark Word 里是否存储着指向当前线程的锁. 如果测试成功, 表示线程获得了锁.
2. 轻量级锁
线程在执行同步块之前, JVM 会先在当先线程的栈帧中创建用于存储锁记录的空间, 并将对象头中的 Mark Word 复制到锁记录中, 然后线程尝试使用 CAS 将对象头中的 Mark Word 替换为对象头中锁记录的指针. 如果成功, 当前线程获得锁, 如果失败, 表示其他线程竞争锁, 当前线程尝试使用自旋来获取锁.
轻量级解锁时, 会使用原子的 CAS 操作将 Displaced Mark Word 替换回到对象头, 如果成功, 则表示没有线程竞争锁. 如果失败, 表示当前锁存在竞争, 锁就会膨胀成重量级锁.
来源: http://www.bubuko.com/infodetail-2998422.html