volatile :ˈvälətl
synchronized :ˈsiNGkrəˌnaīzd

  • JAVA内存模型(JAVA Memory Model JMM)

JAVA内存模型定义了程序中各个变量的访问规则,即JVM将共享变量从内存存取的底层细节。
主内存是线程共享的区域例如堆和方法区,而工作内存实现线程私有的,例如PC寄存器、虚拟机栈
JMM规定所有共享变量都存储在主内存中,每条线程拥有自己的工作内存(比作缓冲区的概念),线程的工作内存中保存了主内存变量的拷贝副本。线程对共享变量的操作都在工作内存中进行,工作内存在线程是私有的,其他线程无法访问,变量的传递都是通过主内存来完成。

主内存与工作内存同步的中操作:

  1. lock(锁定):作用于主内存的变量,把一个变量标识为一条线程的独占状态;
  2. unlock(解锁):作用于主内存的变量,将一个变量从lock状态解锁,释放出来的该变量才能被其他线程lock;
  3. read(读取):作用于主内存的变量,将一个变量的值从主内存传输大工作内存,以便于后面的load操作使用;
  4. load(载入):作用于工作内存,把read操作从主内存得到的变量值放到工作内存的变量副本中;
  5. use(使用):作用于工作内存的变量,把一个工作内存中的变量值传递给执行引擎,当虚拟机遇到一个需要使用变量的值的字节码指令时就会执行该操作;
  6. assign(赋值):作用于工作内存的变量,把一个从执行引擎收到的值赋值给工作内存的变量,当虚拟机遇到一个需要给变量赋值的字节码指令时就会执行该操作;
  7. store(存储):作用于工作内存的变量,把一个工作内存中的变量的值传送到主内存中,以便于随后的write操作;
  8. write(写入):作用于主内存的变量,把store操作从工作内存中的一个变量的值传送到主内存的变量中。

3个特性:
1.原子性(Atomicity):原子代表不可分割的最小单位。原子性指一个操作或多个操作要么全部执行,要么就都不执行。例如银行转账操作 A账户减去10000元和B账户增加10000元必须全部执行,否则就会出现错误,这两个操作不可再分割。
read、load、use、assign、store、write操作都保证了原子性的操作,基本数据类型的读取大致都具有原子性。更大范围保证原子性JMM提供了lock和unlock操作,JVM提供了更高层次的字节码指令monitorenter和monitorexit来隐式使用这两个操作,这两个字节码指令反映到Java代码中就是同步块——synchronized关键字,因此在synchronized块之间的操作也具备原子性。
2.可见性(Visibility):一个线程中堆共享变量(类中的成员变量或静态变量)的修改,其他线程立即得知这个修改。
保证可见性的操作:
volatile:volatile的特殊规则保证了新值立即同步到主内存中,以及每次使用前立即从主内存中刷新。
synchronized:synchronized关键字在释放锁之前,必须把此变量同步回主内存中(执行了store、write操作)。
final:final修饰的变量,一旦完成初始化,其他线程就能看见final字段的值。
3.有序性(Orderly):有序性主要涉及了指令重排序现象和“工作内存与主内存同步延迟”现象。总结为一句话:如果在本线程内观察,所有的操作都是有序的;如果在一个线程中观察另一个线程的所有的操作都是无序的。
为了执行的效率,有时候会发生指令重排,这在单线程中指令重排之后的输出与我们的代码逻辑输出还是一致的。但在多线程中就可能发生问题,volatile在一定程度上可以避免指令重排。

  • volatile和synchronized

    synchronized保证了原子性和可见性,JMM中synchronized块执行流程清空工作内存->在主内存中拷贝变量值到工作内存的副本中->执行完代码->将更新后的共享变量值刷新到主内存中->释放同步锁
    volatile保证了可见性和有序性。线程中volatile修饰的变量对其他线程共享变量的修改是可立即看见的。有序性上volatile可以一定程度避免指令重排,volatile在生成指令上会加一个lock前缀,这个前缀相当于内存屏障可以避免指令重排序不能把后面的指令重排序到内存屏障之前的位置。
    synchronized作用于代码块和方法,而volatile是变量修饰符。
    volatile不会造成线程的阻塞,而synchronized可能会造成线程阻塞。

  • synchronized用法
    1.(普通class)当一个线程访问对象中的synchronized(this)同步代码块时,其他试图访问这个对象的synchronized(this)同步代码块的线程将被阻塞。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Test {
public Test() {
}

public void runTest(String threadName) {
synchronized (this) {
for (int counter = 0; counter < 5; counter++) {
System.out.println(threadName + " counter is" + counter);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Test test = new Test();
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
test.runTest("threadA");
}
});

Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
test.runTest("threadB");
}
});

threadA.start();
threadB.start();

threadA counter is0
threadA counter is1
threadA counter is2
threadA counter is3
threadA counter is4
threadB counter is0
threadB counter is1
threadB counter is2
threadB counter is3
threadB counter is4

2.当一个线程访问对象的synchronized(this)代码块时,其他线程可以访问该对象的其他非同步代码块。

3.当一个线程访问对象的synchronized(obj)代码块时,相当于给obj对象加了锁,那么访问其他试图访问obj对象的线程将会阻塞,直到该对象对obj的访问结束。synchronized(obj)可以理解为哪个线程拿到了obj的锁就可以访问同步代码块的代码。

3.当synchronized修饰一个普通方法时,同步代码块对整个方法生效。
4.当synchronized修饰一个静态方法时,同步锁生效的是整个类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Test {
public Test() {
}

public synchronized static void runTest(String threadName) {
for (int counter = 0; counter < 5; counter++) {
System.out.println(threadName + " counter is" + counter);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Test test = new Test();
Test test2 = new Test();
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
test.runTest("threadA test");
}
});

Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
test2.runTest("threadB test2");
}
});

threadA.start();
threadB.start();

threadA test counter is0
threadA test counter is1
threadA test counter is2
threadA test counter is3
threadA test counter is4
threadB test2 counter is0
threadB test2 counter is1
threadB test2 counter is2
threadB test2 counter is3
threadB test2 counter is4

5.当synchronized修饰的是一个类时,场景与4相同,属于类锁。

synchronized修饰类和静态方法时它作用在类上,属于类锁;修饰普通方法、代码块时它作用在类的对象上,属于对象锁,类锁和对象锁作用的目标时不同的,所以它两互不干扰