并发编程之关键字(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中,为了解决这个问题,引入临界区概念. ...
随机推荐
- iOS简历书写注意事项
1.个人信息模块 1)简历标题 2)姓名 性别 年龄 电话 邮箱 常驻地 学历 英语能力 工作年限 籍贯 专业 (突出优势) 注意:不要从招聘网站导出简历网站 2.求职意向 1)职位 地点 薪资 ...
- 这一次搞懂Spring事务注解的解析
前言 事务我们都知道是什么,而Spring事务就是在数据库之上利用AOP提供声明式事务和编程式事务帮助我们简化开发,解耦业务逻辑和系统逻辑.但是Spring事务原理是怎样?事务在方法间是如何传播的?为 ...
- equals与hashCode的区别
equals与hashCode的区别 1.类中的equals方法是一定要重写/覆盖(Override)的,因为要让它按照设计的需求来根据特征值判断等价性. 这里的特征值,就是String类型的name ...
- opencv3.1.0 计算机中丢失 opencv_world310d.dll _vs2017解决方法
---------------------------opencv1.exe - 系统错误---------------------------无法启动此程序,因为计算机中丢失 opencv_worl ...
- JavaWeb网上图书商城完整项目--day02-18.修改密码页面处理
1.用户登陆成功之后会显示 当点击修改密码的时候,会进入下面的页面 对应的是pwd.jsp这个文件 我们把对jsp页面前段的校验都封装在pwd.js中,在jsp中引入该js文件 <%@ page ...
- 入门大数据---Hive常用DML操作
Hive 常用DML操作 一.加载文件数据到表 1.1 语法 LOAD DATA [LOCAL] INPATH 'filepath' [OVERWRITE] INTO TABLE tablename ...
- 前端开发神器Charles从入门到卸载
前言 本文将带大家学习使用前端开发神器-charles,从基本的下载安装到常见配置使用,为大家一一讲解. 一.花式夸奖Charles 截取 Http 和 Https 网络封包. 支持重发网络请求,方便 ...
- Python实用笔记 (23)面向对象高级编程——使用__slots__
正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性.先定义class: class Student(object): pa ...
- webpack入门进阶(2)
1.4.webpack-dev-server webpack-dev-server是我们在开发阶段需要用到的一个服务器,它会把代码打包到内存,我们可以通过http的方式访问到打包到内存的代码 安装 n ...
- Python之浅谈函数
目录 文件的高级应用 文件修改的两种方式 第一种 第二种 函数的定义 函数的参数 函数的返回值 文件的高级应用 r+即可读又可写,并且是在后面追加 w+清空文件的功能是w提供的 a+a有追加的功能,a ...