前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Java并发机制的底层实现原理--Java并发编程的艺术

Java并发机制的底层实现原理--Java并发编程的艺术

原创
作者头像
猎户星座1
修改于 2020-06-22 08:38:13
修改于 2020-06-22 08:38:13
54400
代码可运行
举报
文章被收录于专栏:Java StudyJava Study
运行总次数:0
代码可运行

主要通过两个 Java 并发中常用的关键字volatile 及 synchronied 来将当Java 使用这两个关键字时对计算机的cpu的影响来说明。

1.volatile关键字

首先volatile关键字不会引起上下文的切换。

当volatile 修饰的共享变量时,在进行写操作时,查看Java程序经 编译 解释为机器语言,汇编语言时,发现多了一个lock 的前缀。

接下来因为这个lock前缀的出现,cpu相对正常的线程执行多了一下两个很重要的操作。

(1)将当前处理器(针对多cpu)中的缓存的数据同步到系统内存中。 (缓存指定是如下图中的L1,L2,L3)

(2)这个同步内存的操作,会让其他cpu的缓存该共享数据的数据失效。

书中解释:

为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部

缓存(L1,L2或其他)后再进行操作,但操作完不知道何时会写到内存。如果对声明了volatile的 变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据

写回到系统内存。但是,就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操

作就会有问题。所以,在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一

致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当

处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状

态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存

里。

image.png
image.png

2.volatile的使用优化

增大对象所占内存(追加字节),提供并发的速度?查看原文

此处不整理了

synchronized 关键字

先来看下利用synchronized实现同步的基础:Java中的每一个对象都可以作为锁。具体表现 为以下3种形式。

·对于普通同步方法,锁是当前实例对象。

·对于静态同步方法,锁是当前类的Class对象。

·对于同步方法块,锁是Synchonized括号里配置的对象。

当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。

那么锁到底存在哪里呢?锁里面会存储什么信息呢?

从JVM规范中可以看到Synchonized在JVM里的实现原理,JVM基于进入和退出Monitor对 象来实现方法同步和代码块同步,但两者的实现细节不一样。代码块同步是使用monitorenter 和monitorexit指令实现的,而方法同步是使用另外一种方式实现的,细节在JVM规范里并没有

详细说明。但是,方法的同步同样可以使用这两个指令来实现。

monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结 束处和异常处,JVM要保证每个monitorenter必须有对应的monitorexit与之配对。任何对象都有 一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter 指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获得对象的锁。

使用 syn 来实现 大三上学习 操作系统课时,当时没有用代码研究比较明白的 生产者-消费者问题

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package pv;

import java.util.*;

public class ProudectAndCoummer {
     //定义一个常量 表示生产者 生产到10会停止生产 等 消费者拿走小于10后 再进行生产
    final static  int MAX_VALUE = 10;
    //将生产出产品 存入到一个 队列中 
   volatile static Queue<String> produceStack  = new LinkedList<>();

