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

什么时候用synchronized?什么时候用分布式锁?

前言

最近在做项目的时候,遇到一个并发问题,当时一门心思的想着要加锁,一开始加的是分布式锁,后来测试过程中发现这个锁加的太重了,也没实现自己的要求,就开始思考是不是方向有问题了,于是又来复习下这方面的内容。

本地资源和外部数据源

  1. 本地资源和外部数据源的基本概念

    • 本地资源:在单 JVM 环境下,资源如果是在应用程序自己的内存空间(如堆内存中的对象、方法区中的静态变量)或者本地文件系统中(应用程序所在服务器的本地硬盘上的文件),这些都属于本地资源。例如,一个简单的ArrayList对象存储在堆内存中,被应用程序内部的方法使用,这就是本地资源。
    • 外部数据源:外部数据源是指存储在应用程序所在 JVM 之外的存储系统中的数据。这些存储系统可以通过网络进行访问,并且通常是为多个应用程序(可能是多个 JVM)提供数据存储和读取服务的。
  2. 常见的外部数据源类型及识别方法

    • 分布式数据库
      • 连接方式:如果你的应用程序通过网络连接(如使用 JDBC 驱动,并配置了数据库服务器的 IP 地址、端口号、用户名和密码)来访问数据库(如 MySQL Cluster、Oracle RAC 等),那么这个数据库就是外部数据源。例如,在配置文件中有类似jdbc:mysql://database - server - ip:3306/mydatabase这样的数据库连接字符串,就表明是在访问外部的数据库服务器。
      • 多应用共享特征:多个不同的应用程序(不同的 JVM)可以连接到同一个分布式数据库,并且可以对数据库中的表进行读写操作。例如,一个电商系统中的用户服务和订单服务都连接到同一个 MySQL 数据库,它们通过不同的数据库表(如用户表和订单表)来存储和获取数据,这个 MySQL 数据库就是外部数据源。
    • 分布式缓存(如 Redis、Memcached)
      • 连接与配置:当你通过特定的客户端库(如 Jedis 用于 Redis)和配置信息(包括服务器地址、端口号等)来连接缓存系统时,这就是外部数据源。例如,在代码中有Jedis jedis = new Jedis("redis - server - ip", 6379);这样的代码,就表明是在连接外部的 Redis 缓存服务器。
      • 共享缓存数据:多个应用程序(不同的 JVM)可以向缓存中存储数据和获取数据。比如,多个微服务可以将一些经常访问的数据(如用户登录信息、商品热门列表等)存储在 Redis 缓存中,并且可以共享这些缓存数据,这种情况下 Redis 就是外部数据源。
    • 分布式文件系统(如 Ceph、HDFS)
      • 访问接口与协议:如果应用程序通过特定的文件系统客户端库和网络协议(如 Ceph 的 librados 库、HDFS 的 Java API)来访问文件系统中的文件,那么这个文件系统就是外部数据源。例如,在代码中有FileSystem fs = FileSystem.get(new Configuration());(HDFS 的 Java API)这样的代码,并且配置指向了远程的 HDFS 服务器,就表明是在访问外部的分布式文件系统。
      • 多应用共享文件资源:多个不同的应用程序(不同的 JVM)可以对分布式文件系统中的文件进行读写操作。例如,一个大数据处理系统中的多个数据处理任务(分布在不同的 JVM)可以从 HDFS 中读取数据文件,并将处理后的结果写回到 HDFS 中的其他文件,这里的 HDFS 就是外部数据源。
    • 消息队列(如 RabbitMQ、Kafka)
      • 连接与消息发送 / 接收机制:当应用程序通过特定的客户端库(如 RabbitMQ 的 Java 客户端、Kafka 的 Java 客户端)和配置信息(包括服务器地址、端口号、队列名称等)来连接消息队列,并通过发送和接收消息来进行通信时,消息队列就是外部数据源。例如,在代码中有ConnectionFactory factory = new ConnectionFactory(); factory.setHost("mq - server - ip");(RabbitMQ 的 Java 客户端)这样的代码,就表明是在连接外部的消息队列服务器。
      • 多应用消息交互:多个不同的应用程序(不同的 JVM)可以通过消息队列进行消息传递。比如,一个订单服务通过消息队列向库存管理服务发送库存更新消息,库存管理服务接收这些消息并进行库存处理,这里的消息队列就是外部数据源。

什么时候用synchronized?什么时候用分布式锁? 

  1. synchronized 的适用情况

    • 单机环境简单并发场景:如果你的应用是在一台机器上运行,并且只是要防止同一个 Java 程序(JVM)里的多个线程同时访问某个共享资源,比如一个本地的缓存数据结构,或者一个本地文件的读写操作,就可以用 synchronized。它就像是给你家里(JVM)的某个房间(共享资源)上了一把锁,只有拿到这把钥匙(锁)的家庭成员(线程)才能进入这个房间。
    • 资源访问频率不高的情况:当对共享资源的访问不是很频繁,而且你可以接受一定程度的性能损耗(因为 synchronized 相对比较 “重”,会有一些性能开销)。例如一个小型工具类中的方法,偶尔会被多个线程调用,使用 synchronized 可以简单地保证方法执行的互斥性。
  2. 分布式锁的适用情况

    • 分布式系统场景:当你的应用是分布在多台机器上,像一个大型的电商系统,订单服务部署在多个服务器上。这些服务器上的程序可能会同时操作一些共享资源,如全局库存数量。这时候单机的 synchronized 就不管用了,因为它只能控制一台机器上的线程,需要分布式锁来协调不同机器上的程序对共享资源的访问,就好像在多栋楼(不同服务器)之间共享一个仓库(共享资源),需要一种能跨楼控制仓库访问的锁。
    • 高并发且数据一致性要求极高的场景:在高并发环境下,对共享资源的操作必须保证严格的一致性,如金融系统中的账户余额操作。分布式锁可以确保在大量并发请求时,只有一个请求能够修改账户余额,避免数据混乱,就像银行(高并发系统)里多个柜员(不同的服务器请求)操作同一个账户(共享资源)时,需要一种严格的锁机制来保证账户余额的准确。

