Synchronized,Volatile用法
来源:互联网 发布:广东广电网络套餐介绍 编辑:程序博客网 时间:2024/06/01 07:56
概述
当我们要对资源进行原子可见性和互斥同步的操作时,我们一般会采用synchronized和volatile关键字来修饰。至于这两个关键字的用法我们可能有些混乱,接下来我们就来捋一捋这两个关键字的用法。
synchronized和volatile之用法对比
- volatile修饰变量,保证了不同线程对这个变量操作的可见性,即一个线程修改了这个变量,这新值对于其他线程来说是立即可见的。
- volatile禁止进行指令重排序。
- volatile本质是告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量,只有当前线程可以访问变量,其他线程则被阻塞。
- volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的。
- volatile仅能实现变量的修改可见性,并不能保证原子性;synchronized则可以早证变量的修改可见性。
- volatile不会造成线程的阻塞,synchronized可能会造成线程的阻塞;
- volatile标记的变量不会被编译器优化;synchronized标记的变量会被编译器优化。
用法和Demo
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程序的方法。
File->New->New Module .选择java library 点击next, Finish,记住,这个时候一定要在运行的类里面写一个主函数。
- 主面板中选择Run->Edit configurations,点击左上角的+。选择Application。右边可以重新命名你运行的工程名字,Main name 就是主函数所在的类,Module就是你一开始New的Module,Jre选择Android Studio的jre既可以了。点击Apply->OK。
- 主面板中选择Run->Edit configurations,点击左上角的+。选择Application。右边可以重新命名你运行的工程名字,Main name 就是主函数所在的类,Module就是你一开始New的Module,Jre选择Android Studio的jre既可以了。点击Apply->OK。
控制台输出的时候可能会有乱码,第一个是Setting里的编码,File->Setting,确保都是UTF-8;第二个是在Java工程目录下的build.gradle添加如下代码,根据自己gradle的版本添加一个就可以了,然后重新运行一遍。,就会没有乱码了。
//新版本的gradletasks.withType(JavaCompile) { options.encoding = "UTF-8"}//旧版本的gradletasks.withType(Compile) { options.encoding = "UTF-8"}
转载请标明出处,如有错误之处,请批评指正,谢谢!
阅读全文
1 0
- Synchronized,Volatile用法
- java 中 synchronized关键字 volatile关键字 用法
- synchronized、volatile、Atomic区别和用法
- synchronized、volatile
- volatile/synchronized
- 线程同步synchronized和volatile用法及区别
- 关于volatile和synchronized
- 关于volatile和synchronized
- volatile/synchronized关键字解释
- synchronized与volatile异同
- volatile 和 synchronized
- volatile and synchronized
- synchronized和volatile笔记
- volatile和synchronized 区别
- 关于volatile和synchronized
- synchronized与volatile异同
- volatile与synchronized
- synchronized和volatile关键字
- 八数码 康托 逆康托 哈希 双解码 双向广搜
- eclipse使用SVN创建,合并分支
- [Linux C编程]数据库
- 数据管理中心
- Qt/C++ QRegExp 提取匹配字符串
- Synchronized,Volatile用法
- 134_容器_git工具_Google_guava与Apache_commons的准备工作(jar src doc)
- LockSupport使用
- tomcat报错
- [LintCode 69] 二叉树的层次遍历 (Python)
- 使用MyBatis对表执行CRUD操作——基于XML的实现
- nyoj119 士兵杀敌(三)
- leetcode--Binary Tree Upside Down
- ubuntu 下为php安转memcache和redis服务