    public static void main(String[] args) {

       Thread t1  = new Thread(){
                //生产者
             @Override
             public void run() {


                 while (true){
                     synchronized (produceStack){
                     if(produceStack.size()==MAX_VALUE){
                         try {
                             System.out.println("生产者:已经生产到最大值,线程暂停,等待拿走后再继续生产");
                             produceStack.wait();


                         } catch (InterruptedException e) {
                             e.printStackTrace();
                         }
                     }else {

                             try {
                             //生产者 的sleep 时间为50 消费者为 200 可以转化为,多个消费者对一个生产者的
                                 Thread.sleep(50);
                                 produceStack.add("产品");
                                 System.out.println("生产者:生产一个产品放到队列上,当前有"+produceStack.size()+"个产品");
                                 produceStack.notify();
                             } catch (InterruptedException e) {
                                 e.printStackTrace();
                             }
                         }


                     }


             }

             }
         };
        Thread t2   = new Thread(){
           @Override
           public void run() {

               while (true){

                   synchronized (produceStack){
                   if(produceStack.size()==0){
                       System.out.println("消费者:已经没有产品了通知生产者补货");
                       produceStack.notify();
                   }else {


                           try {
                               Thread.currentThread().sleep(200);
                               System.out.println("消费者:拿走一个货物,当前有"+produceStack.size()+"个产品");
                               produceStack.poll();
                           } catch (InterruptedException e) {
                               e.printStackTrace();
                           }

                       }

                   }

               }



           }
       };
  //两个线程开始运行,因为设置的 循环结束时间为while (true) 因此 理论上达到,两者一直在工作,
  // 生产者 生产产品到10后停止, 等待消费者去消费,当产品为0后 ,消费者唤醒线程因为只有两个线程,抛去主线程,
  // 通知生产者去生产产品
  t1.start();
  t2.start();
    }


控制台输出如下:
消费者:拿走一个货物,当前有3个产品
生产者:生产一个产品放到队列上,当前有3个产品
生产者:生产一个产品放到队列上,当前有4个产品
生产者:生产一个产品放到队列上,当前有5个产品
生产者:生产一个产品放到队列上,当前有6个产品
消费者:拿走一个货物,当前有6个产品
生产者:生产一个产品放到队列上,当前有6个产品
生产者:生产一个产品放到队列上,当前有7个产品
生产者:生产一个产品放到队列上,当前有8个产品
生产者:生产一个产品放到队列上,当前有9个产品
生产者:生产一个产品放到队列上,当前有10个产品
生产者:已经生产到最大值,线程暂停,等待拿走后再继续生产
消费者:拿走一个货物,当前有10个产品
消费者:拿走一个货物,当前有9个产品
消费者:拿走一个货物,当前有8个产品
消费者:拿走一个货物,当前有7个产品

Java对象头

synchronized用的锁是存在Java对象头里的。如果对象是数组类型,则虚拟机用3个字宽 (Word)存储对象头,如果对象是非数组类型,则用2字宽存储对象头。在32位虚拟机中,1字宽 等于4字节,即32bit。

锁升级 无锁、偏向锁、轻量级锁,重量级锁。(synchronized 锁才有的)

实际因为发现,多个线程竞争同一把锁,锁总是被同一个线程获得,这肯定是不是设计并发时 想要看到的。

注意,锁可以升级,但不可以 降级。

为了让线程获得锁的代价更低而引入了偏向锁。当一个线程访问同步块并 获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出 同步块时不需要进行CAS操作来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。如果测试成功,表示线程已经获得了锁。如果测试失败,则需 要再测试一下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁):如果没有设置,则 使用CAS竞争锁;如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。(疑惑点)

明天再写啦,发出去,防止自己不写了,今天再看 Java核心技术36讲,感觉好多都能看, 老朋友的感觉了,哈哈哈哈

-------------------------------------------------------华丽不分割线-------------------------------------------------------------------------------------------

今天带有自己学习到的归纳一下知识。

ReentrantLock锁

翻译为重入锁,它比Synchronized 提供了很多使用,且明确的方法,能够做到比syn更加的直观,syn 是jvm进行操控,它的获取锁和 释放锁都是隐式进行的,是,通过方法wait(time)和sleep(time second) 方法可以控制线程的暂停和等待,wait() 释放 锁,notify()随机唤醒一个处在wait中的线程 和notifyAll()唤醒所有的等待睡眠的线程。

reentrantlock 可以通过lock及 unlock 进行 锁的上锁和获取,比较明显的对资源进行操控,因此在使用,r.lock()进行加锁,及使用r.unlock(),进行 释放锁,因此最终该线程的一次执行过程最后 需要用finally{ r.unlock(); } 去包裹住将锁释放,防止因为异常的出现导致 锁无法进行释放。

代码语言:java
AI代码解释
复制
        ReentrantLock r=new ReentrantLock(); //构造方法中添加 Boolean 值,默认为 非公平锁
    //    public ReentrantLock() {
    //    sync = new NonfairSync();
   // }
   
    fnal ReentrantLock lock = this.lock;
    
    
    try{
        //进行的操作 
    
    }
    finally{  //最后使用finally 把锁的所有权释放掉,防止因为 try 捕获到异常,导致锁 无法释放
      lock.unlock();
    }
  
   
  // Condition则是将wait、notify、notifyAll等操作转化为相 应的对象,将复杂而晦涩的同步操作转变为直观可控的对象行为。

        Condition condition = r.newCondition(); //通过condition 来使用 锁的状态进一步修饰
        condition.await();
        condition.signal();

