当前位置: 首页 > news >正文

并发学习之synchronized,JVM内存图,线程基础知识

文章目录

  • Java内存图
    • 内存图区域介绍
    • 执行流程
  • 进程和线程
    • 概念解释
    • 线程的6种状态
    • 简述等待队列和同步队列(阻塞队列)
    • 线程之间是独立的
  • synchronized
    • 静态方法
    • 非静态方法
    • 代码块

知识总结:

  • 方法区存储类信息
  • 正在执行的程序叫进程,进程会开辟内存空间
  • 程序的执行过程 :线程
  • 线程的执行-----》方法的入栈出栈
  • Java创建线程-----》Java创建一个又一个栈

Java内存图

Java内存的主要组成部分:

  • 虚拟机栈
  • 堆区
  • 方法区
  • 本地方法栈
  • 程序计数器

在这里插入图片描述

内存图区域介绍

  1. 虚拟机栈:每个线程都会有自己的虚拟机栈,是线程私有的区域。
    • 虚拟机栈主要用于存储栈帧,每个方法被调用时都会创建一个表栈帧。栈帧包含方法的局部变量,操作数栈和其他等。
      • 操作数栈:主要用于 方法执行过程中的数据计算和临时存储。它的行为类似于传统CPU的 运算寄存器,但采用 栈结构(LIFO) 来存储和操作数据。
  2. **堆:**堆是一个线程共享区域,是Java内存管理的核心区域。
    • 通过new关键字创建的对象实例和数组都存储在堆中。
  3. 方法区:方法区是一个线程共享区域,主要用于存储类的相关信息,比如类的字段,方法的定义,常量等。
    • 常量池:常量池位于方法区中,用于存放编译期生成的各种字面量和符号引用。
      • 字面量:指的是在源代码中直接给出的数据值,比如直接写在源代码中的数字,字符串,字符,布尔值等。他们是常量的直观表达方式,在编译阶段就会确定下来。例如:在int num = 10;10为整形字面量。在String str = "hello"中,“hello”为字符串字面量。
      • 符号引用:符号引用是一种间接引用,它在编译时用于表示类、方法、字段等在常量池中的引用。符号引用主要包括类和接口的全限定名、字段的名称和描述符、方法的名称和描述符等信息。这些信息在编译阶段被存储在常量池中,用于在运行时解析和定位实际的类、方法和字段等实体。(符号引用是在编译阶段放到常量池的。)
  4. **本地方法栈:**是线程的私有区域,主要为Native方法(使用非Java语言编写的方法,如C/C++)提供服务,用于存储Native方法的调用栈信息,支持Java程序对Native的调用。
  5. **程序计数器:**每个线程都有一个程序计数器,是线程的私有区域。主要用于存储线程正在执行的字节码指令的地址,使得线程能够按照正确的顺序执行字节码指令,当线程执行Java方法时,程序计数器记录当前执行的字节码地址;执行Native方法时,程序计数器的值为undefined。

执行流程

  1. 编译:.java 到.class 的过程叫编译 。在这个过程中,Java 编译器(如 javac 命令 )对.java 源文件进行词法分析、语法分析、语义分析等操作,将符合 Java 语法规则的代码转换为字节码形式的.class 文件。字节码是一种与平台无关的中间代码,可被 Java 虚拟机(JVM)识别并执行,从而实现 “一次编写,到处运行” 的特性 。

  2. **类加载:**Java 虚拟机(JVM)将.class 字节码文件加载到内存中,此过程由类加载器完成。

    • 加载时会检查字节码文件的格式等是否正确,然后在方法区创建对应的类模板,存放类的结构信息,比如字段、方法等。例如 Demo.class 会被加载,其类信息放入方法区。
  3. 连接

    • 验证:确保被加载的类字节码文件符合 JVM 规范,比如文件格式、语义等方面的校验,保障程序运行安全。
    • 准备:为类的静态变量分配内存并设置默认初始值,如静态变量 static int num; 此时会被赋予默认值 0 。
    • 解析:将常量池中的符号引用转换为直接引用,使 JVM 能直接定位到类、方法、字段等实体。
  4. 初始化

    对类的静态变量赋予程序员设定的初始值,执行静态代码块。若有多个静态变量和静态代码块,按代码中出现的顺序执行初始化操作。

  5. 执行

    • 程序从 main 方法开始执行,在虚拟机栈中为 main 方法创建栈帧。栈帧里存放局部变量、操作数栈等信息。执行过程中,遇到对象创建指令,会在堆中分配内存创建对象。比如执行到 new 关键字时,就在堆中开辟空间构建对象实例。
    • 当调用其他方法时,为被调用方法创建新栈帧并压入虚拟机栈,方法执行完,对应栈帧弹出,返回上一层调用方法继续执行。

