招人时需要准备一些面试问题,有些内容虽然日常在用,但是没有系统化理解,面试时也很难评判对方回答的是否准确。因此,重新梳理了volatile与synchronized。
volatile
volatile 是最轻量级的同步机制。Java内存模型对volatile专门定义了一些特殊访问规则。
1, 线程可见性, 当一个线程修改了被volatile修饰的变量后,无论是否加锁,其他线程都可以立即看到最新的修改, 而普通变量做不到这一点。
2, 禁止指令重排序,普通变量仅仅保证该方法的执行过程所有依赖赋值结果的地方都能获得正确的结果,而不能保证变量赋值操作的顺序与代码的执行书序一致。
volatile仅仅解决可见性问题,不能保证互斥。
注意事项:
如果一个变量i是volatile,那么jvm不保证多线程执行i= i +1是线程安全的。 因为i= i +1是先读取i(一定能读取最新值),然后加上1,最后再赋值(赋值的结果其他线程是立即可见)。 可以想象,当ThreadA执行了读取i(当前值5),然后加上1,将6赋值给i, ThreadB也在能在同时执行i= i +1,当ThreadB读取i的时候,因为ThreadA的赋值操作还没有执行,ThreadB读取的到的可能是5,最后虽然ThreadA和ThreadB都执行了+1操作,但是最终结果是6, 而不是期望值7。
volatile只保证可见性和禁止重排序。也就是i=i+1操作中读取i和给i赋值是单独的两个院子操作,而i=i+1不是原子操作。
截图如下(20个线程, 每个线程执行1万次i=i+1), 代码比较简单在文末部分。
synchronized
synchronized保证多个线程有序执行同一段代码, 相当于"隐式"锁.
具体使用方式:
对普通同步方法, 锁时当前实例对象
对静态同步方法,锁时当前的Class对象
对于同步方块,锁是Synchronized括号中的对象。
从Jvm中可以看出,Synchronized是基于进入和退出 monitor对象来实现方法同步和代码块同步。 代码块同步使用monitorenter和monitorexit。 方法同步使用另外一种。
使用建议
1, 如果变量只有一个线程写,多个线程读,那就使用volatile。
2, 如果变量是多个线程读写,那就使用Synchronized
参考
1, https://jorosjavajams.wordpress.com/volatile-vs-synchronized/
2,https://stackoverflow.com/questions/3519664/difference-between-volatile-and-synchronized-in-java
3,https://medium.com/google-developer-experts/on-properly-using-volatile-and-synchronized-702fc05faac2
volatile测试代码(简要说明volatile只保证可见性,并不提供i=i+1这种操作的原子性)
package com.yq.myvolatile;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Created by EricYang on 2019/7/13.
*/
@Slf4j
public class VolatileDemo {
private volatile int count = 0;
public static void main(String[] args) {
VolatileDemo demo = new VolatileDemo();
System.out.println("initial value");
demo.showCountValue();
long begin = System.currentTimeMillis();
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(20);
for (int i = 0; i < 20; i++) {
MyCounterTask task = new MyCounterTask(demo, "Task-" + i);
executor.execute(task);
}
System.out.println("submit to threadPool done ");
executor.shutdown();
try {
TimeUnit.SECONDS.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("final value after executed by multi-thread.");
demo.showCountValue();
}
public void add() {
count = count + 1;
}
public int getCount() {
return count;
}
private void showCountValue() {
System.out.println("count=" + count);
}
}
示例代码仅供测试,只是用来说明问题的,不是生产可用代码。
package com.yq.myvolatile;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
@Slf4j
@Getter
class MyCounterTask implements Runnable {
private VolatileDemo demo;
private String name;
public MyCounterTask(VolatileDemo demo, String name) {
this.demo = demo;
this.name = name;
}
@Override
public void run() {
long threadId = Thread.currentThread().getId();
for (int i = 0; i < 10000; i++) {
demo.add();
}
log.info("done a task. count={}, threadId={}, name={}" , demo.getCount(), threadId, name);
}
}