轉(zhuǎn)載請(qǐng)以鏈接形式標(biāo)明出處:
本文出自:103style的博客
本文是 看到 這篇文章中 “volatile 的意義痹束?” 那一小節(jié)提供的一個(gè)例子引發(fā)的測(cè)試软驰。
volatile 的意義觅廓?
- 防止CPU指令重排序
volatile有兩條關(guān)鍵的語義:
- 保證被volatile修飾的變量對(duì)所有線程都是可見的
- 禁止進(jìn)行指令重排序
volatile變量具有下列特性:
- 可見性:總是能看到(任意線程)對(duì)這個(gè)volatile變量最后的寫入。
- 原子性:對(duì)任意單個(gè)volatile變量的讀/寫具有原子性驼侠,但類似于volatile++這種復(fù)合操作不具有原子性零聚。
下面的例子是用來證明下面這個(gè)觀點(diǎn)的后半句是錯(cuò)誤的。
由于volatile修飾的變量在各個(gè)線程里都是一致的拉队,所以基于volatile變量的運(yùn)算在多線程并發(fā)的情況下是安全的弊知。
原測(cè)試示例
例子是這樣的:
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++;
}
}
}
大家可以看看 最后運(yùn)行的結(jié)果輸出的 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++;
}
}
}
兩個(gè)測(cè)試的結(jié)果
兩個(gè)結(jié)果都是 100粱快? 還是只有第二個(gè)是100 ?
其實(shí)兩個(gè)結(jié)果都不是100秩彤,這是因?yàn)?main
方法對(duì)應(yīng)的線程 不會(huì)等待 新創(chuàng)建的線程執(zhí)行完。
我們可以加上時(shí)間輸出看看試試:
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++;
}
}
}
控制臺(tái)打印的結(jié)果:
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
方法對(duì)應(yīng)的線程 先執(zhí)行完了事哭。
然后為了解決 main
方法對(duì)應(yīng)的線程 先執(zhí)行完漫雷, 我們加上 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++;
}
}
}
查看控制臺(tái)輸出:
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 修飾符
然后我們?cè)囋嚢?volatile
修飾符 去掉試試,運(yùn)行的結(jié)果輸出的 start
值是多少 ?
我們修改代碼如下:
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++;
}
}
}
查看控制臺(tái)輸出:
[100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100]
就不賣關(guān)子了鳍咱,這里結(jié)果沒問題的原因主要是因?yàn)?創(chuàng)建線程的耗時(shí) 比 add
方法執(zhí)行的耗時(shí)還長降盹, 所以就相當(dāng)與單線程執(zhí)行了,我們可以來驗(yàn)證下谤辜。
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));
}
}
}
查看控制臺(tái)輸出:
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
當(dāng)我們修改 add
方法的次數(shù)為 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++;
}
}
}
查看控制臺(tái)輸出:
[9337838, 9957329, 10000000, 10000000, 10000000, 9925170, 10000000, 9922369, 10000000, 10000000]
修改上面的測(cè)試代碼蓄坏,給 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++;
}
}
}
查看控制臺(tái)輸出:
[2292403, 2449807, 2146843, 1899033, 2120498, 4689152, 2264998, 2181451, 2266435, 2443323]
可以發(fā)現(xiàn)結(jié)果也是不對(duì)的价捧。
執(zhí)行結(jié)果正確的代碼
要正確輸出結(jié)果我們可以修改代碼如下:
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++;
}
}
}
或者
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);
}
}
}
或者
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();
}
}
}
如果覺得不錯(cuò)的話,請(qǐng)幫忙點(diǎn)個(gè)贊唄涡戳。
以上
掃描下面的二維碼结蟋,關(guān)注我的公眾號(hào) 103Tech, 點(diǎn)關(guān)注渔彰,不迷路嵌屎。