Synchronized,Volatile用法

概述

    当我们要对资源进行原子可见性和互斥同步的操作时,我们一般会采用synchronized和volatile关键字来修饰。至于这两个关键字的用法我们可能有些混乱,接下来我们就来捋一捋这两个关键字的用法。

synchronized和volatile之用法对比

  1. volatile修饰变量,保证了不同线程对这个变量操作的可见性,即一个线程修改了这个变量,这新值对于其他线程来说是立即可见的。
  2. volatile禁止进行指令重排序。
  3. volatile本质是告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量,只有当前线程可以访问变量,其他线程则被阻塞。
  4. volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的。
  5. volatile仅能实现变量的修改可见性,并不能保证原子性;synchronized则可以早证变量的修改可见性。
  6. volatile不会造成线程的阻塞,synchronized可能会造成线程的阻塞;
  7. volatile标记的变量不会被编译器优化;synchronized标记的变量会被编译器优化。

用法和Demo

  1. volatile修饰变量,保证了不同线程对这个变量操作的可见性,即一个线程修改了这个变量,这新值对于其他线程来说是立即可见的。我们来看一下下面这个例子

    普通的多线程修改变量的例子:

    public class MyClass {

        public static int count = 0;

        public static void inc() {

            //这里延迟2毫秒,使得结果明显
            try {
                Thread.sleep(2);
            } catch (InterruptedException e) {
            }

            count++;
        }

        public static void main(String[] args) {

            //同时启动1000个线程,去进行count++计算
            for (int i = 0; i < 1000; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        MyClass.inc();
                    }
                }).start();
            }

            //期望輸出1000
            System.out.println("运行结果:MyClass.count=" + MyClass.count);
        }
    }

多次执行上面代码片输出的结果是
运行结果:MyClass.count=857
运行结果:MyClass.count=849
···············

    发现输出结果基本上都是小于1000。这是每次开启一个线程去执行加法操作的时候,可能上一个线程还没有执行完,拿到的值还是未修改之前的值,这样就会导致最后输出的结果不是我们期望的1000.

用volatile修饰变量之后的多线程问题

public class MyClass {

    public static volatile int count = 0;

    public static void inc() {

        //这里延迟2毫秒,使得结果明显
        try {
            Thread.sleep(2);
        } catch (InterruptedException e) {
        }

        count++;
    }

    public static void main(String[] args) {

        //同时启动1000个线程,去进行count++计算
        for (int i = 0; i < 1000; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    MyClass.inc();
                }
            }).start();
        }

        //期望輸出1000
        System.out.println("运行结果:MyClass.count=" + MyClass.count);
    }
}

多次执行上面代码片输出的结果是
运行结果:MyClass.count=775
运行结果:MyClass.count=814
···············

    发现输出结果基本上没有1000。这样的结果,是不是让你很意外,volatile修饰了count,这样的话,如果线程A修改了count的值,那对于线程B而言应该是可见的,为什么结果不是1000呢?因为volatile只是保证了变量修改的可见性,却没有保证原子性。在java的内存模型中每一个线程运行时都会有一个线程栈,线程栈保存了线程运行时变量的信息。当线程访问的时候,首先通过对象的引用找到对应在堆内存中变量的值,然后把堆内存变量具体的值load到线程本地内存中,建立一个变量副本,之后线程就不会再和堆内存变量的值有关系,而是直接修改变量副本的值,在修改完之后的某一个时刻(线程退出前),自动把线程变量副本的值写对象所在堆内存中,这样堆内存中的对象就变化了。所以,上面开启了1000个线程,修改的只是修改了自己的副本,并没有直接修改堆内存的值,这样结果一般就会小于1000。如果需要同步操作,则需要要其他的来控制。
    附上面解释图(来源于网络),便于理解,给两种不同的图,实质上是一样的。

这里写图片描述

这里写图片描述

2.synchronized的用法

  1. synchronized修饰变量(count一定是引用类型)
    public static Integer count = 0;

    public void inc() {
        synchronized (count) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            count++;
        }
    }
2.synchronized修饰方法
   public synchronized void inc() {
        try {
            Thread.sleep(10);
            count++ ;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
3.synchronized同步代码块
 public void inc() {
        synchronized(this) {
            try {
                Thread.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
             count++ ;
        }
    }

synchronized的用法不在这里过多的赘述,自己敲几个demo熟悉就知道了。

附加

这里的程序我都是用Android Studio来编写和调试的,附上设置让Android Studio也能运行java程序的方法。

  1. File->New->New Module .选择java library 点击next, Finish,记住,这个时候一定要在运行的类里面写一个主函数。

    1. 主面板中选择Run->Edit configurations,点击左上角的+。选择Application。右边可以重新命名你运行的工程名字,Main name 就是主函数所在的类,Module就是你一开始New的Module,Jre选择Android Studio的jre既可以了。点击Apply->OK。
      这里写图片描述
  2. 控制台输出的时候可能会有乱码,第一个是Setting里的编码,File->Setting,确保都是UTF-8;第二个是在Java工程目录下的build.gradle添加如下代码,根据自己gradle的版本添加一个就可以了,然后重新运行一遍。,就会没有乱码了。

//新版本的gradle
tasks.withType(JavaCompile) {
    options.encoding = "UTF-8"
}
//旧版本的gradle
tasks.withType(Compile) {
    options.encoding = "UTF-8"
}

这里写图片描述

这里写图片描述

转载请标明出处,如有错误之处,请批评指正,谢谢!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值