java基础提升篇:synchronized同步块和volatile同步变量
初遇
Java语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量。这两种机制的提出都是为了实现代码线程的安全性。其中 Volatile 变量的同步性较差(但有时它更简单并且开销更低),而且其使用也更容易出错。
synchronized同步块
Java中的同步块用synchronized标记。同步块在Java中是同步在某个对象上。所有同步在一个对象上的同步块在同时只能被一个线程进入并执行操作。所有其他等待进入该同步块的线程将被阻塞,直到执行该同步块中的线程退出。
有四种不同的同步块:
实例方法:一个实例一个线程。
静态方法:一个类只能由一个线程同时执行。
实例方法中的同步块
静态方法中的同步块
在多线程下最好是使用这种:
public class MyClass {public static void log2(String msg1, String msg2){synchronized(MyClass.class){log.writeln(msg1);log.writeln(msg2);}}
}
上述同步块都同步在不同对象上。实际需要那种同步块视具体情况而定。
实例方法同步
下面是一个同步的实例方法:
public synchronized void add(int value){this.count += value;
}
注意在方法声明中同步(synchronized )关键字。这告诉Java该方法是同步的。
Java实例方法同步是同步在拥有该方法的对象上。这样,每个实例其方法同步都同步在不同的对象上,即该方法所属的实例。只有一个线程能够在实例方法同步块中运行。如果有多个实例存在,那么一个线程一次可以在一个实例同步块中执行操作。一个实例一个线程。
静态方法同步
静态方法同步和实例方法同步方法一样,也使用synchronized 关键字。Java静态方法同步如下示例:
public static synchronized void add(int value){count += value;
}
同样,这里synchronized 关键字告诉Java这个方法是同步的。
静态方法的同步是指同步在该方法所在的类对象上。因为在Java虚拟机中一个类只能对应一个类对象,所以同时只允许一个线程执行同一个类中的静态同步方法。
对于不同类中的静态同步方法,一个线程可以执行每个类中的静态同步方法而无需等待。不管类中的那个静态同步方法被调用,一个类只能由一个线程同时执行。
实例方法中的同步块
有时你不需要同步整个方法,而是同步方法中的一部分。Java可以对方法的一部分进行同步。
在非同步的Java方法中的同步块的例子如下所示:
public void add(int value){synchronized(this){this.count += value;}}
示例使用Java同步块构造器来标记一块代码是同步的。该代码在执行时和同步方法一样。
注意Java同步块构造器用括号将对象括起来。在上例中,使用了“this”,即为调用add方法的实例本身。在同步构造器中用括号括起来的对象叫做监视器对象。上述代码使用监视器对象同步,同步实例方法使用调用方法本身的实例作为监视器对象。
一次只有一个线程能够在同步于同一个监视器对象的Java方法内执行。
下面两个例子都同步他们所调用的实例对象上,因此他们在同步的执行效果上是等效的。
public class MyClass {public synchronized void log1(String msg1, String msg2){log.writeln(msg1);log.writeln(msg2);}public void log2(String msg1, String msg2){synchronized(this){log.writeln(msg1);log.writeln(msg2);}}}
在上例中,每次只有一个线程能够在两个同步块中任意一个方法内执行。
如果第二个同步块不是同步在this实例对象上,那么两个方法可以被线程同时执行。
静态方法中的同步块
和上面类似,下面是两个静态方法同步的例子。这些方法同步在该方法所属的类对象上。
public class MyClass {public static synchronized void log1(String msg1, String msg2){log.writeln(msg1);log.writeln(msg2);}public static void log2(String msg1, String msg2){synchronized(MyClass.class){log.writeln(msg1);log.writeln(msg2);}}}
这两个方法不允许同时被线程访问。
如果第二个同步块不是同步在MyClass.class这个对象上。那么这两个方法可以同时被线程访问。
Java同步实例
在下面例子中,启动了两个线程,都调用Counter类同一个实例的add方法。因为同步在该方法所属的实例上,所以同时只能有一个线程访问该方法。
public class Counter{long count = 0;public synchronized void add(long value){this.count += value;}}public class CounterThread extends Thread{protected Counter counter = null;public CounterThread(Counter counter){this.counter = counter;}public void run() {for(int i=0; i<10; i++){counter.add(i);}}}public class Example {public static void main(String[] args){Counter counter = new Counter();Thread threadA = new CounterThread(counter);Thread threadB = new CounterThread(counter);threadA.start();threadB.start();}}
创建了两个线程。他们的构造器引用同一个Counter实例。Counter.add方法是同步在实例上,是因为add方法是实例方法并且被标记上synchronized关键字。因此每次只允许一个线程调用该方法。另外一个线程必须要等到第一个线程退出add()方法时,才能继续执行方法。
如果两个线程引用了两个不同的Counter实例,那么他们可以同时调用add()方法。这些方法调用了不同的对象,因此这些方法也就同步在不同的对象上。这些方法调用将不会被阻塞。如下面这个例子所示:
public class Example {public static void main(String[] args){Counter counterA = new Counter();Counter counterB = new Counter();Thread threadA = new CounterThread(counterA);Thread threadB = new CounterThread(counterB);threadA.start();threadB.start();}}
注意这两个线程,threadA和threadB,不再引用同一个counter实例。CounterA和counterB的add方法同步在他们所属的对象上。调用counterA的add方法将不会阻塞调用counterB的add方法。
volatile同步变量
线程为了提高效率,将某成员变量(如A)拷贝了一份(如B),线程中对A的访问其实访问的是B。只在某些动作时才进行A和B的同步。因此存在A和B不一致的情况。volatile就是用来避免这种情况的。volatile告诉jvm, 它所修饰的变量不保留拷贝,直接访问主内存中的(也就是上面说的A) 。
理解volatile特性的一个好方法是:把对volatile变量的单个读/写,看成是使用同一个监视器锁对这些单个读/写操作做了同步。下面我们通过具体的示例来说明。
class VolatileFeaturesExample {volatile long vl = 0L; //使用volatile声明64位的long型变量public void set(long l) {vl = l; //单个volatile变量的写}public void getAndIncrement () {vl++; //复合(多个)volatile变量的读/写}public long get() {return vl; //单个volatile变量的读}
}
假设有多个线程分别调用上面程序的三个方法,这个程序在语意上和下面程序等价:
class VolatileFeaturesExample {long vl = 0L; // 64位的long型普通变量public synchronized void set(long l) { //对单个的普通 变量的写用同一个监视器同步vl = l;}public void getAndIncrement () { //普通方法调用long temp = get(); //调用已同步的读方法temp += 1L; //普通写操作set(temp); //调用已同步的写方法}public synchronized long get() { //对单个的普通变量的读用同一个监视器同步return vl;}
}
如上面示例程序所示,对一个volatile变量的单个读/写操作,与对一个普通变量的读/写操作使用同一个监视器锁来同步,它们之间的执行效果相同。
监视器锁的happens-before规则保证释放监视器和获取监视器的两个线程之间的内存可见性,这意味着对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。
监视器锁的语义决定了临界区代码的执行具有原子性。这意味着即使是64位的long型和double型变量,只要它是volatile变量,对该变量的读写就将具有原子性。如果是多个volatile操作或类似于volatile++这种复合操作,这些操作整体上不具有原子性。
简而言之,volatile变量自身具有下列特性:
可见性。对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。
原子性:对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。
参考文章:
- http://www.ibm.com/developerworks/cn/java/j-jtp06197.html
- http://www.cnblogs.com/Qian123/p/5691705.html
java基础提升篇:synchronized同步块和volatile同步变量相关推荐
- synchronized同步块和volatile同步变量
Java语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量.这两种机制的提出都是为了实现代码线程的安全性.其中 Volatile 变量的同步性较差(但有时它更简单并且开销更低),而 ...
- java基础提升篇:深入浅出Java多线程
初遇 Java给多线程编程提供了内置的支持.一个多线程程序包含两个或多个能并发运行的部分.程序的每一部分都称作一个线程,并且每个线程定义了一个独立的执行路径. 多线程是多任务的一种特别的形式,但多线程 ...
- java基础提升篇:Static关键字
Static变量 static关键字 在类中用static声明的成员变量为静态成员变量,它为该类的公用变量,在第一次使用时初始化,对于该类的所有对象来说,static成员变量只有一份. 可以通过引用或 ...
- 你所需要的java基础提升篇大总结
java基础篇深入解析大总结 java基础(一) 深入解析基本类型 java基础(二) 自增自减与贪心规则 java基础(三) 加强型for循环与Iterator java基础(四) java运算顺序 ...
- 【通知】+ java基础提升篇:Java 序列化的高级认识
点击上方"好好学java",选择"置顶公众号" 优秀学习资源.干货第一时间送达! 好好学java java知识分享/学习资源免费分享 关注 精彩内容 你所需要的 ...
- java基础提升篇:Java 序列化的高级认识
如果你只知道实现 Serializable 接口的对象,可以序列化为本地文件.那你最好再阅读该篇文章,文章对序列化进行了更深一步的讨论,用实际的例子代码讲述了序列化的高级认识,包括父类序列化的问题.静 ...
- Java基础提升篇:equals()与hashCode()方法详解
概述 java.lang.Object类中有两个非常重要的方法: public boolean equals(Object obj) public int hashCode() Object类是类继承 ...
- Java基础提升篇:equals()方法和“==”运算符
点击上方"好好学java",选择"置顶公众号" 优秀学习资源.干货第一时间送达! 好好学java java知识分享/学习资源免费分享 关注 精彩内容 你所需要的 ...
- Java基础提升篇:理解String 及 String.intern() 在实际中的应用
点击上方"好好学java",选择"置顶公众号" 优秀学习资源.干货第一时间送达! 好好学java java知识分享/学习资源免费分享 关注 精彩内容 你所需要的 ...
最新文章
- ICCV 2021 | 用于细粒度 3D 形状分割的基于持久同源的图卷积网络
- ios获取软键盘完成事件
- 字符串编辑距离的计算方法
- Jasypt 加密-整合SpringBoot
- junit链接mysql_java – 使用JUnit进行简单的JDBC连接测试
- jpa获取数据库当前时间_SpringDataJPA存储数据时通过注解自动设置创建时间和修改时间...
- vue项目请求封装;axios封装使用
- 逆水寒7月19服务器维护,逆水寒7月19日更新维护公告 更新内容汇总
- 为什么 Kubernetes 要替换 Docker
- MySQL迁移安装_mysql数据库安装路径迁移
- exec sql_EXEC SQL概述和示例
- 排列(permutation)的末尾 0 的个数
- bsod错误代码。_如何解决BSOD(蓝屏死机)和Windows Stop错误?
- 北航软件工程考研经验总结
- monodepth2训练细节
- Mac 安装 TeamViewer 打开应用闪退
- 力扣第235题“二叉搜索树的最近公共先祖”的解题思路
- Jpeg文件格式转换成bmp文件格式
- 计算机学院校友论坛,西工大计算机学院北京校友论坛成立并举办第一次活动
- Mark Text使用说明
热门文章
- ICallbackEventHandler - 客戶端回調 - 無刷新(對她的理解就是:觸發庫函數-回調-回傳-接收)...
- 再谈STM32的CAN过滤器-bxCAN的过滤器的4种工作模式以及使用方法总结
- pack 内存对齐操作
- ESP8266串口处理
- Springcloud gateway获取post请求内容
- 跨链Cosmos(11) 消息结构
- python_面向对象进阶之属性值的限制
- TEEC_RegisterSharedMemory和TEEC_AllocateSharedMemory的区别
- [ARM-assembly]-ARMv8 A64 Quick Reference
- Wireshark抓取数据包