学习笔记-synchronize关键字
本文内容源于视频教程,若有侵权,请联系作者删除。
一、线程安全
1 public class ThreadSafeDemo { 2 3 private static int count = 0; 4 5 public static void main(String[] args) throws InterruptedException { 6 for (int i = 0; i < 1000; i++) { 7 new Thread(() -> increment()).start(); 8 } 9 Thread.sleep(2000); 10 System.out.println(count); 11 } 12 13 private static void increment() { 14 try { 15 Thread.sleep(1); 16 } catch (InterruptedException e) { 17 e.printStackTrace(); 18 } 19 count++; 20 } 21 }
我们的预期值是1000,但实际得到的结果往往小于这个数,这就是线程安全问题。
在increment加上synchronized关键字之后就不会出现上面问题。
二、synchronized关键字
2.1 概念
通常我们称synchronized为同步锁,他能将并行执行的程序转换为串行执行,保证了线程安全。
在1.6版本之前,synchronized被称之为重量级锁,经过1.6版本优化之后,引入了轻量级锁,偏向锁,也就没那么重了。
2.2 使用
synchronized有三种用法:
1.修饰实例方法,作用于当前对象
2.修饰静态方法,作用于当前类
3.修饰代码块,作用于指定加锁对象
2.3原理
2.3.1对象在内存中的布局
对象在内存中的布局包含对象头,实例数据,对其填充,对象头包含了对象标记和类元信息。
对象标记(Mark World):记录锁,GC等对象信息
类源信息(Meta Data):对象指向它的类元数据(Klass)的首地址
对象标记的存储分为以下几种情况
2.3.2 锁升级
hotspot 虚拟机的作者经过调查发现,大部分情况下,加锁的代码不仅仅不存在多线程竞争,而且总是由同一个线程多次获得。由此出现了偏向锁。
2.3.2.1 偏向锁
当一个线程首次访问加了同步锁的代码块时,会在锁对象头中存储当前线程的 ID,后续这个线程再次访问这段加了同步锁的代码块时,不需要再次加锁和释放锁。而是直接比较对象头里面是否存储了指向当前线程的偏向锁,如果存在,直接执行同步代码块。
当已存在线程执行同步代码块,其他线程访问同步代码块时,先比较Mark Word里线程ID是否为当前线程,如果不是,则需要判断偏向锁的标识。如果标识被设置为0(表示当前是无锁状态),则使用CAS竞争锁,如果标识设置成1(表示当前是偏向锁状态),则尝试使用CAS将对象头的偏向锁指向当前线程,触发偏向锁的撤销。撤销锁过程中,若原来持有锁的线程退出了临界区,说明同步代码块已执行完,此时会设置锁对象头为无锁状态,后面的线程可以通过CAS拿到当前锁,否则,升级为轻量级锁。
2.3.2.2 轻量级锁
锁升级为轻量级锁之后,对象的 Markword 也会进行相应的的变化。升级为轻量级锁的过程:
1. 线程在自己的栈桢中创建锁记录 LockRecord。
2. 将锁对象的对象头中的 MarkWord 复制到线程的刚刚创建的锁记录中。
3. 将锁记录中的 Owner 指针指向锁对象。
4. 将锁对象的对象头的 MarkWord 替换为指向锁记录的指针。
经过调查发现大多数情况下,同步代码块的执行时间较短由此轻量级锁通过自旋来实现。简单来说,未拿到锁的线程会在原地等待,不断重试争抢锁,当满足一定条件时不再重试,升级为重量级锁。
2.3.2.3 重量级锁
先看下面例子
1 public class MonitorDemo { 2 3 public static void main(String[] args) { 4 5 } 6 7 static { 8 synchronized (MonitorDemo.class){ 9 10 } 11 } 12 }
打开终端,输入javap -v MonitorDemo.class查看当前类字节码信息
重量级锁通过锁对象头中争抢monitor算法实现,monitorenter 表示去获得一个对象监视器。monitorexit 表示释放 monitor 监视器的所有权,使得其他被阻塞的线程可以尝试去获得这个监视器。监视器锁本质又是依赖于底层的操作系统的 Mutex Lock(互斥锁)来实现的。而操作系统实现线程之间的切换需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么 Synchronized 效率低的原因