进程和线程

概念解释

  1. 进程:进程是正在执行的程序的实例。
    • 进程是资源分配的基本单位。
    • 不同进程之间内存隔离(一个进程崩溃通常不会影响其他进程)。
    • 启动一个Java程序时,JVM就是一个进程。
  2. 线程:线程是进程内的执行单元,负责执行程序代码。一个进程可以包含多个线程,所有线程共享进程的内存空间(如堆、方法区),但每个线程有自己的栈。
    • Java创建一个线程就是在虚拟机栈中创建一个又一个栈。
    • 线程是CPU调度的基本单位。
    • 线程共享进程资源,但有自己的程序计数器、栈和局部变量。
  3. 线程的执行 → 方法的入栈出栈
    • 栈(Stack):每个线程拥有独立的栈,用于存储方法调用的上下文(局部变量、方法参数、返回地址等)。
    • 入栈(Push):调用方法时,JVM会创建一个栈帧(Stack Frame)并入栈。
    • 出栈(Pop):方法执行完毕后,栈帧被弹出,控制权返回给调用者。

线程的6种状态

  1. 新建

    • 描述:线程创建但未被启动。

    • 触发条件

      Thread thread = new Thread(); // 此时状态为 NEW(新建)
      
  2. 可运行/就绪

    • 线程已启动,正在 JVM 中执行或等待 CPU 调度。

    • 包含两种情况

      • Ready:等待操作系统分配 CPU 时间片。
      • Running:正在执行。
    • 触发条件

      thread.start(); // 进入 RUNNABLE 状态
      
  3. BLOCKED(阻塞)

    • 描述:线程因竞争 同步锁 被阻塞(如 synchronized)。是被动的。
    • 触发条件
      • 线程尝试获取一个已被其他线程持有的锁。
  4. WAITING(无限等待)

    • 描述:线程主动进入等待状态,直到被其他线程显式唤醒。
    • 触发方法
      • Object.wait()(未指定超时)
      • Thread.join()(未指定超时)
      • LockSupport.park()
  5. TIMED_WAITING(超时等待)

    • 描述:线程主动进入限时等待状态,超时后自动唤醒。
    • 超时后进入就绪态
    • 触发方法
      • Thread.sleep(long millis)
      • Object.wait(long timeout)
      • Thread.join(long millis)
      • LockSupport.parkNanos()
  6. TERMINATED(终止)

    • 描述:线程执行完毕或异常退出。
    • 触发条件
      • run() 方法执行结束。
      • 线程抛出未捕获异常。

简述等待队列和同步队列(阻塞队列)

特性等待队列(Wait Queue)同步队列(Sync Queue)
触发条件调用 Object.wait()竞争锁失败(如 synchronizedlock.lock()
是否释放锁wait() 会释放锁)(线程仍持有竞争资格)
唤醒机制notify()/notifyAll()锁释放后自动唤醒队头线程
典型应用生产者-消费者模型ReentrantLocksynchronized

线程之间是独立的

在这里插入图片描述

  • 线程之间是相互独立的,main,t1,t2相互独立,结果会立马输出0,不会管t1,t2线程的任务是否执行完
  • 输出结果为0
  • 加上t1.join()和t2.join()方法,就会等待t1,t2线程执行完毕再输出,结果将不再是0。由于两个线程之间会相互覆盖,最终结果也会小于2000000。

synchronized

  • 加在静态方法上:锁定的是类
  • 加在非静态方法:锁定的是方法的调用者,当前实例。
  • 修饰代码块:锁定的是传入的对象

静态方法

