003 ++ --

文章目录

    • [++ --](#++ --)
    • [为了解决这个问题,你可以使用 synchronized 关键字来确保每次只有一个线程可以访问 increment() 方法:](#为了解决这个问题,你可以使用 synchronized 关键字来确保每次只有一个线程可以访问 increment() 方法:)
    • [或者,你也可以使用 AtomicInteger,这是一个线程安全的整数类:](#或者,你也可以使用 AtomicInteger,这是一个线程安全的整数类:)
    • 乐观锁

++ --

在Java中,++ 和 -- 操作符并不是线程安全的。这意味着,如果在多线程环境中直接对这些操作符进行并发操作,可能会导致数据的不一致。

java 复制代码
public class Counter {  
    private int count = 0;  
  
    public void increment() {  
        count++;  
    }  
  
    public int getCount() {  
        return count;  
    }  
}

如果有两个线程同时调用 increment() 方法,并且都读取了 count 的值(比如都是0),然后都对其加1,那么两个线程可能都会将 count 设置为1,而不是期望的2。这就是所谓的竞态条件(Race Condition),它导致了数据的不一致。

为了解决这个问题,你可以使用 synchronized 关键字来确保每次只有一个线程可以访问 increment() 方法:

java 复制代码
public class Counter {  
    private int count = 0;  
  
    public synchronized void increment() {  
        count++;  
    }  
  
    public synchronized int getCount() {  
        return count;  
    }  
}

或者,你也可以使用 AtomicInteger,这是一个线程安全的整数类:

java 复制代码
import java.util.concurrent.atomic.AtomicInteger;  
  
public class Counter {  
    private AtomicInteger count = new AtomicInteger(0);  
  
    public void increment() {  
        count.incrementAndGet();  
    }  
  
    public int getCount() {  
        return count.get();  
    }  
}

在这两种情况下,你都可以确保在多线程环境中对 count 的操作是线程安全的

在Java中,++ 和 -- 操作符本身并不具备线程安全性。线程安全性意味着在并发环境中,对共享数据的操作不会导致数据不一致或其他未定义的行为。++ 和 -- 操作符的不安全性主要源于它们包含多个步骤,这些步骤在多线程环境中可能被其他线程的操作打断,从而导致数据竞争(data race)和竞态条件(race condition)。

具体来说,count++(或 count--)这样的操作实际上包含了三个步骤:

读取 count 的当前值。

对该值进行加1(或减1)操作。

将新值写回到 count。

在单线程环境中,这三个步骤是顺序执行的,因此不会出现问题。但在多线程环境中,如果有多个线程同时执行这些步骤,就可能发生以下情况:

两个线程可能几乎同时读取 count 的值,导致它们获取到相同的初始值。

然后,这两个线程可能都会对这个值进行加1(或减1)操作,并试图将结果写回 count。

最终,count 的值只会增加1(或减少1),而不是期望的增加2(或减少2),因为两个线程的操作互相覆盖了。

这种情况就是典型的竞态条件,它导致了数据的不一致性。

为了解决这个问题,需要确保对 count 的操作是原子的,即这些操作在执行过程中不会被其他线程打断。Java提供了几种方法来实现这一点:

使用 synchronized 关键字来同步方法或代码块,确保在同一时间只有一个线程可以执行这些操作。

使用Java的 java.util.concurrent.atomic 包中的原子类,如 AtomicInteger,这些类提供了线程安全的原子操作。

通过这些方法,可以确保在多线程环境中对共享数据的操作是线程安全的,从而避免数据竞争和竞态条件的发生。

乐观锁

java 复制代码
import java.util.concurrent.atomic.AtomicStampedReference;  
  
public class OptimisticLockCounter {  
    private AtomicStampedReference<Integer> countRef;  
    private static final int INITIAL_VALUE = 0;  
    private static final int INITIAL_STAMP = 0;  
  
    public OptimisticLockCounter() {  
        countRef = new AtomicStampedReference<>(INITIAL_VALUE, INITIAL_STAMP);  
    }  
  
    public boolean increment() {  
        int[] stampHolder = new int[1];  
        Integer oldValue, newValue;  
        do {  
            oldValue = countRef.get(stampHolder); // 获取当前值和邮戳  
            newValue = oldValue + 1; // 计算新值  
            // 尝试在邮戳未更改的情况下设置新值  
        } while (!countRef.compareAndSet(oldValue, newValue, stampHolder[0], stampHolder[0] + 1));  
        return true; // 如果成功更新,则返回true  
    }  
  
    public boolean decrement() {  
        int[] stampHolder = new int[1];  
        Integer oldValue, newValue;  
        do {  
            oldValue = countRef.get(stampHolder); // 获取当前值和邮戳  
            newValue = oldValue - 1; // 计算新值  
            // 尝试在邮戳未更改的情况下设置新值  
        } while (!countRef.compareAndSet(oldValue, newValue, stampHolder[0], stampHolder[0] + 1));  
        return true; // 如果成功更新,则返回true  
    }  
  
    public int getCount() {  
        return countRef.getReference(); // 获取当前计数值  
    }  
  
    // 新增一个尝试获取当前计数值和邮戳的方法,便于测试  
    public int[] getCountAndStamp() {  
        return new int[]{countRef.getReference(), countRef.getStamp()};  
    }  
}
相关推荐
七星静香24 分钟前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
Jacob程序员25 分钟前
java导出word文件(手绘)
java·开发语言·word
ZHOUPUYU26 分钟前
IntelliJ IDEA超详细下载安装教程(附安装包)
java·ide·intellij-idea
stewie629 分钟前
在IDEA中使用Git
java·git
Elaine20239144 分钟前
06 网络编程基础
java·网络
G丶AEOM1 小时前
分布式——BASE理论
java·分布式·八股
落落鱼20131 小时前
tp接口 入口文件 500 错误原因
java·开发语言
想要打 Acm 的小周同学呀1 小时前
LRU缓存算法
java·算法·缓存
镰刀出海1 小时前
Recyclerview缓存原理
java·开发语言·缓存·recyclerview·android面试
阿伟*rui3 小时前
配置管理,雪崩问题分析,sentinel的使用
java·spring boot·sentinel