标签:this system nts rup 更改 ice param 对象 内存屏障
可见性,原子性,有序性
Java内存模型(Java Memory Model)描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量这样的底层细节。
每个线程都有自己独立的工作内存,里面保持该线程使用到的变量副本。
线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接从主内存中进行读写。
不同线程之间无法直接访问其他线程工作内存的变量,所以线程间变量值的传递需要通过主内存来完成。
要实现共享变量的可见性,必须保证两点:
JMM关于synchronized的两条规定:
public class SyncExample {
//修饰代码快
public void test1(String name){
synchronized (this){
for (int i = 0; i < 10; i++){
System.out.println(name + ":" + i);
}
}
}
//修饰方法
public synchronized void test2(String name){
for (int i = 0; i < 10; i++){
System.out.println(name + ":" +i);
}
}
public static void main(String[] args) {
final SyncExample sync1 = new SyncExample();
SyncExample sync2 = new SyncExample();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> sync1.test1("sync1"));
executorService.execute(() -> sync2.test1("sync2"));
}
}
//结果
sync2:0
sync1:0
sync2:1
sync1:1
sync2:2
sync1:2
sync1:3
sync2:3
sync1:4
sync2:4
sync1:5
sync2:5
sync1:6
sync2:6
sync1:7
sync2:7
sync1:8
sync2:8
sync1:9
sync2:9
sync1和sync2的test1交替执行,说明同步代码块作用在当前对象,不同对象调用互不影响。
当换成test2()时,结果和上相同,说明synchronized修饰一个方法时,作用和同步代码块相同,都是作用当前对象。
所以,如果一个方法中是一个完整的同步代码快,它和synchronized修饰一个方法是等同的。
当修饰一个类时
public static void test3(){
synchronized (SyncExample.class){
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread() + "test3 :" + i + " ");
}
}
}
Thread[pool-1-thread-1,5,main]test3 :0
Thread[pool-1-thread-1,5,main]test3 :1
Thread[pool-1-thread-1,5,main]test3 :2
Thread[pool-1-thread-1,5,main]test3 :3
Thread[pool-1-thread-1,5,main]test3 :4
Thread[pool-1-thread-2,5,main]test3 :0
Thread[pool-1-thread-2,5,main]test3 :1
Thread[pool-1-thread-2,5,main]test3 :2
Thread[pool-1-thread-2,5,main]test3 :3
Thread[pool-1-thread-2,5,main]test3 :4
从结果上可以看出,修饰一个类时,作用于这个类的所有对象。
volatile通过加入内存屏障和禁止重排序优化来实现可见性
volatile变量在每次被线程访问时,都强迫从主内存中读取该变量的值,而当发生变化时,会强迫线程将最新的值刷新到主内存。
private int number = 0;
number ++;
number ++ 不是原子操作,可以分为三步
1.读取number的值,2.将number值+1,3.写入最新的numnber的值
public class TestAtomicDemo {
public static void main(String[] args) {
AtomicDemo demo = new AtomicDemo();
for (int i = 0; i < 10; i ++){
new Thread(demo).start();
}
}
}
class AtomicDemo implements Runnable{
private volatile int serialNumber = 0;
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +":"+getSerialNumber());
}
public int getSerialNumber(){
return serialNumber ++;
}
}
Thread-0:0
Thread-1:1
Thread-3:3
Thread-2:2
Thread-5:4
Thread-4:5
Thread-6:7
Thread-7:7
Thread-8:6
Thread-9:8
可以看到Thread6和Thread7的执行结果相同。
原子性:提供了互斥访问,同一时刻,只能由一个线程对它进行操作。
jdk1.5后java.util.concurrent.atomic包下面提供可常用的原子变量。CAS(Compare-And-Swap)算法保证数据原子性。
private volatile AtomicInteger serialNumber = new AtomicInteger(0);
public int getSerialNumber(){
return serialNumber.incrementAndGet();
}
AtomicInteger的incrementAndGet()方法是如何保证原子性的呢?
private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
private static final long VALUE = U.objectFieldOffset(AtomicInteger.class, "value");
private volatile int value;
/**
* Atomically increments the current value,
* with memory effects as specified by {@link VarHandle#getAndAdd}.
*
* <p>Equivalent to {@code addAndGet(1)}.
*
* @return the updated value
*/
public final int incrementAndGet() {
return U.getAndAddInt(this, VALUE, 1) + 1;
}
从源码(我的是JDK10)中可以看到AtomicInteger使用了一个Unsafe类的getAndAddInt方法。
/**
* Atomically adds the given value to the current value of a field
* or array element within the given object {@code o}
* at the given {@code offset}.
*
* @param o object/array to update the field/element in
* @param offset field/element offset
* @param delta the value to add
* @return the previous value
* @since 1.8
*/
@HotSpotIntrinsicCandidate
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!weakCompareAndSetInt(o, offset, v, v + delta));
return v;
}
getIntVolatile方法,如果没有其他线程来处理Object o,这个方法返回只v就等于offset。
weakCompareAndSetInt方法就是CAS算法核心
offset为内存值,v是预估值,v+delta为更新值,当且仅当offset==v,才会+1操作。
Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序的过程不会影响单线程的执行结果,却会影响到多线程并发执行的正确性。
volatile, synchronized,Lock,可以保证有序性
##### happens-before原则
标签:this system nts rup 更改 ice param 对象 内存屏障
原文地址:https://www.cnblogs.com/Godfery/p/10353625.html