并发编程之关键字(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中,为了解决这个问题,引入临界区概念. ...
随机推荐
- 《ElasticSearch入门》一篇管够,持续更新
一.顾名思义: Elastic:灵活的:Search:搜索引擎 二.官方简介: Elasticsearch是一个基于Lucene的搜索服务器.它提供了一个分布式多用户能力的全文搜索引擎,基于RESTf ...
- 使用SSH远程管理时本地文件被修改了
背景: 有两个网段:1段作为工作网段即员工办公用:2段作为专用网段配置了一系列需要的环境. 在Ubuntu 16.04用Python的SSH工具在对这两个网段远程管理,我写了一个检测环境的脚本,用SF ...
- 在IntelliJ IDEA中注释使用的说明
/** * @author 标明该类模块的开发作者 * @version 标明该类模块的版本 * @see 参开转向,也就是相关的主题 * @param 对方法中的某些参数进行说明 * @return ...
- laravel查询常用的方式含义.
find($id) 传值并返回一个模型.如果不存在匹配的模型,则返回null.findOrFail($id) 传值并返回一个模型.如果不存在匹配的模型, 它会抛出异常.first() 返回在数据库中找 ...
- ida 调试android之路
系统: Mac OSX 调试环境:IDA7.0, adb 手机环境:红米手机 android 4.4.4 前提条件: 红米手机root之路:https://www.cnblogs.com/dzqdz ...
- Python的多继承问题-MRO和C3算法
大部分内容转载自C3 线性化算法与 MRO 理解Python中的多继承 Python 中的方法解析顺序(Method Resolution Order, MRO)定义了多继承存在时 Python 解释 ...
- 三文搞懂学会Docker容器技术(下)
接着上面一篇:三文搞懂学会Docker容器技术(上) 三文搞懂学会Docker容器技术(中) 7,Docker容器目录挂载 7.1 简介 容器目录挂载: 我们可以在创建容器的时候,将宿主机的目录与容器 ...
- 黎活明8天快速掌握android视频教程--25_网络通信之资讯客户端
1 该项目的主要功能是:后台通过xml或者json格式返回后台的视频资讯,然后Android客户端界面显示出来 首先后台新建立一个java web后台 采用mvc的框架 所以的servlet都放在se ...
- django 分页器,序列化 ,MTV MVC
序列化组件## from django.core import serializers # django自带的一个小型的序列化工具# def reg(request):# user_list = mo ...
- APP测试之内存命令查询
CPU占有率 adb shell dumpsys cpuinfo :获取本机CPU占有率 adb shell dumpsys cpuinfo | find ...