概述
synchronized是Java的一个关键字,用来保证多线程下临界区资源的共享安全性
synchronized可以加在方法上(静态方法和普通方法)、代码块上
使用语法:
synchronized (对象) {
// 操作临界资源
}
public synchronized void test() {
// 操作临界资源
}
为什么需要加synchronized?
由于在多线程环境下,对于共享资源(比如共享变量)的修改,可能会导致结果与预期不一致
产生原因:以下有两个线程,分别对static变量进行多次+1和-1操作,最终结果预期为0,但由于单条java指令被编译成字节码后可能对应多条指令,因此会造成数据出错
示例:
static int sum = 0;
static Object object = new Object();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 500; i++) {
sum++;
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 500; i++) {
sum--;
}
});
t1.start();
t2.start();
t1.join(); // 等待t1线程执行完成
t2.join(); // 等待t2线程执行完成
log.debug("{}", sum); // 最后结果不确定
i++对应字节码为:
getstatic i // 取指令
iconst_1
iadd // 加1操作
putstatic i // 存指令
i--对应字节码为:
getstatic i
iconst_1
isub
putstatic i
假如第一个线程取完指令并进行+1操作后,还没来得及存指令;此时发生了线程上下文切换,第二个线程也取指令并完成了-1操作。
此时无论是第一个线程先将1写回变量,还是第二个线程将-1写回变量,结果都不等于0
因此,需要有一种方法保证两个线程不能同时操作临界资源,synchronized就可以解决这样的问题。
Monitor监视器(管程)
首先,synchronized无论是加在方法上,还是加在代码块上,本质上都跟Java中的一个类相对应。
假如存在一个Main类,synchronized加在不同位置时对应的类:
- synchronized void test():加在普通方法上,对应类实例,相当于Main.this
- synchronized static void test():加在静态方法上,对应类对象Main.class
- synchronized (obj):加在任意其他对象上,对应给对象的实例,即obj
因此,Java中的每一个类都需要能够让线程互斥地访问临界资源,当一个线程正在使用临界资源时,其他线程需要处于阻塞状态。
由于需要记录正在使用临界资源的线程、处于阻塞状态的线程、处于等待状态的线程(由于运行条件不满足主动等待的线程),每个类都需要关联一种程序结构,这种程序结构就叫做:管程
从上述得出:
管程需要存储3种不同状态的线程:
- Owner:正在执行的线程
- EntryList:未获取到锁而处于阻塞状态的线程
- WaitSet:处于等待状态的线程
类是如何与管程建立关联的?
每个Java中的类都会包含头部header,header中包含了32bit的MarkWord字段
Java中的类就是通过MarkWord字段与管程相关联的
ptr_to_heavyweight_monitor:指向管程(Monitor)的指针
此时每一个Java类都可以对多个线程进行管理了。