1.线程涉及到主存和工作内存的概念:
- CPU在读取运算所需的数据、存储运算得到的数据结果等操作的时候,会涉及到与内存的读写交互,但CPU运算速度远高于内存的读写速度,为了解决这种速度不匹配的情况,增加了多级高速缓存;这样,运算前,将数据复制到高速缓存,运算后,将结果同步写回主存(对于JVM来说,这个主存是指Java堆内存);
-
CPU在计算时,并不总是从主存中读数据(数据是指共享变量,存在线程间共享和竞争的关系),而是通过从多级缓存中拿数据,从而提高执行效率。读取数据的优先级是:寄存器 > 高速缓存(多级) > 主存;
-
所以线程的工作内存是个抽象概念,指的是寄存器和(多级)高速缓存,线程的工作内存中保存的是主存中数据的备份;
-
当多线程访问主存中的某个经常被访问的数据时,是先从线程的工作内存中读写数据,然后在某个合适的时候再写回主存中,所以每个工作线程中的副本变量不一定是最新的(对其他线程是不可见的),这就是上面多线程读写count字段出现线程不安全的原因;
所以需要加锁保证多线程间数据同步,volatile字段的作用只能保证数据的可见性和有序性,即:线程A对volatile count字段执行写操作后,会立即将count值写回到主存中,并通知其他线程count值发生变化,这样count对其他线程就是可见的了,但是volatile并不会保证操作的原子性;
2.4种使用方式
public class Foo {
private int count = 0;
private Object o = new Object();
public static synchronized void t1() {
// 静态方法加synchronized和t2()的方式争用的都是类锁,和对象锁唯一的区别就是,内存中类锁只有一把;
}
public void t2() {
synchronized (Foo.class) {
}
}
public synchronized void t3() {
}
public void t4() {
synchronized (this) {
}
}
public void t5() {
// 如果使用下面的o对象,多线程并发访问t5(),结果是什么?
// Object o = new Object();
synchronized (o) {
count++;
}
}
}
3.总结
- Java代码添加synchronized修饰符后,编译器编译成字节码时,会在进入同步代码块前添加monitorenter指令,退出代码块时添加monitorexit指令;
- 在机器码执行模式下,monitorenter这个Java指令对应的汇编代码被执行时,执行了quick_lock_entrypoints.cc文件中的artLockObjectFromCode()函数;
- artLockObjectFromCode()函数调用了obj->MonitorEnter(self)函数,内部调用Monitor::MonitorEnter()函数;
- Monitor::MonitorEnter()函数根据判断,如果同一个线程获取锁,则更新lock count;如果多线程争用锁,则看情况,严重的话使用ART提供的Mutex互斥锁;
- 而Mutex的实现是使用了系统调用futex()函数,所以在多线程争用锁的时候,最终是由Linux的系统调用futex()保证多线程同步机制的;