前两篇文章讲了synchronized关键字,详见:
Java高并发编程之synchronized关键字(一)
Java高并发编程之synchronized关键字(二)
依然先从一段简单的代码开始:
public class T011 {
public boolean running = true;
public void m() {
System.out.println("m start...");
while (running) {
}
System.out.println("m end");
}
public static void main(String[] args) {
T011 t011 = new T011();
new Thread(t011::m, "thread_1").start();
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
t011.running = false;
}
}
线程thread_1启动后执行t011对象的m方法,m方法中有一个while循环,当running为true的时候,while循环将持续下去,要让while循环停止,最直观的方法是,将t011的running设置为false,但是,代码运行起来,却发现,m方法一直不会结束,这是为什么呢?
大致上讲,running存在于堆内存的t011对象中,当thread_1线程开始运行的时候,会把running的值从内存中copy到thread_1线程的工作区,在运行过程中直接使用这个copy,并不会每次都去读取堆内存,这样,当主线程修改running的值之后,thread_1线程感知不到,所以不会停止运行。
那么,有什么办法能让thread_1停止运行呢?答案是使用volatile关键字,将会强制所有线程都去堆内存中读取running的值。
将 public boolean running = true;
改为public volatile boolean running = true;
即可达到目的。
总结起来,volatile关键字的作用,是使一个变量在多个线程间可见。
值得注意的是,volatile并不能保证多个线程共同修改running变量时所带来的不一致问题,也就是说volatile不能完全替代synchronized。volatile只保证可见性,而synchronized既保证可见性,又保证原子性。
volatile不能保证原子性,用下面的小程序可以证明:
public class T015 {
volatile int count = 0;
void m() {
for(int i = 0; i < 1000; i++) {
count++;
}
}
public static void main(String[] args) {
T015 t015 = new T015();
List<Thread> threads = new ArrayList<>();
for(int i = 0; i < 10; i++) {
threads.add(new Thread(t015::m, "thread_" + i));
}
threads.forEach(o->o.start());
threads.forEach(o->{
try {
o.join();
} catch (Exception e) {
e.printStackTrace();
}
});
System.out.println(t015.count);
}
}
如果volatile可以保证原子性,那么结果应该是10000,然而并不总是得到10000。
要保证原子性的做法很简单,只需要在m方法的声明处加上synchronized即可。
如果不想使用synchronized,又希望得到正确的结果,该怎么做呢?
可以使用AtomicInteger,代码如下:
public class T015 {
AtomicInteger count = new AtomicInteger(0);
void m() {
for(int i = 0; i < 1000; i++) {
count.incrementAndGet();
}
}
public static void main(String[] args) {
T015 t015 = new T015();
List<Thread> threads = new ArrayList<>();
for(int i = 0; i < 10; i++) {
threads.add(new Thread(t015::m, "thread_" + i));
}
threads.forEach(o->o.start());
threads.forEach(o->{
try {
o.join();
} catch (Exception e) {
e.printStackTrace();
}
});
System.out.println(t015.count);
}
}
这里需要注意下,尽管AtomicInteger中的方法是具有原子性的,但是,其多个方法同时使用并不会构成原子性,依然需要synchronized来加以保证。