Synchronized,Volatile用法

来源:互联网 发布:广东广电网络套餐介绍 编辑:程序博客网 时间:2024/06/01 07:56

概述

    当我们要对资源进行原子可见性和互斥同步的操作时,我们一般会采用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的版本添加一个就可以了,然后重新运行一遍。,就会没有乱码了。

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

这里写图片描述

这里写图片描述

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

原创粉丝点击