# synchronized

synchronized 对象锁
采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】,其它线程再想获取这个【对象锁】时就会阻塞住。这样就能保证拥有锁的线程可以安全的执行临界区内的代码,不用担心线程上下文切换

为了避免临界区的竞态条件发生,有多种手段可以达到目的:阻塞式的解决方案:synchronized,Lock
非阻塞式的解决方案:原子变量

语法

synchronized(对象) // 线程 1, 线程 2 (blocked)
{
// 临界区
}

demo

static int counter = 0;
static final Object room = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
synchronized (room) {
counter++;
}
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
synchronized (room) {
counter--;
}
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
log.debug("{}",counter);
}

思考

synchronized 实际是用对象锁保证了临界区内代码的原子性,临界区内的代码对外是不可分割的,不会被线程切换所打断。
为了加深理解,请思考下面的问题

如果把 synchronized (obj) 放在 for 循环的外面,如何理解?-- 原子性
如果 t1 synchronized (obj1) 而 t2 synchronized (obj2) 会怎样运作?-- 锁对象
如果 t1 synchronized (obj) 而 t2 没有加会怎么样?如何理解?-- 锁对象

# 方法上的 synchronized

class Test{
	public synchronized void test() {
		}
}
// 等价于 锁住了 this 对象
class Test{
	public void test() {
		synchronized(this) {
		}
	}
}

锁住 static 方法,相当于锁住了类对象。

class Test{
	public synchronized static void test() {
	}
}
// 等价于
class Test{
	public static void test() {
		synchronized(Test.class) {
		}
	}
}

类对象和 this 对象不一样。

# 变量的线程安全分析

成员变量和静态变量是否线程安全?
如果它们没有共享,则线程安全
如果它们被共享了,根据它们的状态是否能够改变,又分两种情况
如果只有读操作,则线程安全
如果有读写操作,则这段代码是临界区,需要考虑线程安全

局部变量是线程安全的
但局部变量引用的对象则未必
如果该对象没有逃离方法的作用访问,它是线程安全的
如果该对象逃离方法的作用范围,需要考虑线程安全

方法访问修饰符带来的思考,如果把 method2 和 method3 的方法修改为 public 会不会代理线程安全问题?
情况 1:有其它线程调用 method2 和 method3
情况 2:在 情况 1 的基础上,为 ThreadSafe 类添加子类,子类覆盖 method2 或 method3 方法。

常见线程安全类

String
Integer
StringBuffer
Random
Vector
Hashtable
java.util.concurrent 包下的类
这里说它们是线程安全的是指,多个线程调用它们同一个实例的某个方法时,是线程安全的。

Hashtable table = new Hashtable();
// 线程 1,线程 2
if( table.get("key") == null) {
	table.put("key", value);
}
// 只能保证单句语句线程安全,组合在一起可能会被重写

不可变类线程安全性
String、Integer 等都是不可变类,因为其内部的状态不可以改变,因此它们的方法都是线程安全的
String 有 replace,substring 等方法【可以】改变值啊,那么这些方法又是如何保证线程安全的呢?直接赋值新对象。

# Monitor 概念

Monitor 是 java 对象头 (object header)

以 32 位虚拟机为例

普通对象:
Object Header (64 bits) = Mark Word (32 bits) + Klass Word (32 bits)

数组对象
Object Header (96 bits) = Mark Word (32bits) + Klass Word (32bits) + array length (32bits)

Normal Mark Word (32 bits) = hashcode:25 | age:4 | biased_lock:0 | 01
Biased Mark Word (32 bits) = thread:23 | epoch:2 | age:4 | biased_lock:1 | 01

# Monitor 原理

Monitor 被翻译为监视器或管程
每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的 Mark Word 中就被设置指向 Monitor 对象的指针

Monitor 由 WaitSet、EntryList、Owner 组成。
Owner:Monitor 只能有一个 Owner
EntryList:在 Owner 线程上锁的时候,又有新的线程来执行,就会进入 EntryList BLOCKED
WaitSet:是之前获得过锁,但条件不满足进入 WAITING 状态的线程,后面讲 wait-notify 时会分析

# synchronized 原理

# 轻量级锁

轻量级锁的使用场景:如果一个对象虽然有多线程要加锁,但加锁的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化。
轻量级锁对使用者是透明的,即语法仍然是 synchronized
假设有两个方法同步块,利用同一个对象加锁

static final Object obj = new Object();
public static void method1() {
	synchronized( obj ) {
	// 同步块 A
	method2();
	}
}
public static void method2() {
	synchronized( obj ) {
	// 同步块 B
	}
}

执行方法 1 的时候,栈帧先创建一个锁记录(Lock Record)对象,每个线程都的栈帧都会包含一个锁记录的结,内部可以存储锁定对象的 Mark Word。让锁记录中 Object reference 指向锁对象,并尝试用 cas 替换 Object 的 Mark Word,将 Mark Word 的值存入锁记录。

如果替换成功,加锁。
如果不成功,则有两种情况:
如果是其它线程已经持有了该 Object 的轻量级锁,这时表明有竞争,进入锁膨胀过程。
如果是自己执行了 synchronized 锁重入,那么再添加一条 Lock Record 作为重入的计数。
当退出 synchronized 代码块(解锁时)如果有取值为 null 的锁记录,表示有重入,这时重置锁记录,表示重
入计数减一

当退出 synchronized 代码块(解锁时)锁记录的值不为 null,这时使用 cas 将 Mark Word 的值恢复给对象
头,两种情况:
成功,则解锁成功
失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程

image-20210606215410239

# 锁膨胀

如果在尝试加轻量级锁的过程中,CAS 操作无法成功,这时一种情况就是有其它线程为此对象加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁。

static Object obj = new Object();
public static void method1() {
	synchronized( obj ) {
	// 同步块
	}
}

锁膨胀,即,已有轻量级锁的情况下,再加轻量级锁:
即为 Object 对象申请 Monitor 锁,让 Object 指向重量级锁地址,然后自己进入 Monitor 的 EntryList BLOCKED
当 Thread-0 退出同步块解锁时,使用 cas 将 Mark Word 的值恢复给对象头,失败。这时会进入重量级解锁
流程,即按照 Monitor 地址找到 Monitor 对象,设置 Owner 为 null,唤醒 EntryList 中 BLOCKED 线程

# 自旋

重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步
块,释放了锁),这时当前线程就可以避免阻塞。

自旋就是重复几次调用锁,如果几次后还是失败,该阻塞还是得阻塞。

# 偏向锁

-->