java volatile && synchronized

Java代码在编译后会变成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节
码,最终需要转化为汇编指令在CPU上执行,Java中所使用的并发机制依赖于JVM的实现和CPU的指令。

volatile

volatile在多处理器开发中保证了共享变量的 “可见性”

可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。

在这里要提一句,在多处理器下运行的程序,若是其中某个变量改变,新的变量值并不会立刻就更新到内存,而是先在缓存中更新,定时再更新到内存中,这可能是基于速率或者其他条件的考虑。

但是这就可能导致问题出现:在一个CPU修改了数据之后,修改后的数据在它自己的缓存中,那么其他CPU是不知道的,也就是“看不见”。

这个时候就需要volatile出现来保证可见性了。

volatile是如何来保证可见性的呢?
被volatile关键字修饰的共享变量进行写操作的时候会多出一行汇编代码:

0x01a3de1d: movb $0×0,0×1104800(%esi);0x01a3de24: lock addl $0×0,(%esp);

这条汇编代码中包含Lock前缀指令,这条Lock前缀指令在多核处理器下回做两件事情:

  1. 将当前处理器缓存行的数据写回到系统内存

    Lock前缀指令导致该处理器在执行指令期间声言LOCK#信号,LOCK#信号会锁住缓存并写回到内存,并使用缓存一致性机制确保修改的原子性,此操作称为 “缓存锁定”
    缓存一致性机制会阻止同时修改两个以上处理器缓存的内存区域数据。

  2. 这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效

    主要就是维护内部缓存和其他处理器缓存的一致性。如果通过嗅探一个处理
    器来检测其他处理器打算写内存地址,而这个地址当前处于共享状态,那么正在嗅探的处理器将使它的缓存行无效,在下次访问相同内存地址时,强制执行缓存行填充。

synchronized的实现


synchronized实现同步的基础:Java中每一个对象都可以作为锁。

  • 对于普通同步方法,锁是当前实例对象
  • 对于静态同步方法,锁是当前类的Class对象
  • 对于同步方法块,锁是Synchronized括号里配置的对象

当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。


synchronized的三种应用方式


下面代码是一个小例子,Account类中有一个静态变量 i=0作为共享的资源,有一个计数的实例方法,在main方法中创建了100个线程去实现计数器的方法,运行这个程序最后输出的 i 的值并不一定是99,极可能是一个比99小的数,这是因为不同的线程可能同时对 i 进行了加1的操作,导致有线程做了“无用功”,最后 i 并没有加到99。

这个时候我们就需要使用synchronized


public class Account {
    //共享的资源
    static int i=0;

    //实例方法
    public void count(){
        i++;
    }

    public static void main(String[] args) {
        Account account=new Account();
        Thread[] threads=new Thread[100];
        for (int j = 0; j < 100; j++) {
            threads[j]=new Thread(new Runnable() {
                @Override
                public void run() {
                    account.count();
                }
            });
        }

        for (Thread thread : threads) {
            thread.start();
        }
        System.out.println("i="+i);
    }
}


  1. 在普通的实例方法上添加锁:
	//实例方法
    public synchronized void count(){
        System.out.println("i++;i="+i);
        i++;
    }

再次运行程序可以发现,这次输出了i=99。此处实现了同步,看程序可以得到,循环中是一个对象,在普通方法上添加synchronized就是给对象加锁,因此实现了同步。

  1. 在静态方法上添加锁:
	//实例方法
    public static synchronized void count(){
        System.out.println("i++;i="+i);
        i++;
    }

锁的是该类,可以这样想,在java中一切皆对象,类也是基于一个class的类创建出来的对象。锁是当前类的Class对象的意思就是锁住的是这个class类。

  1. 在代码块中使用synchronized可以指定锁的类型,比如是对象锁还是类锁:
    1. 指定锁为this,指的就是调用这个方法的实例对象—对象锁
synchronized (this){
            System.out.println("i++;i="+i);
            i++;
        }
  1. 指定为this.class,指的就是这个类对象—类锁
synchronized (Account.class){
            System.out.println("i++;i="+i);
            i++;
        }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

每周都想吃火锅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值