【Java并发】【synchronized】适合初学者体质入门的synchronized

简介: 欢迎来到我的Java线程同步入门指南!我不是外包员工,梦想是写高端CRUD。2025年我正在沉淀中,博客更新速度加快,欢迎点赞、收藏、关注。本文介绍Java中的`synchronized`关键字,适合初学者。`synchronized`用于确保多个线程访问共享资源时不会发生冲突,避免竞态条件、保证内存可见性、防止原子性破坏及协调多线程有序访问。

👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD
🔥 2025本人正在沉淀中... 博客更新速度++
👍 欢迎点赞、收藏、关注,跟上我的更新节奏
📚欢迎订阅专栏,专栏别名《在2B工作中寻求并发是否搞错了什么》

前言

这一篇同样是sychronzied的入门篇,不会涉及底层原理和实现,很适合初学者的学习。好了, 不废话了,让我们马上开始吧🤗

入门

什么是synchronized?

synchronized 是 Java 中的关键字,用于实现线程同步,确保多个线程在访问共享资源时不会发生冲突。它可以修饰方法或代码块,保证同一时间只有一个线程执行被修饰的代码,从而避免数据不一致问题。

为什么用synchronized?

  1. 解决竞态条件(Race Condition)

问题:多个线程同时修改同一个共享变量时,操作顺序可能被打乱,导致结果不可预测。
如果两个线程同时调用 increment(),可能发生以下情况:线程 A 读取 count=0 → 线程 B 也读取 count=0 → 两者都改为 1 → 最终 count=1(实际应为 2)。

// 有问题的count++
public class Counter {
   
    private int count = 0;

    public void increment() {
   
        count++; // 这行代码实际包含三步:读取值 → 修改值 → 写回值
    }
}

解决:使用synchronized解决,synchronized确保同一时刻只有一个线程能执行increment(),避免值被覆盖。

public class Counter {
   
    private int count = 0;

    // 添加 synchronized 关键字
    public synchronized void increment() {
   
        count++; // 现在是一个原子操作
    }
}
  1. 保证内存可见性

问题:线程有自己的工作内存(缓存),修改共享变量后可能不会立即同步到主内存,导致其他线程看到旧值。

// 存在问题的flag读和写方法
public class VisibilityDemo {
   
    private boolean flag = false;

    public void setFlag() {
   
        flag = true; // 线程 A 修改 flag
    }

    public void checkFlag() {
   
        while (!flag); // 线程 B 可能永远看不到 flag 变为 true
    }
}

解决:使用synchronized解决,通过 synchronized 的锁机制,强制线程从主内存读取最新值,避免可见性问题。

public class VisibilityDemo {
   
    private boolean flag = false;

    // 添加 synchronized 保证可见性
    public synchronized void setFlag() {
   
        flag = true; // 修改后立即同步到主内存
    }

    // 同样用 synchronized 读取
    public synchronized boolean checkFlag() {
   
        return flag; // 从主内存读取最新值
    }
}
  1. 避免原子性破坏

问题:某些操作看似是“一步完成”,但实际由多个底层指令组成(如 i++),多线程环境下可能被分割执行,比如下面的转账例子。

// 非原子操作
public void transfer(Account from, Account to, int amount) {
   
    if (from.balance >= amount) {
   
        from.balance -= amount; // 非原子操作
        to.balance += amount;    // 可能被其他线程打断
    }
}

解决:使用synchronized解决。这里更安全的做法是使用全局锁(如定义一个 final Object lock),避免嵌套锁导致的死锁风险。

public void transfer(Account from, Account to, int amount) {
   
    // 锁定两个账户对象,避免并发修改
    synchronized (from) {
   
        synchronized (to) {
   
            if (from.balance >= amount) {
   
                from.balance -= amount;
                to.balance += amount;
            }
        }
    }
}
  1. 协调多线程的有序访问

问题:多个线程需要按特定顺序操作共享资源(如生产者-消费者模型)。

public class Queue {
   
    private List<Integer> list = new ArrayList<>();

    public synchronized void add(int value) {
   
        list.add(value); // 生产者线程添加数据
    }

    public synchronized int remove() {
   
        return list.remove(0); // 消费者线程移除数据
    }
}

解决synchronized 确保同一时刻只有一个线程操作队列,避免并发异常。

public class Queue {
   
