本来这部分内容是放在上一篇文章中的,结果越写越多,觉得应该另起一篇再详细描述一下
不看不看只要答案党看这里
使用volatile关键字
—————————————————-
1. 只能保证读取一致性
2. 不能保证线程安全性(原子性)
—————————————————-
下面看几个例子
第一个例子用来证明读取一致性
package testgc;
public class Counter {
public volatile static int count = 0;
public static void setCount(int i) {
count = i;
}
public static void main(String[] args) {
new Thread(new Runnable() {
public void run() {
while(count==0){
}
}
}).start();
new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Counter.setCount(1);
}
}).start();
}
}
在这个例子中, 我使用成员变量public volatile static int count = 0; 作为一个标志位
在线程1中, 当count==0时, while会一直循环不会退出
在线程2中, 在程序开始2秒后将count标志位改成1 ( 期待线程1可以退出) , 测试结果为
使用volatile时, 线程2可以及时将值1 写入主内存(这也正是volatile要做的), 线程1也可以及时察觉到标志位变化从而退出循环
当不使用volatile时, 线程1会一直持有一个count标志位的copy, 在线程2修改了值后无法察觉变化, 你会看到while循环一直存在,程序会一直执行,无法退出
第二个例子用来证明volatile无法保证线程安全
这个测试方法是从另外一个哥们的博客中看到的, 但是实际上原程序有一个小bug, 使其并不能证明作者的结论(尽管他的结论是正确的)
因为原程序的输出位置在主线程中, 也就是说即使你在inc() 方法前加入synchronized 关键字使其同步, 你也不能观察到正确结果
因为当主线程启动子线程之后, 不管子线程是否运行结束
主线程都会去尝试读取结果值 , 如果这时子线程没有全部结束你会得到一个中间值结果
不加volatile你会得到错误结果
package testgc;
public class TestVolatitle {
public static int count = 0;
public static void inc() {
//这里延迟1毫秒,使得结果明显
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
count++;
//我修改后的输出位置
System.out.println("运行结果:Counter.count=" + TestVolatitle.count);
}
public static void main(String[] args) {
//同时启动10个线程,去进行i++计算,看看实际结果
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
public void run() {
TestVolatitle.inc();
}
}).start();
}
//原程序的输出位置在这里
//System.out.println("运行结果:Counter.count=" + TestVolatitle.count);
}
}
结果为
运行结果:Counter.count=7
运行结果:Counter.count=7
运行结果:Counter.count=7
运行结果:Counter.count=7
运行结果:Counter.count=7
运行结果:Counter.count=7
运行结果:Counter.count=7
运行结果:Counter.count=9
运行结果:Counter.count=9
运行结果:Counter.count=9
加入volatile关键字之后的代码, 还是会得到错误结果
package testgc;
public class TestVolatitle {
public volatile static int count = 0;
public static void inc() {
//这里延迟1毫秒,使得结果明显
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
count++;
//我修改后的输出位置
System.out.println("运行结果:Counter.count=" + TestVolatitle.count);
}
public static void main(String[] args) {
//同时启动10个线程,去进行i++计算,看看实际结果
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
public void run() {
TestVolatitle.inc();
}
}).start();
}
//原程序的输出位置在这里
//System.out.println("运行结果:Counter.count=" + TestVolatitle.count);
}
}
运行结果:Counter.count=3
运行结果:Counter.count=5
运行结果:Counter.count=5
运行结果:Counter.count=6
运行结果:Counter.count=3
运行结果:Counter.count=3
运行结果:Counter.count=7
运行结果:Counter.count=9
运行结果:Counter.count=10
运行结果:Counter.count=9
如何让它正确打印1到10呢? 只能同步或者等待, 使用synchronized可以做到同步,使用join可以让主线程等待子线程结束(或者超时)
synchronized 使所有的子线程互相同步
join 使主线程等待子线程(或者超时)
使用synchronized的例子
package testgc;
public class TestVolatitle {
public volatile static int count = 0;
public synchronized static void inc() {
//这里延迟1毫秒,使得结果明显
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
count++;
//我修改后的输出位置
System.out.println("运行结果:Counter.count=" + TestVolatitle.count);
}
public static void main(String[] args) {
//同时启动10个线程,去进行i++计算,看看实际结果
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
public void run() {
TestVolatitle.inc();
}
}).start();
}
//原程序的输出位置在这里
//System.out.println("运行结果:Counter.count=" + TestVolatitle.count);
}
}
运行结果:Counter.count=1
运行结果:Counter.count=2
运行结果:Counter.count=3
运行结果:Counter.count=4
运行结果:Counter.count=5
运行结果:Counter.count=6
运行结果:Counter.count=7
运行结果:Counter.count=8
运行结果:Counter.count=9
运行结果:Counter.count=10
使用join
package testgc;
public class TestVolatitle {
public volatile static int count = 0;
public static void inc() {
//这里延迟1毫秒,使得结果明显
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
count++;
//我修改后的输出位置
System.out.println("运行结果:Counter.count=" + TestVolatitle.count);
}
public static void main(String[] args) throws InterruptedException {
//同时启动10个线程,去进行i++计算,看看实际结果
for (int i = 0; i < 10; i++) {
Thread t = new Thread(new Runnable() {
public void run() {
TestVolatitle.inc();
}
});
t.start();
t.join();
}
//原程序的输出位置在这里
//System.out.println("运行结果:Counter.count=" + TestVolatitle.count);
}
}
运行结果:Counter.count=1
运行结果:Counter.count=2
运行结果:Counter.count=3
运行结果:Counter.count=4
运行结果:Counter.count=5
运行结果:Counter.count=6
运行结果:Counter.count=7
运行结果:Counter.count=8
运行结果:Counter.count=9
运行结果:Counter.count=10
对于他的例子其实还有一个偷懒的办法, 让主线程在输出结果之前sleep两秒, 就可以观察到正确的结果
使用synchronized关键字, 结果正确, 使用volatile, 结果不正确