实例介绍

import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

// 定义一个枚举类型表示游戏角色的职业
enum GameRoleClassEnum {
    WARRIOR, MAGE, ROGUE, PRIEST
}

// 定义游戏角色配置类
class GameRoleConfiguration {
    private int health;
    private int attack;
    private int defense;
    private GameRoleClassEnum roleClass;

    public int getHealth() {
        return health;
    }

    public void setHealth(int health) {
        this.health = health;
    }

    public int getAttack() {
        return attack;
    }

    public void setAttack(int attack) {
        this.attack = attack;
    }

    public int getDefense() {
        return defense;
    }

    public void setDefense(int defense) {
        this.defense = defense;
    }

    public GameRoleClassEnum getRoleClass() {
        return roleClass;
    }

    public void setRoleClass(GameRoleClassEnum roleClass) {
        this.roleClass = roleClass;
    }
}

// 定义游戏角色配置工厂类
class GameRoleConfigFactory {
    public static GameRoleConfiguration getGameRoleConfigByType(Integer type) {
        GameRoleConfiguration config = new GameRoleConfiguration();
        switch (type) {
            case 1:
                config.setRoleClass(GameRoleClassEnum.WARRIOR);
                config.setHealth(100);
                config.setAttack(20);
                config.setDefense(15);
                break;
            case 2:
                config.setRoleClass(GameRoleClassEnum.MAGE);
                config.setHealth(60);
                config.setAttack(30);
                config.setDefense(5);
                break;
            case 3:
                config.setRoleClass(GameRoleClassEnum.ROGUE);
                config.setHealth(70);
                config.setAttack(25);
                config.setDefense(8);
                break;
            case 4:
                config.setRoleClass(GameRoleClassEnum.PRIEST);
                config.setHealth(80);
                config.setAttack(10);
                config.setDefense(12);
                break;
            default:
                break;
        }
        return config;
    }
}

// 游戏角色配置工具类
public class GameRoleUtil {

    private static final int DEFAULT_HEALTH = 50;
    private static final int DEFAULT_ATTACK = 10;
    private static final int DEFAULT_DEFENSE = 8;

    private static final Map<Integer, Function<Integer, GameRoleConfiguration>> roleConfigFunctionMap = new HashMap<>();

    static {
        // 为不同类型的游戏角色配置注册对应的创建函数
        roleConfigFunctionMap.put(1, GameRoleConfigFactory::getGameRoleConfigByType);
        roleConfigFunctionMap.put(2, GameRoleConfigFactory::getGameRoleConfigByType);
        roleConfigFunctionMap.put(3, GameRoleConfigFactory::getGameRoleConfigByType);
        roleConfigFunctionMap.put(4, GameRoleConfigFactory::getGameRoleConfigByType);
    }

    /**
     * 生成游戏角色配置
     *
     * @param type    角色类型
     * @param health  生命值
     * @param attack  攻击力
     * @param defense 防御力
     * @return 生成的游戏角色配置
     */
    public static GameRoleConfiguration generateGameRoleConfig(Integer type, Integer health, Integer attack, Integer defense) {
        Function<Integer, GameRoleConfiguration> generateFunction = roleConfigFunctionMap.get(type);
        GameRoleConfiguration roleConfig = generateFunction.apply(type);
        health = health == null? DEFAULT_HEALTH : health;
        attack = attack == null? DEFAULT_ATTACK : attack;
        defense = defense == null? DEFAULT_DEFENSE : defense;
        roleConfig.setHealth(health);
        roleConfig.setAttack(attack);
        roleConfig.setDefense(defense);
        return roleConfig;
    }
}

总结

写着写着突然发现有些基本功丢失了,常拾常新。

 


http://www.kler.cn/a/473458.html

相关文章:

  • 比较procfs 、 sysctl和Netlink
  • ubuntu 20.04 安装 5.4 内核
  • python对redis的增删查改
  • 【Unity3D】Text文本文字掉落效果
  • NodeLocal DNS 全攻略:从原理到应用实践
  • 计算机网络之---有线网络的传输介质
  • Spring Boot 集成 Easysearch 完整指南
  • 老牌工具,16年依然抗打!
  • 【Java基础】使用Apache POI和Spring Boot实现Excel文件上传和解析功能
  • Linux下文件操作相关接口
  • 备考蓝桥杯:顺序表相关算法题
  • 软件工程实验-实验2 结构化分析与设计-总体设计和数据库设计
  • 数据库第一次作业-----数据库的多种部署方式
  • 代码随想录 day59 第十一章 图论part09
  • SQL Server中可以通过扩展事件来自动抓取阻塞
  • 攻防世界 ics-07
  • 51单片机——定时器中断(重点)
  • 全天候高效响应,中关村科金智能客服机器人优化客户体验
  • Hive部署内嵌模式、本地模式、远程模式
  • 现场展示deepseek VS openAI o1模型大对比
  • BI结合数据分析系统,为企业发展提供坚实的保障
  • WD5105同步降压转换器:9.2V-95V宽电压输入,4.5A大电流输出,95%高效率,多重保护功能
  • Java 注解详解:RetentionPolicy 与 ElementType
  • [Git] git pull --rebase / git rebase origin/master
  • 用VS C#构建Windows服务【纯操作版,附带项目地址】
  • python_excel列表单元格字符合并、填充、复制操作