@synchronized的使用

@synchronized 介绍

@synchronized 是 Objective-C 提供的一种 互斥锁(Mutex),它用于确保一段代码在同一时间只有一个线程能执行,避免多线程访问共享资源时出现数据竞争

基本语法

@synchronized (lockObject) {
    // 需要加锁的代码
}
  • lockObject 是锁的标识,不同的对象代表不同的锁。
  • 如果多个线程使用相同的 lockObject,则 @synchronized 会保证同一时间只有一个线程能执行代码块。
  • 如果 lockObject 为空 (nil),不会起到任何加锁效果,代码块仍然会并发执行。

@synchronized 使用场景

1. 保护共享资源

如果多个线程同时修改同一个对象,可能会出现数据竞争问题。例如:

#import <Foundation/Foundation.h>

@interface MyClass : NSObject
@property (nonatomic, assign) NSInteger count;
@end

@implementation MyClass

- (void)incrementCount {
    @synchronized(self) {
        self.count = self.count + 1;
    }
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MyClass *myObject = [[MyClass alloc] init];
        dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
        
        // 创建多个线程同时调用 incrementCount 方法
        for (int i = 0; i < 1000; i++) {
            dispatch_async(concurrentQueue, ^{
                [myObject incrementCount];
            });
        }
        
        // 等待所有任务完成
        dispatch_barrier_sync(concurrentQueue, ^{
            NSLog(@"Final count: %ld", (long)myObject.count);
        });
    }
    return 0;
}

多次执行结果

Final count: 1000

Final count: 1000

Final count: 1000

如果去掉@synchronized,你会发现结果大多都不是1000

2. 多线程安全地添加元素到数组

多个线程可能同时访问 NSMutableArray,如果没有同步措施,可能会崩溃:

@interface SafeArray : NSObject
@property (nonatomic, strong) NSMutableArray *array;
@end

@implementation SafeArray

- (instancetype)init {
    if (self = [super init]) {
        _array = [NSMutableArray array];
    }
    return self;
}

- (void)addItem:(id)item {
    @synchronized (self) {
        [self.array addObject:item];
    }
}

@end

注意事项

  • 性能开销@synchronized 会带来一定的性能开销,因为每次进入和退出 @synchronized 块都需要进行锁的获取和释放操作。因此,在性能敏感的场景下,应谨慎使用。
  • 死锁风险:如果在 @synchronized 块中嵌套使用 @synchronized 块,并且使用相同的锁对象,可能会导致死锁。因此,在使用 @synchronized 时,要避免出现死锁的情况。