    private List<Integer> list = new ArrayList<>();

    // 添加和移除方法均用 synchronized 保护
    public synchronized void add(int value) {
   
        list.add(value);
    }

    public synchronized int remove() {
   
        if (!list.isEmpty()) {
   
            return list.remove(0);
        }
        return -1; // 或抛异常
    }
}

怎么用synchronized?

我们可以看到,JLS已经规定了,可以修饰在方法和代码块中。
image.png

修饰方法

1.修饰实例方法
锁是当前对象实例(this),同一对象的多个线程调用该方法时会互斥。

public class Counter {
   
    private int count = 0;

    // 修饰实例方法:锁是当前对象实例
    public synchronized void increment() {
   
        count++;
    }
}

使用场景:多个线程操作同一个对象的实例方法时(如单例对象的资源修改)。

2.修饰静态方法
锁是类的 Class 对象(如 Counter.class),所有线程调用该类的静态方法时会互斥。

public class Counter {
   
    private static int count = 0;

    // 修饰静态方法:锁是 Counter.class
    public static synchronized void increment() {
   
        count++;
    }
}

使用场景:多线程操作静态变量(如全局计数器)。

修饰代码块

可以指定任意对象作为锁,灵活性更高。

1.锁是当前对象实例(this)

public void doSomething() {
   
    // 同步代码块:锁是当前对象实例
    synchronized (this) {
   
        // 需要同步的代码
    }
}

2.锁是类对象(Class)

public void doSomething() {
   
    // 同步代码块:锁是 Counter.class
    synchronized (Counter.class) {
   
        // 需要同步的代码
    }
}

3.锁是任意对象

private final Object lock = new Object();

public void doSomething() {
   
    // 同步代码块:锁是自定义对象
    synchronized (lock) {
   
        // 需要同步的代码
    }
}

syncrhonized在框架源码中的使用

Vector 和 Hashtable

这些类在 JDK 早期版本中通过synchronized修饰所有公共方法实现线程安全。例如Vectoradd() 方法:

public synchronized boolean add(E e) {
   
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = e;
    return true;
}

缺点:锁粒度粗(整个方法加锁),性能较低,现代开发中多被ConcurrentHashMapCollections.synchronizedList()替代。

StringBuffer

StringBuffer的方法均用synchronized修饰以实现线程安全,例如append()

public synchronized StringBuffer append(String str) {
   
    toStringCache = null;
    super.append(str);
    return this;
}

总结

synchronized的优点

  1. 简单易用
    • 只需在方法或代码块上添加关键字即可实现线程同步,无需手动管理锁的获取和释放。
  2. 自动释放锁
    • 当同步代码块执行完毕或发生异常时,锁会自动释放,避免死锁风险。
  3. 内置锁优化
    • JVM 对 synchronized 进行了大量优化,如锁升级机制(偏向锁 → 轻量级锁 → 重量级锁),在低竞争场景下性能较好。
  4. 内存可见性
    • 通过 synchronized 的锁机制,可以保证线程对共享变量的修改对其他线程可见(遵循 happens-before 原则)。
  5. 结构化锁
    • 锁的获取和释放必须成对出现,减少编码错误。

synchronized的缺点

  1. 性能开销
    • 在高竞争场景下,synchronized 会升级为重量级锁,导致线程阻塞和上下文切换,性能较差。
  2. 锁粒度较粗
    • 如果直接修饰方法,可能导致锁的范围过大,降低并发性能。
  3. 不可中断
    • 线程在等待锁时无法被中断(Lock 接口支持可中断的锁获取)。
  4. 功能有限
    • 不支持尝试获取锁(tryLock)、超时获取锁、公平锁等高级功能(ReentrantLock 支持)。
  5. 嵌套锁可能导致死锁
    • 如果多个线程以不同顺序获取嵌套锁,可能导致死锁。

synchronized的适用场景

  1. 低竞争场景
    • 当线程竞争不激烈时,synchronized 的性能足够好,且实现简单。
  2. 简单的线程同步需求
    • 如计数器、单例模式、简单的生产者-消费者模型等。
  3. 需要快速实现线程安全
    • 在开发初期或对性能要求不高的场景下,synchronized 是快速实现线程安全的有效工具。
  4. 需要保证内存可见性
    • 当多个线程需要共享变量时,synchronized 可以确保变量的修改对其他线程可见。
  5. 锁粒度较粗的场景
    • 如果锁的范围不需要特别精细,直接修饰方法即可满足需求。

