Java并发编程基础
- - 并发编程网 - ifeve.com并发是一种能并行运行多个程序或并行运行一个程序中多个部分的能力. 如果程序中一个耗时的任务能以异步或并行的方式运行,那么整个程序的吞吐量和可交互性将大大改善. 现代的PC都有多个CPU或一个CPU中有多个核. 是否能合理运用多核的能力将成为一个大规模应用程序的关键. 进程是以独立于其他进程的方式运行的,进程间是互相隔离的.
我们先看下面一个示例
public class RaceCondition {
private static boolean done; public static void main(final String[] args) throws InterruptedException { new Thread(new Runnable() { public void run() { int i = 0; while (!done) { i++; } System.out.println("Done!"); } }).start(); System.out.println("OS: " + System.getProperty("os.name")); Thread.sleep(2000); done = true; System.out.println("flag done set to true"); } }
在ubutun双核cpu下,默认不加任何jvm参数执行
输出如下: 主线程执行完之后,子线程一直在执行,为什么子线程没有获取到主线程修改done之后的变量值呢?
我们再设置下jvm的参数为 -client,则子线程能够获取主线程修改done之后的值,正常执行完
也就是moren ubutun下默认jvm启动是-server 服务器默认启动的,那么-server启动跟client启动有什么区别呢?-server启动多了JIT即时编译优化,JIT优化会对while循环进行优化,所以它没法看到主线程对done变量修改的值,子线程读取done变量会从操作系统寄存器或者cpu cache中读取done的值,而不会从主存中读取,而主线程修改done变量还是从放在主存。所以就出现上面这种并发编程的变量可见行问题了。
此时 volatile修饰词当然就派上用场了,volatile就是让变量的修改能够让该变量的值从主存中读取,当然更新了各个线程就都能看到了。
还有一种方式也可以达到上面的效果,就是使用synchronized同步,synchronized同步也能够让各个线程从主存中获取最新的值。
package com.mime; public class RaceCondition { // private static volatile boolean done; public static void main(final String[] args) throws InterruptedException { new Thread(new Runnable() { public void run() { int i = 0; while (!getFlag()) { i++; } System.out.println("Done!"); } }).start(); System.out.println("OS: " + System.getProperty("os.name")); Thread.sleep(2000); setFlag(true); System.out.println("flag done set to true"); } private static boolean done; public static synchronized boolean getFlag() { return done; } public static synchronized void setFlag(boolean flag) { done = flag; } }
输出同样是:
OS: Linux flag done set to true Done!
Simply put, it is the copying from local or working memory to main memory. A change made by one thread is guaranteed to be visible to another thread only if the writing thread crosses the memory barriera and then the reading thread crosses the memory barrier. synchronized and volatile keywords force that the changes are globally visible on a timely basis; these help cross the memory barrier—accidentally or intentionally. The changes are first made locally in the registers and caches and then cross the memory barrier as they are copied to the main memory. The sequence or ordering of these crossing is called happens-before—see “The Java Memory Model,” Appendix 2, Web Resources, on page 255, and see Brian Goetz’s Java Concurrency in Practice [Goe06]. The write has to happens-before the read, meaning the writing thread has to cross the memory barrier before the reading thread does, for the change to be visible. Quite a few operations in the concurrency API implicitly cross the memory barrier: volatile, synchronized, methods on Thread such as start() and interrupt(), methods on Execu- torService, and some synchronization facilitators like CountDownLatch.