转载请以链接形式标明出处:
本文出自: 103style的博客
本文是 看到 这篇文章中 “volatile 的意义?” 那一小节提供的一个例子引发的测试。
volatile 的意义?
volatile有两条关键的语义:
下面的例子是用来证明下面这个观点的后半句是错误的。
由于volatile修饰的变量在各个线程里都是一致的,所以基于volatile变量的运算在多线程并发的情况下是安全的。
例子是这样的:
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
之后输出的 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,这是因为
main
方法对应的线程 不会等待 新创建的线程执行完。
我们可以加上时间输出看看试试:
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++; } } } 复制代码
控制台打印的结果:
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++; } } } 复制代码
查看控制台输出:
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 复制代码
然后我们试试把 volatile
修饰符 去掉试试,运行的结果输出的 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++; } } } 复制代码
查看控制台输出:
[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)); } } } 复制代码
查看控制台输出:
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++; } } } 复制代码
查看控制台输出:
[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++; } } } 复制代码
查看控制台输出:
[2292403, 2449807, 2146843, 1899033, 2120498, 4689152, 2264998, 2181451, 2266435, 2443323] 复制代码
可以发现结果也是不对的。
要正确输出结果我们可以修改代码如下:
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(); } } } 复制代码
以上