public class Test {public static void main(String[] args) {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {SyncTest.test();}});Thread thread1 = new Thread(new Runnable() {@Overridepublic void run() {SyncTest.test();}});thread.start();thread1.start();}
}
public class SyncTest {public static void test(){System.out.println("进入静态方法锁"+Thread.currentThread().getName());//模拟耗时操作try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("静态方法锁结束"+Thread.currentThread().getName());}
}

结果(执行顺序不定):

在这里插入图片描述

非静态方法

两个线程操作不同实例时不会阻塞,操作同一实例时会阻塞。

下面这个例子Thread-2必须等待Thread-0释放锁。

public class Test {public static void main(String[] args) {SyncTest syncTest1 = new SyncTest();SyncTest syncTest2 = new SyncTest();Thread thread = new Thread(new Runnable() {@Overridepublic void run() {syncTest1.test1();}});Thread thread1 = new Thread(new Runnable() {@Overridepublic void run() {syncTest2.test1();}});Thread  thread2 = new Thread(new Runnable() {@Overridepublic void run() {syncTest1.test1();}});thread.start();thread1.start();thread2.start();}
}
public synchronized void test1()
{System.out.println("进入同步方法锁"+Thread.currentThread().getName());//模拟耗时操作try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("同步方法锁结束"+Thread.currentThread().getName());
}
输出:
进入同步方法锁Thread-0
进入同步方法锁Thread-1
同步方法锁结束Thread-1
同步方法锁结束Thread-0
进入同步方法锁Thread-2
同步方法锁结束Thread-2

代码块

锁的是传入的对象,两个线程使用不同锁对象时不会阻塞,使用相同锁对象时会阻塞。

下面这个例子Thread-2必须等待Thread-0释放锁。

public class SyncTest {private final Object lock1 = new Object();private final Object lock2 = new Object();public void customLock1() {synchronized (lock1) {System.out.println(Thread.currentThread().getName() + " 进入 lock1 代码块");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + " 离开 lock1 代码块");}}public void customLock2() {synchronized (lock2) {System.out.println(Thread.currentThread().getName() + " 进入 lock2 代码块");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + " 离开 lock2 代码块");}}}public class Test {public static void main(String[] args) {SyncTest instance  = new SyncTest();Thread thread = new Thread(new Runnable() {@Overridepublic void run() {instance.customLock1();}});Thread thread1 = new Thread(new Runnable() {@Overridepublic void run() {instance.customLock2();}});Thread  thread2 = new Thread(new Runnable() {@Overridepublic void run() {//锁住的是同一个对象,会阻塞instance.customLock1();}});thread.start();thread1.start();thread2.start();}
}
输出:
Thread-1 进入 lock2 代码块
Thread-0 进入 lock1 代码块
Thread-0 离开 lock1 代码块
Thread-1 离开 lock2 代码块
Thread-2 进入 lock1 代码块
Thread-2 离开 lock1 代码块

相关文章:

  • Hi3516DV500刷写固件
  • 392. Is Subsequence
  • linux线程基础
  • 摄影构图小节
  • Linux线程同步信号量
  • Vue-键盘事件
  • React学习(二)-变量
  • Centos7.9同步外网yum源至内网
  • 2025最新的软件测试面试大全(含答案+文档)
  • Java获取淘宝拍立淘API接口的详细指南
  • DeepSeek 大模型部署全指南:常见问题、优化策略与实战解决方案
  • 精益数据分析(64/126):移情阶段的用户触达策略——从社交平台到精准访谈
  • 开源项目实战学习之YOLO11:12.2 ultralytics-models-sam-decoders.py源码分析
  • 淘特入口无痕秒单怎么做的?
  • deepin v23.1 搜狗输入法next配置中文输入法下默认用英文标点
  • 如何在Cursor中高效使用MCP协议
  • [Java] 方法和数组
  • impala
  • 实验七 基于Python的数字图像水印算法
  • 【SpringBoot】MyBatisPlus(MP | 分页查询操作
  • 上海这个咖啡文化节首次“走出去”,率本土品牌亮相英国伦敦
  • 南京艺术学院博导、雕塑家尹悟铭病逝,年仅45岁
  • 原核试验基地司令员范如玉逝世,从事核试验研究超40年
  • 有关“普泽会”,俄官方表示:有可能
  • 61岁云浮市律师协会副会长谭炳光因突发疾病逝世
  • 80后女博士黄双燕拟提名为内蒙古盟市政府(行署)副职人选