Fellow Travellers

Synchronized

万世威
字数统计: 1.3k阅读时长: 5 min
2019/01/07 Share

Synchronized

作用

​ 以一种简单的策略来防止线程干扰和内存一致性错误,如果一个对象对多个线程可见,该对象变量的所有读取或写入都是通过同步方法完成的。

​ 即:能够保证在同一时刻最多只有一个线程执行该段代码,达到保证并发安全的效果。

  • Synchronized是Java的关键字,被Java原生支持。

  • 是最基本的互斥同步手段。

用法

  • 对象锁
    • 方法锁(锁对象为this当前实例对象)
    • 同步代码锁(自己指定锁对象)
  • 类锁:只有一个Class对象
    • synchronized修饰静态方法
    • 指定锁为Class对象

多线程访问同步方法的7种情况

1、两个线程同时访问一个对象的同步方法

​ 串行执行

2、两个线程访问的是两个对象的同步方法

​ 并行执行,不是一个对象。

3、两个线程访问的是synchronized的静态方法

​ 串行执行,只有一个Class对象

4、同时访问同步方法和非同步方法

​ 串行执行

5、访问同一个对象的不同的普通同步方法

​ 串行,this是同一个

6、同时访问静态synchronized和非静态的synchronized方法

​ 并行执行,锁对象不是同一个

7、方法抛出异常后,释放锁

​ synchronized会自动释放,Lock等不会

三个核心思想

  1. 一把锁只能同时被一个线程获取,没有拿到锁的线程必须等待(对应第1、5种情况)
  2. 每个实例都对应有自己的一把锁,不同实例之前互不影响;
    例外:锁对象是*.class以及synchronized修饰的是static方法的时候,所有对象共用同一把类锁(对应2、3、4、6种情况)
  3. 无论是方法正常执行完毕或者方法抛出异常,都会释放锁(对应第7种情况)

性质

1、可重入:

​ 同一个线程的外层函数获取锁后,内层函数可以直接再次获取该锁。

2、不可中断

原理

1、加锁和释放锁的原理:Monitor

2、可重入原理:加锁次数计数器

3、可见性原理:内存模型

缺点

1、效率低

  • 锁的释放情况少
  • 视图获取锁时不能设置超时时间
  • 不能中断一个正在视图获取锁的线程

2、灵活度较差

  • 加锁和释放锁的时机单一
  • 每个锁仅有单一的对象

3、无法知道是否成功获取到锁

注意点

1、锁对象不能为空、作用域不宜过大、避免死锁

2、尽量使用JUC包下的类,再考虑Synchronized,再考虑Lock

Monitor是什么

  • 是一种用来实现同步的工具
  • 与每个Java对象相互关联,:每个java对象都有一个Monitor相对应
  • Monitor是实现Sychronized的基础

Monitor与Java对象和线程之间关联

  • 如果一个java对象被某个线程锁住,则该java对象的Mark Word字段中LockWord指向monitor的起始地址
  • Monitor的Owner字段会存放拥有相关联对象锁的线程id

说明:

java对象在堆中的基本内存结构,分为三个部分:

1、对象头(header):包括Mark Word(标记字段)和Class Pointer(类型指针)

2、实例数据(instance data):对象真正存储的有效信息,即代码中定义的各种类型的字段内容

3、对齐填充(padding):由HotSpot虚拟机定义对象起始地址必须是8字节整数倍,当不是整数倍时,需要填充数据补齐,因为对补齐的数据访问只需要一次内存访问即可

img

Monitor的结构

  • _count:记录owner线程获取锁的次数。这决定了synchronized是可重入的。
  • _owner:初始时为NULL表示当前没有任何线程拥有该monitor record,当线程成功拥有该锁后保存线程唯一标识,当锁被释放时又设置为NULL。
  • _WaitSet:存放处于wait状态的线程队列
  • _EntryList:存放等待锁而被block的线程队列

Monitor的具体实现

  • Monitor是在jvm底层实现的,底层代码是c++
  • Monitor的enter方法:获取锁
  • Monitor的exit方法:释放锁
  • Monitor的wait方法:为java的Object的wait方法提供支持
  • Monitor的notify方法:为java的Object的notify方法提供支持
  • Monitor的notifyAll方法:为java的Object的notifyAll方法提供支持
1
2
3
4
5
6
7
8
public class SynchronizedTest {
public int i = 0;
public void syncTask(){
synchronized (this){
i++;
}
}
}

javap -verbose SynchronizedTest.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  public void syncTask();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=3, args_size=1
0: aload_0
1: dup
2: astore_1
// 进入同步方法
3: monitorenter
4: aload_0
5: dup
6: getfield #2 // Field i:I
9: iconst_1
10: iadd
11: putfield #2 // Field i:I
14: aload_1
// 退出同步方法
15: monitorexit
16: goto 24
19: astore_2
20: aload_1
21: monitorexit
22: aload_2
23: athrow
24: return
}

存在两个 monitorexit,为了保证在异常时能够释放锁。

另一种形式:

1
2
3
4
5
6
public class SynchronizedTest {
public int i = 0;
public synchronized void syncTask(){
i++;
}
}

采用:ACC_SYNCHRONIZED 标识。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public synchronized void syncTask();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: dup
2: getfield #2 // Field i:I
5: iconst_1
6: iadd
7: putfield #2 // Field i:I
10: return
LineNumberTable:
line 10: 0
line 11: 10
CATALOG
  1. 1. Synchronized
    1. 1.0.1. 作用
    2. 1.0.2. 用法
    3. 1.0.3. 多线程访问同步方法的7种情况
    4. 1.0.4. 三个核心思想
    5. 1.0.5. 性质
    6. 1.0.6. 原理
    7. 1.0.7. 缺点
    8. 1.0.8. 注意点
  2. 1.1. Monitor是什么
  3. 1.2. Monitor与Java对象和线程之间关联
  4. 1.3. Monitor的结构
  5. 1.4. Monitor的具体实现