一、volatile的作用
- volatile关键作用是使变量在多个线程之间可见。
public class Demo2 {
public static void main(String[] args) throws InterruptedException {
Print print = new Print();
new Thread(new Runnable() {
@Override
public void run() {
try {
print.print();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
//main方法睡一会
Thread.sleep(1000);
//修改打印标志flag
print.setFlag(false);
}
}
class Print{
private volatile boolean flag = true;
public void setFlag(boolean flag) {
this.flag = flag;
}
public void print() throws InterruptedException {
int i = 0;
System.out.println(Thread.currentThread().getName()+"线程开始了...");
while (flag){
i++;
}
System.out.println(Thread.currentThread().getName()+"线程结束了...");
System.out.println(i);
}
}
以上代码的运行结果:
通过运行结果发现print()因为while循环的存在,进入了死循环,而在main方法中对循环的标志进行更改,也无法使while循环的结束,也就是子线程读不到flag的变化。
- 解决方案:
使用volatile修饰打印标志,这样可以强制线程从公共内存读取变量的值,而不是在自己的工作内存读取变量的值。
private volatile boolean flag = true;//解决上述问题
运行结果:
二、比较一下volatile和synchronized
- volatile关键字和synchronize关键字都是java多线程知识中重要的内容,所以学习的时候,我们不妨将它们比较一下,分析出它们各自的特点与区别,加深我们的记忆。
- volatile 关键字是线程同步的轻量级实现,所以volatile的性能肯定比synchronize要好。
- volatile只能修饰变量,而synchronize可以修饰方法,代码块。
- 多线程访问volatile变量不会发生阻塞,而访问synchronize的方法或代码块,可能会发生阻塞。
- volatile可以保证数据的可见性,但不能保证原子性。而synchronize既可以保证可见性又可以保证原子性。
- 关键字volatile解决的是变量在多个线程之间的可见性。而synchronize解决多个线程之间访问公共资源的同步性。
三、volatile的非原子特性
- volatile关键字增加了实例变量在多个线程之间的可见性,但是不具备原子性。
public class Demo3 {
public static void main(String[] args) {
//在main里创建十个子线程
for (int i = 0; i < 10; i++) {
new Thread(new MyThread()).start();
}
}
static class MyThread implements Runnable {
public static volatile int count = 0;
public static void addCount(){
for(int i = 0; i < 1000; i++){
count++;
}
System.out.println(Thread.currentThread().getName()+","+count);
}
@Override
public void run() {
addCount();
}
}
}
运行结果:
根据以上代码,正常情况下,count被十个线程各加了一千次,count应该变成10000,但结果不是。说明一个线程在for循环里还没加完,别的线程就进来抢夺了执行权。说明volatile仅仅表示所有线程从主内存读取count的值,count++不是一个原子操作,所以为了实现原子性,就需要加上synchronize进行同步。
public synchronized static void addCount()
在加上synchronized后,volatile也不需要了,因为synchronize既保证了原子性也保证了可见性。
四、常见原子类进行自增自减
- i++和i–不是原子操作,除了使用synchronize进行同步外,也可以使用AtomicInteger/AtomicLong原子类进行操作。
public static void main(String[] args) {
//在main里创建十个子线程
for (int i = 0; i < 10; i++) {
new Thread(new MyThread()).start();
}
}
static class MyThread implements Runnable {
private static AtomicInteger count = new AtomicInteger();
public static void addCount(){
for(int i = 0; i < 1000; i++){
count.getAndIncrement();
}
System.out.println(Thread.currentThread().getName()+","+count);
}
@Override
public void run() {
addCount();
}
}
运行结果:通过以上代码发现,即使没有使用volatile和synchronize关键字,通过
AtomicInteger
也实现了原子性.