ReentrantLock 相比synchroized ,因为可以像普通对象一样使用,所有可以利用其提供的各种便利方法,进行精细的同步操作

实现比synchroized 来说不能做的操作。

  1. 带超时的获取锁尝试。
  2. 可以判断是否有线程,或者某个特定线程,在排队等待获取锁。
  3. 可以响应中断请求。

通过signal/await的组合,完成了条件判断和通知等待线程,非常顺畅就完成了状态流转。注意,signal和await成对调用非常重要,不然假设只有await动作,线程会一直等待直到 被打断(interrupt)。看到signal 和await ,想到当时 操作系统老师讲的pv操作,起初我以为应该用 reentrantlock 来 用代码表示 生产者消费者 ,因为使用 reentrantlock 来操作锁 的获取 和释放很直观,且signal() 和await ()操作和操作系统老师讲的很像。侧面说明了 使用 reentrantlock的易懂。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Java并发机制的底层实现原理
Java代码 编译之后 得到 Java字节码,被 类加载器加载到JVM中,最终 转化为汇编指令。
103style
2022/12/19
2220
简历上写精通多线程!却不知道:多线程为什么会有并发问题?
大部分同学应该都知道 Synchronized , Lock ,部分同学能说到 volatile 、 并发包 ,优秀的同学则能在前面的基础上,说出Synchronized、volatile的原理,以及并发包中常用的数据结构,例如ConcurrentHashMap的原理。
Java程序猿
2021/05/13
9730
58面试官居然问我Synchronized底层实现,锁升级的具体过程?
这是我去年7,8月份面58被问的一个面试题,说实话被问到这个问题还是很意外的,感觉这个东西没啥用啊,直到后面被问了一波new Object,Integer对象等作为加锁对象行吗?会出现哪些问题?为啥java6后synchronized性能大幅上升?我彻底蒙蔽了。下面详细总结一下
Java识堂
2020/02/19
7991
Java并发之底层实现原理学习笔记
本篇博文将介绍java并发底层的实现原理,我们知道java实现的并发操作最后肯定是由我们的CPU完成的,中间经历了将java源码编译成.class文件,然后进行加载,然后虚拟机执行引擎进行执行,解释为汇编语言,然后转为操作系统指令,然后转为1,0,最后CPU进行识别执行。 提到java的并发,我们不由的就会想到java中常见的键字:volatile和synchronized,我们接下来就会从这两个关机字展开分析: volatile的底层实现原理 synchronized的实现原理和应用 ---- volat
MindMrWang
2018/04/16
7980
高并发编程学习(2)——线程通信详解
上一篇文章我们提到一个应用可以创建多个线程去执行不同的任务,如果这些任务之间有着某种关系,那么线程之间必须能够通信来协调完成工作。
乔戈里
2019/12/02
5080
高并发编程学习(2)——线程通信详解
[Java并发系列] Java并发机制的底层实现
在Java并发实现的机制中,大部分的容器和框架都是依赖于volatile/synchronized/原子操作实现的,了解底层的并发机制,对于并发编程会带来很多帮助 1. synchronized的应用
用户2017109
2018/06/19
5390
Java并发机制的底层实现原理 - synchronized和volatile
synchronized和volatile几乎是java面试基础部分必会,不会你就吃亏了,这一篇文章摘抄《Java并发编程的艺术》
王炸
2019/07/02
5370
Java并发机制的底层实现原理 -  synchronized和volatile
高并发编程学习(2)——线程通信详解
上一篇文章我们提到一个应用可以创建多个线程去执行不同的任务,如果这些任务之间有着某种关系,那么线程之间必须能够通信来协调完成工作。
我没有三颗心脏
2019/11/29
4400
读书笔记《Java并发编程的艺术 - 方腾飞》- 并发机制的底层实现原理
volatile 是轻量级的 synchronize , 它可以保证变量在多线程环境的"可见性", "可见性"是指当一个线程修改了共享变量, 另一个线程能够读到这个修改的值. (根据缓存一致性实现) 和 禁止指令重排序, 但它不是并发安全的.
星尘的一个朋友
2020/11/25
4170
读书笔记《Java并发编程的艺术 - 方腾飞》- 并发机制的底层实现原理
Java并发编程的艺术[2]
2.1-volatile的应用(wall la tai l 还是 wall lei tai l)
疯狂的KK
2020/07/31
7473
Java并发编程的艺术[2]
Java并发总篇
AQS是并发基类 , 通过State以及Exclussive Thread来控制资源总数以及资源独占的线程. 通过LockSupport.park/unpark来控制线程CPU的调度 , 用于让某个线程获取/让出CPU资源.
None_Ling
2020/09/08
6050
【并发编程的艺术】JAVA并发机制的底层原理
在上一篇文章,概述了JVM体系结构和内存模型的基础概念,我们了解到synchronized 和 volatile都属于内存模型中,处理可见性、顺序性、一致性等问题的关键策略,这又涉及到操作系统层面。
程序员架构进阶
2023/09/01
2360
【并发编程的艺术】JAVA并发机制的底层原理
JAVA并发编程系列(10)Condition条件队列-并发协作者
同样今天的分享,我们不纸上谈兵,也不空谈八股文。以实际面经、工作实战经验进行开题,然后再剖析核心源码原理。
拉丁解牛说技术
2024/09/23
1560
《java并发编程实战》总结
①发挥多处理器的强大优势 ②建模的简单性 ③异步事件的简化处理④相应更灵敏的用户界面
CBeann
2023/12/25
2570
《java并发编程实战》总结
并发编程原理剖析——并发编程的实现原理 顶
如果想更深入了解对象头在JVM源码中的定义,需要关心几个文件,oop.hpp/markOop.hpp oop.hpp,每个 Java Object 在 JVM 内部都有一个 native 的 C++ 对象 oop/oopDesc 与之对应。先在oop.hpp中看 oopDesc的定义
须臾之余
2019/07/09
4590
Java 并发编程·Java 并发
可能正在运行,也可能正在等待 CPU 时间片。包含了操作系统线程状态中的 Running 和 Ready。
数媒派
2022/12/01
2.8K0
Java程序员必知的并发编程艺术——并发机制的底层原理实现
Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量。 volatile借助Java内存模型保证所有线程能够看到最新的值。(内存可见性) 实现原理: 将带有volatile变量操作的Java代码转换成汇编代码后,可以看到多了个lock前缀指令(X86平台CPU指令)。这个lock指令是关键,在多核处理器下实现两个重要操作: 1.将当前处理器缓存行的数据写回到系统内存。 2.这个写回内存的操作会使其他处理器里缓存该内存地址的数据失效 如果了解
Java架构
2018/05/04
7750
Java程序员必知的并发编程艺术——并发机制的底层原理实现
Java并发编程系列21 | Condition-Lock的等待通知
我们知道 synchronized 锁通过 Object 类的 wait()和 notify()方法实现线程间的等待通知机制,而比 synchronized 更灵活 Lock 锁同样也有实现等待通知机制的方式,那就是条件 Condition。本文将从以下几个方面介绍 Condition:
java进阶架构师
2020/05/31
7200
并发编程之显式条件
我们之前介绍 synchronized 关键字语义的时候说过,synchronized 虽然不需要我们手动的加锁和释放锁了,但不代表他没有用到锁。同时,我们说每个对象本身结构中也内置了阻塞队列,线程持有器,锁重入计数器等字段。
Single
2019/05/25
3670
并发编程之显式条件
我们之前介绍 synchronized 关键字语义的时候说过,synchronized 虽然不需要我们手动的加锁和释放锁了,但不代表他没有用到锁。同时,我们说每个对象本身结构中也内置了阻塞队列,线程持有器,锁重入计数器等字段。
Single
2019/02/14
5140
推荐阅读
相关推荐
Java并发机制的底层实现原理
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验