学习笔记-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 效率低的原因

 

posted @ 2020-08-27 23:57  落雨有清·风  阅读(34)  评论(0编辑  收藏  举报