修饰符volatile和synchronized的区别联系

关键字volatitle的主要作用是使变量在多个线程间可见。关键字volatile的作用是强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值。

public class RunThread extends Thread {
    private volatile  boolean isRunning = true;
    public boolean isRunning(){
        return isRunning;
    }
    public void setRunning(boolean isRunning){
        this.isRunning = isRunning;
    }
    @Override
    public void run(){
        System.out.println("进入run了");
        while (isRunning==true){
        }
        System.out.println("线程被停止了");
    }
}
public class Run {
    public static void main(String[] args) {
        try {
            RunThread thread = new RunThread();
            thread.start();
            Thread.sleep(1000);
            thread.setRunning(false);
            System.out.println("已经赋值为false");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
输出结果:
进入run了
已经赋值为false
线程被停止了

使用volatile关键字增加了实例变量在多个线程之间的可见性。但volatile关键字最致命的缺点是不支持原子性。下面是synchronized和volatile的比较:

  1. 关键字volatile是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,并且volatile只能修饰变量,而synchronized可以修饰方法,以及代码块。随着JDK新版本的发布,synchronized关键字在执行效率上得到很大提升,在开发中使用synchronized关键字的比率还是比较大的。
  2. 多线程访问volatile不会发生阻塞,而synchronized会出现阻塞。
  3. volatile能保证数据的可见性,但不保证原子性;而synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公共内存中的数据做同步。
  4. 再次重申一下,关键字volatile解决的是变量在多个线程之间的可见性;而synchronized关键字解决的是多个线程之间访问资源的同步性。

线程安全包含原子性和可见性两个方面,Java的同步机制都是围绕这两个方面来确保线程安全的。

volatile非原子的特性

关键字volatile虽然增加了实例变量在多个线程之间的可见性,但它却不具备同步性,那么也就不具备原子性。代码如下:

public class MyThread extends Thread {
    public volatile static int count;
    private static void addCount() {
        for (int i = 0; i < 100; i++) {
            count++;
        }
        System.out.println("count=" + count);
    }
    @Override
    public void run(){
        addCount();
    }
}
public class Run {
    public static void main(String[] args) {
        MyThread[] myThreads = new MyThread[100];
        for (int i = 0; i < 100; i++) {
            myThreads[i] = new MyThread();
        }
        for (int i = 0; i < 100; i++) {
            myThreads[i].start();
        }
    }
}
输出结果:
count=8101
count=8209
count=8209
count=8309
count=8409
count=8509
count=8609
count=8709
count=8809
count=8909
count=9009
count=9109
count=9309
count=9409
count=9409
count=9509
count=9609
count=9709
count=9809
运行结果值不是10000
改代码:

public class MyThread2 extends Thread {
    public volatile static int count;
    //注意一定要加synchronized关键字
    //这样synchronized与static锁的内容就是MyThread2.class类了
    //也就达到了同步的效果了
    private synchronized static void addCount() {
        for (int i = 0; i < 100; i++) {
            count++;
        }
        System.out.println("count=" + count);
    }
    @Override
    public void run(){
        addCount();
    }
}
public class Run {
    public static void main(String[] args) {
        MyThread2[] myThreads = new MyThread2[100];
        for (int i = 0; i < 100; i++) {
            myThreads[i] = new MyThread2();
        }
        for (int i = 0; i < 100; i++) {
            myThreads[i].start();
        }
    }
}
输出结果:
count=8000
count=8100
count=8200
count=8300
count=8400
count=8500
count=8600
count=8700
count=8800
count=8900
count=9000
count=9100
count=9200
count=9300
count=9400
count=9500
count=9600
count=9700
count=9800
count=9900
count=10000

如果在方法private static void addCount()加上synchronized同步关键字,也就没有必要使用volatile关键字来声明count变量了。

关键字volatile的主要使用的场合是在多个线程中可以感知实例变量被更改了,并且可以获得最新的值使用,也就是用多线程读取共享变量时可以获得最新值使用。

关键字volatile提示线程每次从共享内存中读取变量,而不是从私有内存中读取,这样就保证了同步数据的可见性。但这里需要注意的是:如果修改实例变量中的数据,比如i++,也就事i=i+1,则这样的操作其实并不是一个原子操作,也就是非线程安全的。表达式i++的操作步骤分解如下:

  1. 从内存中取出i的值
  2. 计算i的值
  3. 将i的值写到内存中

假如在第2步计算值得时候,另外一个线程也修改i的值,那么这个时候就会出现脏数据。解决的办法其实就是使用synchronized关键字。所以说volatile本身并不处理数据的原子性,而是强制对数据的读写及时影响到主内存的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值