并发编程之关键字(synchronized、volatile)
并发编程主要设计两个关键字:一个是synchronized,另一个是volatile。下面主要讲解这两个关键字,并对这两个关机进行比较。
synchronized
synchronized是通过JMV种的monitorenter和monitorexit指令实现同步。monitorenter指令是在编译后插入到同步代码的开始位置,而monitorexit插入到同步代码的结束位置和异常位置。每一个对象都与一个monitor相关联,当monitor被只有后,它将处于锁定状态。
当一个线程试图访问同步代码时,它必须先获得锁;退出或者抛出异常时,必须释放锁。Java中,每一个对象都可以作为锁。具体的表现形式有3种:
- 对于普通的同步方法,锁是当前的实例对象(this对象)
权限修饰符 synchronized 返回值类型 函数名(形参列表..){
//函数体
}
- 对于静态同步方法,锁是当前类的Class对象
权限修饰符 static synchronized 返回值类型 函数名(形参列表..){
//函数体
}
- 对于同步方法块,锁是Synchronized括号中配置的对象
- 锁对象必须是多线程共享的对象,否则锁不住
Synchronized(锁){
//需要同步的代码块
}
注意:在同步代码块/同步方法中调用sleep()不会释放锁对象,调用wait()会释放锁对象
Synchronized提供了一种排他式的数据同步机制,某个线程在获取monitor lock的时候可能会被阻塞,而这种阻塞有两个明显的缺陷:1. 无法控制阻塞时长; 2. 阻塞不能被中断
public class SyncDefect {
/**
*线程休眠一个小时
*/
public synchronized void syncMethod(){
try {
TimeUnit.HOURS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
SyncDefect defect = new SyncDefect();
new Thread(defect::syncMethod,"t1").start();
//休眠3毫秒后启动线程t2,确保t1先进入同步方法
TimeUnit.MILLISECONDS.sleep(3);
Thread t2 = new Thread(defect::syncMethod, "t2");
t2.start();
//休眠3毫秒后中断线程t2,确保t2已经启动
TimeUnit.MILLISECONDS.sleep(3);
t2.interrupt();
System.out.println(t2.isInterrupted()); //true
System.out.println(t2.getState()); //BLOCKED
}
}
针对synchronized的两个缺点,可以使用BooleanLock来解决
public interface Lock {
void lock() throws InterruptedException;
/**
* 指定获取锁的超时时间
* @param mills 等待获取锁的最大时间
* @throws InterruptedException
* @throws TimeoutException
*/
void lock(long mills) throws InterruptedException, TimeoutException;
void unlock();
List<Thread> getBlockedThreads();
}
public class BooleanLock implements Lock {
/**
* 记录取得锁的线程
*/
private Thread currentThread;
/**
* Bollean开关,标志锁是否已经被获取
*/
private boolean locked = false;
private List<Thread> blockedList = new ArrayList<>();
@Override
public void lock() {
//使用同步代码块的方式获取锁
synchronized (this) {
Thread currentThread = Thread.currentThread();
//当锁已经被某个线程获取,将当前线程加入阻塞队列,并使用this.wait()释放thisMonitor
while (locked){
try {
if(!blockedList.contains(currentThread)){
blockedList.add(currentThread);
}
this.wait();
} catch (InterruptedException e) {
blockedList.remove(currentThread);
e.printStackTrace();
}
}
blockedList.remove(currentThread);
this.locked = true;
this.currentThread = currentThread;
}
}
@Override
public void lock(long mills) throws InterruptedException, TimeoutException {
synchronized (this){
if(mills <= 0) {//时间不合法,调用默认的lock()
this.lock();
} else {
long remainingMills = mills;
long endMills = System.currentTimeMillis() + remainingMills;
while (locked) {
if (remainingMills <= 0) {//在指定的时间内未获取锁或者当前线程被其它线程唤醒,抛出异常
throw new TimeoutException(Thread.currentThread().getName()+" can't get lock during "+mills);
}
if(!blockedList.contains(Thread.currentThread())){
blockedList.add(Thread.currentThread());
}
//等待remainingMills后重新尝试获取锁
this.wait(remainingMills);
remainingMills = endMills - System.currentTimeMillis();
}
blockedList.remove(Thread.currentThread());
this.locked = true;
this.currentThread = Thread.currentThread();
}
}
}
@Override
public void unlock() {
synchronized (this) {
if(Thread.currentThread() == currentThread) {
this.locked = false;
this.notifyAll();
}
}
}
@Override
public List<Thread> getBlockedThreads() {
return Collections.unmodifiableList(blockedList);
}
}
/**
* 测试阻塞中断
*/
public class BooleanLockInterruptTest { private final Lock lock = new BooleanLock(); public void syncMethod() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName()+" get lock.");
TimeUnit.HOURS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("BLOCKED THREAD :"+lock.getBlockedThreads());
lock.unlock();
}
} public static void main(String[] args) throws InterruptedException {
BooleanLockInterruptTest test = new BooleanLockInterruptTest(); new Thread(test::syncMethod,"t1").start();
TimeUnit.MILLISECONDS.sleep(3);
Thread t2 = new Thread(test::syncMethod, "t2");
t2.start();
TimeUnit.MILLISECONDS.sleep(3);
t2.interrupt();
System.out.println(t2.isInterrupted()); //true
System.out.println(t2.getState()); //RUNNABLE
}
}
/**
* 测试超时
*/
public class BooleanLockTimeOutTest { private final Lock lock = new BooleanLock(); public void syncTimeOutMethod() {
try {
lock.lock(1000);
System.out.println(Thread.currentThread().getName()+" get lock.");
TimeUnit.HOURS.sleep(1);
} catch (InterruptedException | TimeoutException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
} public static void main(String[] args) throws InterruptedException {
BooleanLockTimeOutTest test = new BooleanLockTimeOutTest(); new Thread(test::syncTimeOutMethod,"t1").start();
TimeUnit.MILLISECONDS.sleep(3);
new Thread(test::syncTimeOutMethod, "t2").start();
}
}
针对是synhronized还有一些概念及相关知识点需要补充
- Monitor
- 每一个对象都与一个Monitor相关联,一个monitor的lock在某一刻只能被一个线程获取。
- monitor有一个计数器,当为0时,该monitor的lock未被获取;当有线程持获取monitor时,则monitor计数器加一,释放时减一。
- Monitor分为This Monitor和Class Monitor。This Monitor对应类的实例方法,Class Monitor对应类的静态方法。
- synchronized关键字实例方法时,争取的是同一个monitor的锁,与之关联的引用是ThisMonitor的实例引用。即:同一个类中的不同多线程方法,使用的是同一个锁。
- 将静态方法声明为synchronized。该静态方法被调用后,对应的class对象将会被锁住(使用的是ClassMonitor)。其他线程无法调用该class对象的所有静态方法, 直到资源被释放。
- Synchronized使用wait()进入条件对象的等待集,使用notifyAll()/notify()唤醒等待集中的线程。
public synchronized void transfer(int from , int to,
double amount) throws InterruptedException{
while(accounts[from] < amount){
//wait on intrinsic object lock’s single condition
wait();
}
accounts[from] -= amount;
accounts[to] += amount;
//notify all threads waiting on the condition
notifyAll();
}
volatile
volatile是轻量级的synchronized,它为实例域的同步访问提供了一种免锁机制,不会引起线程上下文的切换和调度。它在多处理器开发中保证了共享变量的“可见性“,如果一个属性被声明成volatile,Java模型会确保所有的线程看到这个变量的值时一致的。【volatile变量不能提供原子性】
volatile主要用来锁住一个属性,在对该属性的值进行写操作时,会将数据写回主存,并将CPU里缓存了该内存地址的数据无效。【线程在对volatile修饰的变量进行读写操作时,会首先检查线程缓存的值是否失效,如果失效,就会从主存中把数据读到线程缓存里】。 volatile本质上就是告诉JVM当前变量的值需要从主存中读取,当前变量的值被修改后直接刷新到主存中,且不需要被编译器优化(即:禁止命令重排)。
使用示范:
private volatile boolean done;
public boolean isDone(){
return done;
}
public void setDone(boolean done){
this.done = done;
} // Same as private boolean done;
public synchronized boolean isDone(){
return done;
}
public synchronized void setDone(boolean done){
this.done = done;
}
比较synchronized和volatile
|
|
volatile
|
synchronized
|
|
作用对象
|
实例变量、类变量
|
方法、代码块
|
|
原子性
|
不具备
|
具备
|
|
可见性
|
具备
|
具备
|
|
可见性原理
|
使用机器指令的方式迫使其它工作内存中的变量失效
|
利用monitor锁的排它性实现
|
|
是否会指令重排
|
否
|
是
|
|
是否造成线程阻塞
|
否
|
是
|
并发编程之关键字(synchronized、volatile)的更多相关文章
- 并发编程(一)—— volatile关键字和 atomic包
本文将讲解volatile关键字和 atomic包,为什么放到一起讲呢,主要是因为这两个可以解决并发编程中的原子性.可见性.有序性,让我们一起来看看吧. Java内存模型 JMM(java内存模型) ...
- 【Java并发编程】6、volatile关键字解析&内存模型&并发编程中三概念
volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以 ...
- Java并发编程(六)volatile关键字解析
由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我们先来了解一下与内存模型相关的概念和知识. 一.内存模型的相关概念 Java内存模型规定所有的变量都是存在 ...
- Java并发编程:JMM和volatile关键字
转载请标明出处: http://blog.csdn.net/forezp/article/details/77580491 本文出自方志朋的博客 Java内存模型 随着计算机的CPU的飞速发展,CPU ...
- Java并发编程——为什么要用volatile关键字
首发地址 https://blog.leapmie.com/archives/66ba646f/ 日常编程中出现 volatile 关键字的频率并不高,大家可能对 volatile 关键字比较陌生,再 ...
- 【Java并发编程】11、volatile的使用及其原理
一.volatile的作用 在<Java并发编程:核心理论>一文中,我们已经提到过可见性.有序性及原子性问题,通常情况下我们可以通过Synchronized关键字来解决这些个问题,不过如果 ...
- Java并发编程的艺术(三)——volatile
1. 并发编程的两个关键问题 并发是让多个线程同时执行,若线程之间是独立的,那并发实现起来很简单,各自执行各自的就行:但往往多条线程之间需要共享数据,此时在并发编程过程中就不可避免要考虑两个问题:通信 ...
- Java并发编程(三)volatile域
相关文章 Java并发编程(一)线程定义.状态和属性 Java并发编程(二)同步 Android多线程(一)线程池 Android多线程(二)AsyncTask源代码分析 前言 有时仅仅为了读写一个或 ...
- 【Java并发编程实战】-----synchronized
在我们的实际应用当中可能经常会遇到这样一个场景:多个线程读或者.写相同的数据,访问相同的文件等等.对于这种情况如果我们不加以控制,是非常容易导致错误的.在java中,为了解决这个问题,引入临界区概念. ...
随机推荐
- lw_oopc(c语言实现面向过程宏文件)解析
一:计算结构体成员变量偏移量宏 #ifdef LW_OOPC_USE_USER_DEFINED_OFFSETOF // 有些环境可能不支持,不过,这种情形极少出现 #define LW_OOPC_OF ...
- 旷世提出类别正则化的域自适应目标检测模型,缓解场景多样的痛点 | CVPR 2020
论文基于DA Faster R-CNN系列提出类别正则化框架,充分利用多标签分类的弱定位能力以及图片级预测和实例级预测的类一致性,从实验结果来看,类该方法能够很好地提升DA Faster R-CNN系 ...
- Python初识函数
Python初识函数 函数理论篇 什么是函数 在编程语言中的函数不同于数学中的函数.不管是数学上的函数还是编程语言中的函数都是为了完成特定的某一功能而诞生的,他们的区别在于: 1.数学中的函数当输入的 ...
- 微信小程序之页面跳转(tabbar跳转及页面内跳转)
一.简介 微信小程序页面主要分为tabbar页面和应用内页面,这两种页面的跳转方式不同 二.tabBar页面跳转 tabBar 是底部导航栏页面,如下图 在app.json中的配置如下: 跳转方式如下 ...
- linux下将多个ts文件合并为一个MP4文件
1. 安装ffmpeg工具 sudo apt install ffmpeg 2. 确保所有ts文件无损坏后,确保当前目录(即存放ts文件的目录)无txt文件及mp4文件,在存放ts文件的目录下建立te ...
- jQurey Select2 4.0
https://jeesite.gitee.io/front/jquery-select2/4.0/index.htm
- 十位大牛做出的web前端开发规范总结
Web前端作为开发团队中不可或缺的一部分,需要按照相关规定进行合理编写(一部分不良习惯可能给自己和他人造成不必要的麻烦).不同公司不同团队具有不同的规范和文档.下面是根据不同企业和团队的要求进行全面详 ...
- Linux中清空docker容器日志
新建文件docker-clear-log,放在/usr/local/bin/目录下,文件内容如下: #!/bin/bash -e if [[ -z $ ]]; then echo "No c ...
- Oauth2.0认证流程
- rem和px
做过一段时间的H5页面,但是对于rem与px的换算还是比较模糊,总是引用一段脚本,也没有深究过为什么,就稀里糊涂的用了,遇到一些细微的地方,总是不知道是什么原因导致的,我总是只要能完成效果就行,全然不 ...