线程安全的AtomicLong使用

本文探讨了在多线程环境中使用普通计数器类可能导致的问题,并通过引入AtomicLong改进了计数器类,确保了多线程环境下的计数器操作能够正确执行。

看一个计数的类:

[java]  view plain  copy
  1.   
[java]  view plain  copy
  1. public class Counter {  
  2.     private static long counter = 0;  
  3.     public static long addOne(){  
  4.         return ++counter;  
  5.     }  
  6. }  

初看感觉没啥问题,但这个类在多线程的环境下就会有问题了。

假如开多个线程都来使用这个计数类,它会表现的“不稳定”

[java]  view plain  copy
  1. public static void main(String[] args) {  
  2.     for(int i=0;i<100;i++){  
  3.         Thread thread = new Thread(){  
  4.             @Override  
  5.             public void run() {  
  6.                 try {  
  7.                     Thread.sleep(100);  
  8.                     if(Counter.addOne() == 100){  
  9.                         System.out.println("counter = 100");  
  10.                     }  
  11.                 } catch (InterruptedException e) {  
  12.                     e.printStackTrace();  
  13.                 }  
  14.             }  
  15.         };  
  16.         thread.start();  
  17.     }  
  18. }  

程序会开100个线程,每个线程都会把counter 加一。那么应该有一个线程拿到counter =100的值,但实际运行情况是大多数据情况下拿不到100,在少数情况能拿到100.

因为 Counter 类在 addOne()方法被调用时,并不能保证线程的安全,即它不是原子级别的运行性,而是分多个步骤的,打个比方:

线程1首先取到counter 值,比如为10,然后它准备加1,这时候被线程2占用了cpu,它取到counter为10,然后加了1,得到了11。这时线程1 又拿到了CPU资源,继续它的步骤,加1为11,然后也得到了11。这就有问题了。

那么怎么解决它呢?JDK在concurrent包里提供了一些线程安全的基本数据类型的实现,比如 Long型对应的concurrent包的类是AtomicLong。

现在修改下代码:

[java]  view plain  copy
  1. import java.util.concurrent.atomic.AtomicLong;  
  2.   
  3.   
  4. public class Counter {  
  5.     private static AtomicLong counter = new AtomicLong(0);  
  6.   
  7.   
  8.     public static long addOne() {  
  9.         return counter.incrementAndGet();  
  10.     }  
  11. }  


运行了多次,结果都是能输出counter = 100。


所以在多线程环境下,可以简单使用AtomicXXX 使代码变得线程安全。

转载请注明出处:http://blog.youkuaiyun.com/yaqingwa/article/details/17737771

### 关于线程安全的概念 线程安全是指当多个线程访问某个类的时候,不管运行环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步措施都可以得到正确的结果。如果一个资源或操作在同一时刻只允许被单一线程所使用,则该资源或操作是线程不安全的;反之则是线程安全。 ### Java中实现线程安全的方式 #### synchronized关键字 `synchronized` 是一种内置锁机制,在Java中用于控制多线程对共享数据的操作。它能够确保同一时间只有一个线程可以进入临界区,从而防止其他线程干扰当前正在执行的任务[^1]。 ```java public class Counter { private int count; public synchronized void increment() { this.count++; } } ``` #### volatile关键字 对于某些变量来说,可能只需要保证其可见性而无需考虑原子性问题,这时就可以利用 `volatile` 来修饰这样的字段。标记为 `volatile` 的成员变量表示这个成员将会被多个线程异步地改变。 ```java private volatile boolean flag = false; ``` #### 原子类 为了更高效地解决一些特定类型的并发问题,JDK 提供了一系列原子类(如 AtomicInteger, AtomicLong 等),它们内部实现了基于 CAS (Compare And Swap) 操作的安全更新方法,可以在无锁的情况下完成高效的数值运算。 ```java import java.util.concurrent.atomic.AtomicInteger; AtomicInteger atomicInt = new AtomicInteger(0); atomicInt.incrementAndGet(); ``` #### 锁机制 除了基本的关键字外,还可以借助显式的 Lock 接口及其子接口 ReentrantLock 来构建更加灵活复杂的锁定策略。这使得开发者可以根据实际情况设计出满足需求的自定义同步方案。 ```java ReentrantLock lock = new ReentrantLock(); lock.lock(); try { // critical section here } finally { lock.unlock(); } ``` #### 并发集合 针对常见的容器类型,ConcurrentHashMap 和 CopyOnWriteArrayList 这样的并发版本提供了更好的性能以及更高的安全性,适用于高并发环境下频繁读写的场景。 ```java Map<String, String> map = new ConcurrentHashMap<>(); map.put("key", "value"); String value = map.get("key"); ``` ### 编写线程安全的最佳实践 - **最小化作用域**:尽可能缩小加锁范围,减少持有锁的时间长度。 - **不可变对象**:创建不可变的对象结构有助于简化线程间的数据交换过程。 - **避免忙等待**:应尽量避免长时间占用 CPU 资源而不做有用的工作。 - **优先选用高级别的抽象工具**:比如 ExecutorService 或者 ForkJoinPool 可以让应用程序更容易管理任务队列和工作窃取等问题。 - **测试与调试**:务必进行全面的压力测试来验证系统的稳定性和可靠性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值