Java多线程:线程间通信之volatile与sychronized
由前文Java内存模型我们熟悉了Java的内存工作模式和线程间的交互规范,本篇从应用层面讲解Java线程间通信。
Java为线程间通信提供了三个相关的关键字volatile, synchronized和final。对于final,我们在博文Java中static关键字和final关键字中已经介绍。
1. volatile
1.1. 定义
由volatile定义的变量其特殊性在于:
一个线程对变量的写一定对之后对这个变量的读的线程可见。
换言之
一个线程对volatile变量的读一定能看见它之前最后一个线程对这个变量的写。
1.2. 机理
volatile意味着可见性,在讲解volatile的机理前,我先给下面的这个例子:
package com.cielo.main;
/**
* Created by 63289 on 2017/3/31.
*/
class MyThread extends Thread {
private boolean isRunning = true;
public boolean isRunning() {
return isRunning;
}
public void setRunning(boolean isRunning) {
this.isRunning = isRunning;
}
@Override
public void run() {
System.out.println("进入到run方法中了");
while (isRunning == true) {
}
System.out.println("线程执行完成了");
}
}
public class RunThread{
public static void main(String[] args) {
try {
MyThread thread = new MyThread();
thread.start();
Thread.sleep(1000);
thread.setRunning(false);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在这个例子中,主线程启动了子线程,子线程成功进入run方法,输出”进入到run方法中”,只有由于isRunning==true,无限循环。此时,sleep一秒后的主线程想要改变isRunning的值,它将isRunning变量读取到它的内存空间进行修改后,写入主内存,但由于子线程一直在私有栈中读取isRunning变量,没有在主内存中读取isRunning变量,因此不会退出循环。
如果我们把isRunning赋值行改为:
private volatile boolean isRunning = true;
将其用volatile修饰,则强制该变量从主内存中读取。
这样我们也就明白了volatile的实现机理,即:
当一个线程要使用volatile变量时,它会直接从主内存中读取,而不使用自己工作内存中的副本。
当一个线程对一个volatile变量写时,它会将变量的值刷新到共享内存(主内存)中。
1.3. 特性:不会被重排序
从Java内存模型一篇中,我们简单了解了重排序,这里不会被重排序主要指语句重排序。
我们考虑到下面这个例子,有A,B两个线程
线程A:加载配置文件,将配置元素初始化,之后标识初始化成功。
Map configOptions ;
char[] configText;
volatile boolean initialized = false;
//线程A首先从文件中读取配置信息,调用process...处理配置信息,处理完成了将initialized 设置为true
configOptions = new HashMap();
configText = readConfigFile(fileName);
processConfig(configText, configOptions);//负责将配置信息configOptions 成功初始化
initialized = true;
线程B:等待初始化标识为true,之后开始工作。
while(!initialized)
{
sleep();
}
//使用配置信息干活
doSomethingWithConfig();
很简单的一个例子,在编译器中,如果进行重排序,则会有将initialized=true这一行先执行的可能,如果这件事发生的话,线程B就会先运行,进而使用了没有加载配置文件的Object。而如果initialized变量使用了volatile修饰,则编译器不会将该变量的相关代码进行重排序。(当然,这里的例子只是为了直观,实际情况编译器的重排序会更加复杂)
1.4. 非原子性
使用volatile时,我们要清楚,volatile是非原子性的。
原子性即是指,对于一个操作,其操作的内容只有全部执行/全不执行两个状态,不存在中间态。而volatile并不能锁定某组操作,防止其他线程的干扰,即没有规定原子性,因而volatile是非原子性的。或者说,volatile是非线程安全的。
综上,如果我们想要使用一个原子性的修饰符来控制操作,即在操作变量时锁定变量,我们就需要另一个修饰词synchronized。
2. synchronized
2.1. 定义
synchronized作用的代码范围对于不同线程是互斥的,并且线程在释放锁的时候会将共享变量的值刷新到共享内存中。
2.2. synchronized与voliatile区别
使用:voliatile 用于修饰变量,synchronized可以修饰对象,类,方法,代码块,语句。
原子性:voliatile只保证变量的可见性,不能用于同步变量,即不保证原子性,多线程并发访问voliatile修饰的变量时也不会产生阻塞。synchronized是原子性的,只有锁定了变量的线程才能进入临界区,从而保证临界区的所有语句全部执行。多线程并发访问sychronized修饰的变量会产生阻塞。
机理:
当线程对volatile变量读时,会把工作内存中值置为无效。当线程对sychronized变量读时,会在该线程锁定变量时把工作内存中值置为无效。
当线程对voliatile变量写时,会把值刷新到主内存中。当线程对sychronized变量写时,会在变量解锁时把值刷新到主内存中。
2.3. 注意
无论synchronized加在方法上还是对象上,其修饰的都是对象,而不是方法或者某个代码块代码语句。
每个对象只有一个锁与之相关联。
实现同步需要很大的系统开销来做控制,不要做无谓的锁定。
2.4. synchronized的作用域
synchronized的作用域只有两种。实际上,synchronized直接作用于内存中的一个内存块,因此,可以通过锁定内存块来锁定一个实例变量或者锁定一个静态区域。
- 某个对象实例内
synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法,如果对象有多个synchronized方法,则只要一个线程访问了任何一个synchronized方法,其他线程不能同时访问任何一个该对象的synchronized方法(synchronized作用于对象,且每个对象只有一个锁)。
显然,不同对象的synchronized方法则不会互相影响(synchronized作用于对象)。
- 某个类的范围
又或者说作用于静态方法/静态代码块。synchronized static aMethod(){}防止多个线程同时访问这个类中的synchronized static方法,它可以对类的所有实例对象起作用。
2.5. synchronized应用
2.5.1. synchronized方法
每个实例对应一个lock,线程获得该含有synchronized方法的实例的锁才可以执行,否则阻塞。方法一旦执行,则一直到方法返回才可以释放锁。此后被阻塞的线程才能获得该锁。对于一个实例,其声明为synchronized的方法显然只有一个能处于执行状态。从而避免了类访问变量的冲突。
synchronized同步的开销很大,如果synchronized作用于一个比较大的方法上,显然是不合算的。
2.5.2. synchronized代码块
synchronized代码块形式如下:
synchronized (synchronizedObject){
//Some thing
}
代码块内部代码必须在获得synchronizedObject的锁时才能执行。需要重点说的是synchronized(this),这也是比较常用的代码块。
synchronized的效果类似于在方法前修饰,只是修饰的范围缩小成代码块。两个线程同时访问一个变量时,如果一个线程在执行synchronized的代码,那么该实例被锁定,另一个线程如果要访问该实例被synchronized作用的范围,则会被阻塞。
此外,如果不使用this作为锁,而是只是想让一段代码同步,可以临时创建如下锁:
private byte[] lock=new byte[0];
从操作码上讲,创建一个长度为0的数组对象是最经济的,只需要3条操作码。
2.5.3. synchronized静态方法
synchronized修饰静态方法时或者在普通方法中以类为对象如下形式:
class StaticSynchronized{
public void aMethod{
synchronized (StaticSynchronized.class){
//Some thing
}
}
}
为synchronized静态方法。
注意的是,对于同一个类,其static和实例方法如果都用synchronized修饰,其作用的必然不是同一个对象(显然)。
2.5.4. synchronized对象
比较简单粗暴的实现方式,直接把对象锁定,思路也很清晰。Java负责跟踪被加锁的对象,该锁定对象的线程每次给对象加锁时对象的计数器+1,每次解锁时计数器-1,如果对象的计数器为0,那么解除该线程的锁定。
3. 参考文章
如何使用 volatile, synchronized, final 进行线程间通信
JAVA多线程之volatile 与 synchronized 的比较
Java多线程:线程间通信之volatile与sychronized的更多相关文章
- Java多线程——线程间通信
Java多线系列文章是Java多线程的详解介绍,对多线程还不熟悉的同学可以先去看一下我的这篇博客Java基础系列3:多线程超详细总结,这篇博客从宏观层面介绍了多线程的整体概况,接下来的几篇文章是对多线 ...
- Java并发——线程间通信与同步技术
传统的线程间通信与同步技术为Object上的wait().notify().notifyAll()等方法,Java在显示锁上增加了Condition对象,该对象也可以实现线程间通信与同步.本文会介绍有 ...
- 多线程-线程间通信-多生产者多消费者问题(JDK1.5后Lock,Condition解决办法及开发中代码范例)
1 package multithread4; 2 3 import java.util.concurrent.locks.Condition; 4 import java.util.concurre ...
- java多线程-线程间协作
大纲: wait.notify.notifyAll Condition 生产消费者 一.wait.notify.notifyAll wait.notify.notifyAll是Object的本地fin ...
- 多线程-线程间通信-多生产者多消费者问题解决(notifyAll)
1 package multithread4; 2 3 /* 4 * 生产者,消费者. 5 * 6 * 多生产者,多消费者的问题. 7 * 8 * if判断标记,只有一次,会导致不该运行的线程运行了. ...
- 多线程 线程间通信 wait,notify
1. 方法wait锁释放,notify()锁不释放
- Java多线程:线程间通信之Lock
Java 5 之后,Java在内置关键字sychronized的基础上又增加了一个新的处理锁的方式,Lock类. 由于在Java线程间通信:volatile与sychronized中,我们已经详细的了 ...
- Java 里如何实现线程间通信
正常情况下,每个子线程完成各自的任务就可以结束了.不过有的时候,我们希望多个线程协同工作来完成某个任务,这时就涉及到了线程间通信了. 本文涉及到的知识点:thread.join(), object.w ...
- Java 如何实现线程间通信
正常情况下,每个子线程完成各自的任务就可以结束了.不过有的时候,我们希望多个线程协同工作来完成某个任务,这时就涉及到了线程间通信了. 本文涉及到的知识点: thread.join(), object. ...
随机推荐
- javascript之DOM编程正则表达式引入
在javascript中,正则表达式和java中区别不大.只有一小部分不同的地方: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Trans ...
- javascript之prototype原型属性案例
练习: 给字符串对象添加一个toCharArray的方法,然后再添加一个reverse(翻转)的 方法 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML ...
- int(*p)[]和int(**p)[]
1. int(*p)[10]: 根据运算符的结合律,()的优先级最高,所以p是一个指针,指向的一个维度为10的一维数组. p一个指向数组的某一行 int a[1][4]={1,2,3,4}; int ...
- Caffe框架,了解三个文件
不知道从什么时候开始,Deep Learning成为了各个领域研究的热点,也不知道从什么时候开始,2015CVPR的文章出现了很多Deep Learning的文章,更不知道从什么时候开始,三维重建各个 ...
- 04_查看Android内存使用情况
创建项目 Android清单文件 <?xml version="1.0" encoding="utf-8"?> <manifest xm ...
- MySQL学习笔记_4_MySQL创建数据表(下)
MySQL创建数据表(下) 五.数据表类型及存储位置 1.MySQL与大多数数据库不同,MySQL有一个存储引擎概念.MySQL可以针对不同的存储需求选择不同的存储引擎. 2. showengines ...
- Css3盒子模型-css学习之旅(5)
主要内容: 盒子模型内边距,外边距,边框,外边距合并 主要包括:margin(外边距)padding(内边距) border(边框)centent(内容) 内边距:padding,paddinglef ...
- Linux C系统编程:信号与定时器的使用
#include <stdio.h> #include <signal.h> void do_alarm(int num); int main(void) { //注册一个定时 ...
- CCRenderBuffer初始化中的render state参数
绘制纹理三角形的渲染状态(render state)已经被CCSprite基类设置过了,所以你可以简单的将self.renderState传递过去就可以了. 渲染状态是混合模式(blend mode) ...
- Java-HttpSession监听
//HttpSession监听 public interface HttpSessionActivationListener extends EventListener { /** Notificat ...