后话

什么就结束了?别急,这个synchronized的原理,也是很有说法的。

点上关注,主播马上带你们深入学习synchronized

最近主播的下班时间都是准点,这下沉淀爽了🤗

参考

Chapter 17. Threads and Locks

目录
相关文章
|
11天前
|
存储 Oracle Java
java零基础学习者入门课程
本课程为Java零基础入门教程,涵盖环境搭建、变量、运算符、条件循环、数组及面向对象基础,每讲配示例代码与实践建议,助你循序渐进掌握核心知识,轻松迈入Java编程世界。
103 0
|
2月前
|
Java
java入门代码示例
本文介绍Java入门基础,包含Hello World、变量类型、条件判断、循环及方法定义等核心语法示例,帮助初学者快速掌握Java编程基本结构与逻辑。
297 0
|
2月前
|
Java API 数据库
2025 年最新 Java 实操学习路线,从入门到高级应用详细指南
2025年Java最新实操学习路线,涵盖从环境搭建到微服务、容器化部署的全流程实战内容,助你掌握Java 21核心特性、Spring Boot 3.2开发、云原生与微服务架构,提升企业级项目开发能力,适合从入门到高级应用的学习需求。
488 0
|
2月前
|
前端开发 Java 数据库连接
帮助新手快速上手的 JAVA 学习路线最详细版涵盖从入门到进阶的 JAVA 学习路线
本Java学习路线涵盖从基础语法、面向对象、异常处理到高级框架、微服务、JVM调优等内容,适合新手入门到进阶,助力掌握企业级开发技能,快速成为合格Java开发者。
387 3
|
2月前
|
监控 Java API
2025 年全新出炉的 Java 学习路线:从入门起步到实操精通的详细指南
2025年Java学习路线与实操指南,涵盖Java 21核心特性、虚拟线程、Spring Boot 3、微服务、Spring Security、容器化部署等前沿技术,助你从入门到企业级开发进阶。
465 0
|
3月前
|
NoSQL Java 关系型数据库
Java 从入门到进阶完整学习路线图规划与实战开发最佳实践指南
本文为Java开发者提供从入门到进阶的完整学习路线图,涵盖基础语法、面向对象、数据结构与算法、并发编程、JVM调优、主流框架(如Spring Boot)、数据库操作(MySQL、Redis)、微服务架构及云原生开发等内容,并结合实战案例与最佳实践,助力高效掌握Java核心技术。
313 1
|
3月前
|
Java 测试技术 API
Java IO流(二):文件操作与NIO入门
本文详解Java NIO与传统IO的区别与优势,涵盖Path、Files类、Channel、Buffer、Selector等核心概念,深入讲解文件操作、目录遍历、NIO实战及性能优化技巧,适合处理大文件与高并发场景,助力高效IO编程与面试准备。
|
3月前
|
Java 编译器 API
Java Lambda表达式与函数式编程入门
Lambda表达式是Java 8引入的重要特性,简化了函数式编程的实现方式。它通过简洁的语法替代传统的匿名内部类,使代码更清晰、易读。本文深入讲解Lambda表达式的基本语法、函数式接口、方法引用等核心概念,并结合集合操作、线程处理、事件回调等实战案例,帮助开发者掌握现代Java编程技巧。同时,还解析了面试中高频出现的相关问题,助你深入理解其原理与应用场景。
|
3月前
|
安全 Java 数据库连接
2025 年最新 Java 学习路线图含实操指南助你高效入门 Java 编程掌握核心技能
2025年最新Java学习路线图,涵盖基础环境搭建、核心特性(如密封类、虚拟线程)、模块化开发、响应式编程、主流框架(Spring Boot 3、Spring Security 6)、数据库操作(JPA + Hibernate 6)及微服务实战,助你掌握企业级开发技能。
424 3
|
3月前
|
算法 Java 测试技术
零基础学 Java: 从语法入门到企业级项目实战的详细学习路线解析
本文为零基础学习者提供完整的Java学习路线,涵盖语法基础、面向对象编程、数据结构与算法、多线程、JVM原理、Spring框架、Spring Boot及项目实战,助你从入门到进阶,系统掌握Java编程技能,提升实战开发能力。
165 0