<think>我们正在讨论的是Java中的两个注解:@Scheduled和@Synchronized。这两个注解分别来自Spring框架和Project Lombok库。 1. **@Scheduled**:Spring框架中的注解,用于任务调度。标记有@Scheduled的方法会被定期调用,可以配置固定速率、固定延迟或cron表达式。 2. **@Synchronized**:Project Lombok提供的注解,用于方法级别的同步。它会在方法上生成一个同步块,确保同一时间只有一个线程可以执行该方法。 **使用场景**: - 当我们需要定期执行某个任务(使用@Scheduled),并且这个任务需要是线程安全的(使用@Synchronized)时,这两个注解可能会一起使用。 **注意事项**: - **锁对象的选择**:@Synchronized默认使用当前实例作为锁(即`synchronized(this)`),但如果是静态方法,则使用当前类的Class对象作为锁。注意,如果多个方法使用同一个锁对象,可能会导致不必要的阻塞。 - **性能考虑**:同步会带来性能开销,特别是在高频率调度的任务中。因此,应确保同步块尽可能小,避免在同步块中执行耗时操作。 - **死锁风险**:避免在同步方法中调用其他同步方法,否则可能引发死锁。 - **Spring代理机制**:由于Spring的AOP代理机制,@Scheduled注解是通过代理对象调用的,而@Synchronized是在目标对象上实现的。因此,这两个注解的组合使用在大多数情况下是可行的,但要注意代理模式(如JDK动态代理和CGLIB代理)可能对锁的影响。 - **集群环境**:在分布式环境中,使用@Synchronized只能保证单个JVM内的同步。如果需要在多个实例之间同步,需要使用分布式锁。 **示例代码**: ```java import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import lombok.Synchronized; @Component public class ScheduledTask { // 使用@Scheduled注解,每5秒执行一次 @Scheduled(fixedRate = 5000) @Synchronized // 确保同一时间只有一个线程执行该方法 public void doScheduledTask() { // 执行需要同步的任务逻辑 // 注意:这里的方法会在多个线程中调用(由调度器触发),但@Synchronized保证了同一时刻只有一个线程执行 } } ``` **替代方案**: - 如果任务的执行时间可能超过调度间隔,可以考虑使用其他同步机制(如ReentrantLock)或调整调度策略(例如设置`@Scheduled(fixedDelay)`等待上次执行完成后再开始下一次)。 - 在集群环境中,需要使用分布式锁(如Redis锁、ZooKeeper等)来替代@Synchronized。 **总结**: - 在单机环境下,结合使用@Scheduled和@Synchronized可以确保定时任务的线程安全。 - 在分布式系统中,需要额外的机制来实现跨JVM的同步。 - 注意锁的粒度和性能影响。 §§相关问题§§ 1. @Scheduled注解有哪些参数可以配置? 2. 除了@Synchronized,还有哪些实现线程安全的方法? 3. 在Spring中,如何实现分布式定时任务? </think>### @Scheduled 与 @Synchronized 结合使用指南 #### 一、核心功能说明 1. **@Scheduled** Spring框架的任务调度注解,用于定时执行方法,支持三种模式: - `fixedRate`:固定频率执行(上次开始后间隔) - `fixedDelay`:固定延迟执行(上次结束后间隔) - `cron`:Cron表达式控制 2. **@Synchronized** Lombok提供的线程同步注解,本质是为方法生成`synchronized`代码块: ```java // Lombok编译后实际代码 public synchronized void method() { // ... } ``` #### 二、组合使用场景 ✅ **适用场景**: 1. **共享资源保护** 定时任务需修改共享数据(如全局计数器、缓存数据)时防止并发冲突: ```java @Component public class DataSyncService { private int counter = 0; @Scheduled(fixedRate = 5000) @Synchronized public void syncData() { counter++; // 安全操作共享变量 // 数据同步逻辑... } } ``` 2. **第三方API调用限流** 避免高频触发外部API的速率限制: ```java @Scheduled(cron = "0/10 * * * * ?") @Synchronized public void callExternalAPI() { // 保证每分钟最多6次调用 } ``` 3. **文件/IO操作防冲突** 多任务禁止同时读写同一文件: ```java @Scheduled(fixedDelay = 10000) @Synchronized public void writeLogFile() { // 独占文件写入 } ``` #### 三、注意事项与风险 ⚠️ **关键问题**: 1. **死锁风险** 嵌套调用同步方法时可能死锁: ```java @Synchronized public void methodA() { methodB(); // 可能触发死锁 } @Synchronized public void methodB() { // ... } ``` 2. **性能下降** 高频调度任务中,锁竞争会导致线程阻塞: ``` +------------------+---------------------+ | 调度频率 | 吞吐量下降幅度 | +------------------+---------------------+ | 100ms/次 | 最高达40% | | 1s/次 | <10% | +------------------+---------------------+ ``` 3. **集群环境失效** `@Synchronized`仅在单JVM内生效,分布式环境需改用: - Redis分布式锁 - ZooKeeper协调 - Quartz集群调度 4. **锁粒度问题** 默认使用`this`对象锁,高并发时建议细化锁对象: ```java private final Object lock = new Object(); @Scheduled(...) public void task() { synchronized(lock) { // 更细粒度的锁 // ... } } ``` #### 四、最佳实践 1. **替代方案推荐** - 使用`ReentrantLock`替代同步块(支持超时机制): ```java private final Lock lock = new ReentrantLock(); @Scheduled(...) public void safeTask() { if (lock.tryLock(1, TimeUnit.SECONDS)) { try { // ... } finally { lock.unlock(); } } } ``` 2. **调度配置建议** - 避免`fixedRate`搭配同步:上次执行未完成时新任务会阻塞 - 优先使用`fixedDelay`:确保每次执行间隔可控 3. **监控必备** 添加任务执行时长日志: ```java @Scheduled(...) @Synchronized public void monitoredTask() { long start = System.currentTimeMillis(); // ...业务逻辑 log.info("Task cost: {}ms", System.currentTimeMillis()-start); } ``` > **总结**:在单机低并发场景下可谨慎使用组合注解,分布式环境或高性能要求场景需改用分布式锁+异步队列方案。Spring官方文档建议任务调度应设计为幂等操作[^1],减少对锁的依赖。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值