并发编程主要设计两个关键字:一个是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)的更多相关文章

  1. 并发编程(一)—— volatile关键字和 atomic包

    本文将讲解volatile关键字和 atomic包,为什么放到一起讲呢,主要是因为这两个可以解决并发编程中的原子性.可见性.有序性,让我们一起来看看吧. Java内存模型 JMM(java内存模型) ...

  2. 【Java并发编程】6、volatile关键字解析&内存模型&并发编程中三概念

    volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以 ...

  3. Java并发编程(六)volatile关键字解析

    由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我们先来了解一下与内存模型相关的概念和知识. 一.内存模型的相关概念 Java内存模型规定所有的变量都是存在 ...

  4. Java并发编程:JMM和volatile关键字

    转载请标明出处: http://blog.csdn.net/forezp/article/details/77580491 本文出自方志朋的博客 Java内存模型 随着计算机的CPU的飞速发展,CPU ...

  5. Java并发编程——为什么要用volatile关键字

    首发地址 https://blog.leapmie.com/archives/66ba646f/ 日常编程中出现 volatile 关键字的频率并不高,大家可能对 volatile 关键字比较陌生,再 ...

  6. 【Java并发编程】11、volatile的使用及其原理

    一.volatile的作用 在<Java并发编程:核心理论>一文中,我们已经提到过可见性.有序性及原子性问题,通常情况下我们可以通过Synchronized关键字来解决这些个问题,不过如果 ...

  7. Java并发编程的艺术(三)——volatile

    1. 并发编程的两个关键问题 并发是让多个线程同时执行,若线程之间是独立的,那并发实现起来很简单,各自执行各自的就行:但往往多条线程之间需要共享数据,此时在并发编程过程中就不可避免要考虑两个问题:通信 ...

  8. Java并发编程(三)volatile域

    相关文章 Java并发编程(一)线程定义.状态和属性 Java并发编程(二)同步 Android多线程(一)线程池 Android多线程(二)AsyncTask源代码分析 前言 有时仅仅为了读写一个或 ...

  9. 【Java并发编程实战】-----synchronized

    在我们的实际应用当中可能经常会遇到这样一个场景:多个线程读或者.写相同的数据,访问相同的文件等等.对于这种情况如果我们不加以控制,是非常容易导致错误的.在java中,为了解决这个问题,引入临界区概念. ...

随机推荐

  1. Zookeeper——基本使用以及应用场景(手写实现分布式锁和rpc框架)

    文章目录 Zookeeper的基本使用 Zookeeper单机部署 Zookeeper集群搭建 JavaAPI的使用 Zookeeper的应用场景 分布式锁的实现 独享锁 可重入锁 实现RPC框架 基 ...

  2. 在windows上安装docker

    开启Hyper-V 添加方法非常简单,把以下内容保存为.cmd文件,然后以管理员身份打开这个文件.提示重启时保存好文件重启吧,重启完成就能使用功能完整的Hyper-V了.   pushd " ...

  3. cb10a_c++_顺序容器的操作3关系运算符

    cb10a_c++_cb09a_c++_顺序容器的操作3 2 顺序容器的操作3 3 关系运算符 4 所有的容器类型都可以使用 5 比较的容器必须具有相同的容器类型,double不能与int作比较 6 ...

  4. c++无法解析的外部符号 "int const bufferSize

    无法解析的外部符号 "int const bufferSize 严重性 代码 说明 项目 文件 行 禁止显示状态错误 LNK2001 无法解析的外部符号 "int const bu ...

  5. Python学习手册(第4版) - 专业程序员的养成完整版PDF免费下载_百度云盘

    Python学习手册(第4版) - 专业程序员的养成完整版PDF免费下载_百度云盘 提取码:g7v1 作者简介 作为全球Python培训界的领军人物,<Python学习手册:第4版>作者M ...

  6. 08.利用Easymock测试简单的servlet

    1.首先导入需要使用的servlet的jar包 接下来我们编写一个登陆的servlet package com.fjnu.service; import java.net.HttpRetryExcep ...

  7. Mybatis学习笔记(1)

    CRUD操作 1.从实体类参数中取值 #{属性名} select * from user where username = #{username} 2.当sql语句只有一个参数且参数类型是基本类型或基 ...

  8. Docker(三)Docker常用命令

    Docker常用命令 帮助命令 # 显示 Docker 版本信息 docker version # 显示系统信息,包括镜像和容器的数量 docker info # 查看帮助文档 帮助文档地址:http ...

  9. 入门大数据---Spark累加器与广播变量

    一.简介 在 Spark 中,提供了两种类型的共享变量:累加器 (accumulator) 与广播变量 (broadcast variable): 累加器:用来对信息进行聚合,主要用于累计计数等场景: ...

  10. No configuration file found and no output filename configured via Cli option.报错

    webpack手动配置webpack.config.js文件,打包时出现的报错,可以试试这种解决方案 报错如下: No configuration file found and no output f ...