转载请以链接形式标明出处: 本文出自:103style的博客
本文是 看到 这篇文章中 “volatile 的意义?” 那一小节提供的一个例子引发的测试。
volatile 的意义?
- 防止CPU指令重排序
volatile有两条关键的语义:
- 保证被volatile修饰的变量对所有线程都是可见的
- 禁止进行指令重排序
下面的例子是用来证明下面这个观点的后半句是错误的。
由于volatile修饰的变量在各个线程里都是一致的,所以基于volatile变量的运算在多线程并发的情况下是安全的。
原测试示例
例子是这样的:
代码语言:javascript复制public class Test {
private volatile int start = 0;
public static void main(String[] args) {
new Test().test();
}
private void test() {
Runnable runnable = new Runnable() {
@Override
public void run() {
add();
}
};
for (int i = 0; i < 10; i ) {
Thread thread = new Thread(runnable);
thread.start();
}
System.out.println("start = " start);
}
void add() {
for (int i = 0; i < 10; i ) {
start ;
}
}
}
大家可以看看 最后运行的结果输出的 start
值是多少 ?
给 add 方法加上 synchronized 之后
给 add()
方法加上 synchronized
之后输出的 start
值又是多少呢 ?
public class Test {
private volatile int start = 0;
public static void main(String[] args) {
new Test().test();
}
private void test() {
Runnable runnable = new Runnable() {
@Override
public void run() {
add();
}
};
for (int i = 0; i < 10; i ) {
Thread thread = new Thread(runnable);
thread.start();
}
System.out.println("start = " start);
}
synchronized void add() {
for (int i = 0; i < 10; i ) {
start ;
}
}
}
两个测试的结果
两个结果都是 100? 还是只有第二个是100 ?
其实两个结果都不是100,这是因为 main
方法对应的线程 不会等待 新创建的线程执行完。
我们可以加上时间输出看看试试:
代码语言:javascript复制public class Test {
private volatile int start = 0;
public static void main(String[] args) {
new Test().test();
}
private void test() {
Runnable runnable = new Runnable() {
@Override
public void run() {
add();
System.out.println("new thread: " System.currentTimeMillis());
}
};
for (int i = 0; i < 10; i ) {
Thread thread = new Thread(runnable);
thread.start();
}
System.out.println("start = " start);
System.out.println("main: " System.currentTimeMillis());
}
void add() {
for (int i = 0; i < 10; i ) {
start ;
}
}
}
控制台打印的结果:
代码语言:javascript复制new thread: 1583915204005
new thread: 1583915204005
new thread: 1583915204007
new thread: 1583915204007
new thread: 1583915204007
new thread: 1583915204007
start = 60
main: 1583915204007
new thread: 1583915204008
new thread: 1583915204008
new thread: 1583915204008
new thread: 1583915204008
可以看到 main
方法对应的线程 先执行完了。
然后为了解决 main
方法对应的线程 先执行完, 我们加上 Thread.sleep(1000);
看看:
public class Test {
private volatile int start = 0;
public static void main(String[] args) {
new Test().test();
}
private void test() {
Runnable runnable = new Runnable() {
@Override
public void run() {
add();
System.out.println("new thread: " System.currentTimeMillis());
}
};
for (int i = 0; i < 10; i ) {
Thread thread = new Thread(runnable);
thread.start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("start = " start);
System.out.println("main: " System.currentTimeMillis());
}
void add() {
for (int i = 0; i < 10; i ) {
start ;
}
}
}
查看控制台输出:
代码语言:javascript复制new thread: 1583915390819
new thread: 1583915390821
new thread: 1583915390822
new thread: 1583915390823
new thread: 1583915390823
new thread: 1583915390823
new thread: 1583915390823
new thread: 1583915390823
new thread: 1583915390823
new thread: 1583915390823
start = 100
main: 1583915391822
去掉修饰 start 的 volatile 修饰符
然后我们试试把 volatile
修饰符 去掉试试,运行的结果输出的 start
值是多少 ?
我们修改代码如下:
代码语言:javascript复制public class Test {
private int start = 0;
public static void main(String[] args) {
List<Integer> res = new ArrayList<>();
for (int i = 0; i < 20; i ) {
res.add(new Test().test());
}
System.out.println(res);
}
private int test() {
Runnable runnable = () -> add();
for (int i = 0; i < 10; i ) {
Thread thread = new Thread(runnable);
thread.start();
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
return start;
}
void add() {
for (int i = 0; i < 10; i ) {
start ;
}
}
}
查看控制台输出:
代码语言:javascript复制[100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100]
就不卖关子了,这里结果没问题的原因主要是因为 创建线程的耗时 比 add
方法执行的耗时还长, 所以就相当与单线程执行了,我们可以来验证下。
public class Test {
private int start = 0;
public static void main(String[] args) {
new Test().test();
}
private int test() {
for (int i = 0; i < 10; i ) {
long t = System.nanoTime();
Thread thread = new Thread(new TestRunnable(i));
System.out.println(i "_new_thred: " (System.nanoTime() - t));
thread.start();
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
return start;
}
void add() {
for (int i = 0; i < 10; i ) {
start ;
}
}
private class TestRunnable implements Runnable {
int id;
public TestRunnable(int i) {
id = i;
}
@Override
public void run() {
long t = System.nanoTime();
add();
System.out.println(id "_add: " (System.nanoTime() - t));
}
}
}
查看控制台输出:
代码语言:javascript复制0_new_thred: 1232700
1_new_thred: 31800
2_new_thred: 18000
3_new_thred: 24500
0_add: 62100
4_new_thred: 19700
5_new_thred: 76800
3_add: 19200
6_new_thred: 22300
7_new_thred: 24500
8_new_thred: 32000
9_new_thred: 26100
4_add: 23100
8_add: 20000
7_add: 18400
1_add: 20900
2_add: 19600
5_add: 40300
9_add: 22100
6_add: 23600
当我们修改 add
方法的次数为 10W
次之后:
public class Test {
private int start = 0;
public static void main(String[] args) {
List<Integer> res = new ArrayList<>();
for (int i = 0; i < 10; i ) {
res.add(new Test().test());
}
System.out.println(res);
}
private int test() {
Runnable runnable = () -> add();
ExecutorService executorService = Executors.newFixedThreadPool(100);
for (int i = 0; i < 100; i ) {
executorService.execute(runnable);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
executorService.shutdown();
return start;
}
void add() {
for (int i = 0; i < 100000; i ) {
start ;
}
}
}
查看控制台输出:
代码语言:javascript复制[9337838, 9957329, 10000000, 10000000, 10000000, 9925170, 10000000, 9922369, 10000000, 10000000]
修改上面的测试代码,给 start
添加 volatile
修饰符:
public class Test {
private volatile int start = 0;
public static void main(String[] args) {
List<Integer> res = new ArrayList<>();
for (int i = 0; i < 10; i ) {
res.add(new Test().test());
}
System.out.println(res);
}
private int test() {
Runnable runnable = () -> add();
ExecutorService executorService = Executors.newFixedThreadPool(100);
for (int i = 0; i < 100; i ) {
executorService.execute(runnable);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
executorService.shutdown();
return start;
}
void add() {
for (int i = 0; i < 100000; i ) {
start ;
}
}
}
查看控制台输出:
代码语言:javascript复制[2292403, 2449807, 2146843, 1899033, 2120498, 4689152, 2264998, 2181451, 2266435, 2443323]
可以发现结果也是不对的。
执行结果正确的代码
要正确输出结果我们可以修改代码如下:
代码语言:javascript复制public class Test {
private int start = 0;
public static void main(String[] args) {
List<Integer> res = new ArrayList<>();
for (int i = 0; i < 10; i ) {
res.add(new Test().test());
}
System.out.println(res);
}
private int test() {
Runnable runnable = () -> add();
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i ) {
executorService.execute(runnable);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
executorService.shutdown();
return start;
}
synchronized void add() {
for (int i = 0; i < 100000; i ) {
start ;
}
}
}
或者
代码语言:javascript复制public class Test {
private AtomicInteger start = new AtomicInteger(0);
public static void main(String[] args) {
List<Integer> res = new ArrayList<>();
for (int i = 0; i < 10; i ) {
res.add(new Test().test());
}
System.out.println(res);
}
private int test() {
Runnable runnable = () -> add();
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i ) {
executorService.execute(runnable);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
executorService.shutdown();
return start.get();
}
void add() {
for (int i = 0; i < 100000; i ) {
start.addAndGet(1);
}
}
}
或者
代码语言:javascript复制public class Test {
private static ReentrantLock lock = new ReentrantLock();
private int start = 0;
public static void main(String[] args) {
List<Integer> res = new ArrayList<>();
for (int i = 0; i < 10; i ) {
res.add(new Test().test());
}
System.out.println(res);
}
private int test() {
Runnable runnable = () -> add();
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i ) {
executorService.execute(runnable);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
executorService.shutdown();
return start;
}
void add() {
lock.lock();
try {
for (int i = 0; i < 100000; i ) {
start ;
}
} finally {
lock.unlock();
}
}
}
以上