Java 7之多线程第5篇 - 原子类

本文深入解析Java中的原子类,涵盖基本类型、数组类型、引用类型及对象属性修改类型。通过实例展示如何确保线程安全下的数据操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一道面试题:

假如有一个文件可以允许多个人同时编辑,如果一个人在编辑完成后进行提交时,另外一个人已经对这个文档进行了修改,这时候就需要提醒下要提交的人,“文档已经修改,是否查看?”

最为简单的办法就是:


其实原子类大体也是用到这样的思想。

Java.util.concurrent包里包含的主要就是一些与并发实现相关的类,首先来看一下最为基础的原子类(java.util.concurrent.atomic)和和线程锁(java.utl.concurrent.locks)。这一篇将着重讲解一下原子类。

根据修改的数据类型,可以将java.util.concurrent.atomic包中的原子操作类可以分为4类。

1. 基本类型: AtomicInteger, AtomicLong, AtomicBoolean ;
2. 数组类型: AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray ;
3. 引用类型: AtomicReference, AtomicStampedRerence, AtomicMarkableReference ;
4. 对象的属性修改类型: AtomicIntegerFieldUpdater, AtomicLongFieldUpdater, AtomicReferenceFieldUpdater 。

这些类存在的目的是对相应的数据进行原子操作。所谓原子操作,是指操作过程不会被中断,保证数据操作是以原子方式进行的。


1、基本类型


拿Atomic举例来说,调用类中相关的方法可以肯定,能够返回一个唯一的数值。在这个类中有一个关键的变量定义如下:

  1. private volatile int value;  
private volatile int value;
这个私有的变量被volatile修饰,那么volatile关键字的作用是什么呢?
(1)可以使value在被某个线程修改后及时刷回到主内存中

(2)线程在获取value值时,这个值必须要从主内存中取出

可以看到,其实被volatile修饰的原始类型类似于一个小小的同步块,但是与同步块比起来,由于没有线程锁这样一个概念,所以在某些情况下还是得不到保证。例如要获取一个唯一增长的序列时,还是会产生问题。

举个例子:

  1. public class UnsafeSequence {  
  2.     private volatile int value;  
  3.     public  int get() {  
  4.          return value++;  
  5.     }  
  6. }  
public class UnsafeSequence {
    private volatile int value;
	public  int get() {
         return value++;
	}
}
在执行的时候,两个线程在调用get()方法时很可能会得到相同的值。如何能保证一个唯一且增长的序列时,可能会给get()方法上锁(加synchronized关键字或同步块),这时候就不需要volatile关键字了,因为如果给get()方法上锁,那么同步块本身会刷内存的。怎么利用volatile来实现呢?

下面继续来分析源码,发现这个类中提供了一些设置value值的方法,其中就包括对value值进行加1的操作,如下:

  1. public final int getAndIncrement() {  
  2.         for (;;) {  
  3.             int current = get(); // 获取value当前值  
  4.             int next = current + 1;  
  5.             if (compareAndSet(current, next))  
  6.                 return current;  
  7.         }  
  8.     }  
public final int getAndIncrement() {
        for (;;) {
            int current = get(); // 获取value当前值
            int next = current + 1;
            if (compareAndSet(current, next))
                return current;
        }
    }
还有comareAndSet()方法的源代码如下:

  1. /** 
  2.      * Atomically sets the value to the given updated value 
  3.      * if the current value  == the expected value. 
  4.      */  
  5.     public final boolean compareAndSet(int expect, int update) {  
  6.         return unsafe.compareAndSwapInt(this, valueOffset, expect, update);  
  7.     }  
/**
     * Atomically sets the value to the given updated value
     * if the current value  == the expected value.
     */
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

当value值等于expect的时候,修改value的值为update,也就是给value值加1。可能有些人会问,为什么要比较value和expect的值呢?这就是设计的巧妙之处。

试想一下,如果value值为2的时候,被两个线程调用get()方法得到值后,其中线程1为value值加1后,调用compareAndSet()方法将value值修改为3并被刷回到内存中。线程2也要调用compareAndSet()方法时,这时的expect=2就和value=3的值不符合了,所以不会返回3,避免错误。

此时如何处理呢?在for(;;)死循环中重新作处理后,就可以得到正确的值4并且返回了。

下面我们来利用这个类得到 一个唯一且完全增长的序列,如下:

  1. public class SafeSequence {  
  2.     private final AtomicInteger value=new AtomicInteger(0);  
  3.     public  int getSequence() {  
  4.          return value.getAndIncrement();  
  5.     }  
  6. }  
public class SafeSequence {
    private final AtomicInteger value=new AtomicInteger(0);
	public  int getSequence() {
         return value.getAndIncrement();
	}
}

AtomicLong类对long数据类型进行原子操作。在32位操作系统中,64位的long 和 double 变量由于会被JVM当作两个分离的32位来进行操作,所以不具有原子性。而使用AtomicLong能让long的操作保持原子性。除此之外,使用该类的方法,也会方便地得到唯一序列。


2、数组类型


如上是对基本类型进行原子操作,而数组类型是对数组中的元素进行原子操作。只简单的举个例子,给数组中指定索引处的某个元素加1后返回,方法如下:

  1. // Atomically decrements by one the element at index  i.  
  2. public final int decrementAndGet(int i) {  
  3.     return addAndGet(i, -1);            // 对数组索引i处存储的值减去1  
  4. }  
  5.   
  6. // Atomically adds the given value to the element at index  i.  
  7. public final int addAndGet(int i, int delta) {  
  8.     long offset = checkedByteOffset(i); // 计算偏移量  
  9.     while (true) {  
  10.         int current = getRaw(offset);   // 获取数组中存储的当前值  
  11.         int next = current + delta;     // 计算更新值  
  12.         if (compareAndSetRaw(offset, current, next)) // 对值进行原子性修改  
  13.             return next;  
  14.     }  
  15. }  
    // Atomically decrements by one the element at index  i.
    public final int decrementAndGet(int i) {
        return addAndGet(i, -1);            // 对数组索引i处存储的值减去1
    }

    // Atomically adds the given value to the element at index  i.
    public final int addAndGet(int i, int delta) {
        long offset = checkedByteOffset(i); // 计算偏移量
        while (true) {
            int current = getRaw(offset);   // 获取数组中存储的当前值
            int next = current + delta;     // 计算更新值
            if (compareAndSetRaw(offset, current, next)) // 对值进行原子性修改
                return next;
        }
    }
由于多个线程操作时,可能会存在安全隐患。例如,数组0索引处存储值为0,第一个线程的任务是对索引0处的值加1,并且等于第二个线程获取到了原始的存储值0,第二个线程任务是对0索引处的值加10,在获取0索引值后马上进行了修改,将值变为10。这时候第二个线程获取到的结果就应该为11,而不是在原存储值的基础上加1。调用如上的方法可以避免多线程下的错误。

3、引用类型


AtomicReference是作用是对"对象"进行原子操作。测试用到方法的源代码如下:

  1. // Atomically sets to the given value and returns the old value.  
  2. public final V getAndSet(V newValue) {  
  3.     while (true) {  
  4.         V x = get();  
  5.         if (compareAndSet(x, newValue))  
  6.             return x;  
  7.     }  
  8. }  
    // Atomically sets to the given value and returns the old value.
    public final V getAndSet(V newValue) {
        while (true) {
            V x = get();
            if (compareAndSet(x, newValue))
                return x;
        }
    }
编写例子进行测试,如下:

  1. public final static AtomicReference<String> ATOMIC_REFERENCE = new AtomicReference<String>("abc");    
  2.         
  3.     public static void main(String []args) {    
  4.         for(int i = 0 ; i < 100 ; i++) {    
  5.             final int num = i;    
  6.             new Thread() {    
  7.                 public void run() {    
  8.                     try {    
  9.                         Thread.sleep(Math.abs((int)(Math.random() * 100)));    
  10.                     } catch (InterruptedException e) {    
  11.                         e.printStackTrace();    
  12.                     }    
  13.                     if("abc"==ATOMIC_REFERENCE.getAndSet(new String("abc"))) {    
  14.                         System.out.println("我是线程:" + num + ",我获得了锁进行了对象修改!");    
  15.                     }    
  16.                 }    
  17.             }.start();    
  18.         }    
  19.     }    
public final static AtomicReference<String> ATOMIC_REFERENCE = new AtomicReference<String>("abc");  
      
    public static void main(String []args) {  
        for(int i = 0 ; i < 100 ; i++) {  
            final int num = i;  
            new Thread() {  
                public void run() {  
                    try {  
                        Thread.sleep(Math.abs((int)(Math.random() * 100)));  
                    } catch (InterruptedException e) {  
                        e.printStackTrace();  
                    }  
                    if("abc"==ATOMIC_REFERENCE.getAndSet(new String("abc"))) {  
                        System.out.println("我是线程:" + num + ",我获得了锁进行了对象修改!");  
                    }  
                }  
            }.start();  
        }  
    }  
只有一个线程得到了运行。

进一步扩展如上问题。试想,如果还有另外其他的线程在如上的线程运行完之前,又将new String("abc")修改为"abc",那么如上的线程又会继续修改,如下:

  1. public class AtomicReferenceABATest {  
  2.       
  3.     public final static AtomicReference <String> ATOMIC_REFERENCE = new AtomicReference<String>("abc");  
  4.   
  5.     public static void main(String []args) {  
  6.         for(int i = 0 ; i < 100 ; i++) {  
  7.             final int num = i;  
  8.             new Thread() {  
  9.                 public void run() {  
  10.                     try {  
  11.                         Thread.sleep(Math.abs((int)(Math.random() * 100)));  
  12.                     } catch (InterruptedException e) {  
  13.                         e.printStackTrace();  
  14.                     }  
  15.                     if(ATOMIC_REFERENCE.compareAndSet("abc" , "abc2")) {  
  16.                         System.out.println("我是线程:" + num + ",我获得了锁进行了对象修改!");  
  17.                     }  
  18.                 }  
  19.             }.start();  
  20.         }  
  21.         new Thread() {  
  22.             public void run() {  
  23.                 while(!ATOMIC_REFERENCE.compareAndSet("abc2""abc"));  
  24.                 System.out.println("已经改为原始值!");  
  25.             }  
  26.         }.start();  
  27.     }  
  28. }  
public class AtomicReferenceABATest {
	
	public final static AtomicReference <String> ATOMIC_REFERENCE = new AtomicReference<String>("abc");

	public static void main(String []args) {
		for(int i = 0 ; i < 100 ; i++) {
			final int num = i;
			new Thread() {
				public void run() {
					try {
						Thread.sleep(Math.abs((int)(Math.random() * 100)));
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					if(ATOMIC_REFERENCE.compareAndSet("abc" , "abc2")) {
						System.out.println("我是线程:" + num + ",我获得了锁进行了对象修改!");
					}
				}
			}.start();
		}
		new Thread() {
			public void run() {
				while(!ATOMIC_REFERENCE.compareAndSet("abc2", "abc"));
				System.out.println("已经改为原始值!");
			}
		}.start();
	}
}
运行后,发现进行了几次修改。如果我们只想进行一次修改,这时候该怎么办?AtomicStampedReference解决这个问题:

  1. public class AtomicStampedReferenceTest {  
  2.       
  3.     public final static AtomicStampedReference <String> ATOMIC_REFERENCE = new AtomicStampedReference<String>("abc" , 0);  
  4.       
  5.     public static void main(String []args) {  
  6.         for(int i = 0 ; i < 100 ; i++) {  
  7.             final int num = i;  
  8.             final int stamp = ATOMIC_REFERENCE.getStamp();  
  9.             new Thread() {  
  10.                 public void run() {  
  11.                     try {  
  12.                         Thread.sleep(Math.abs((int)(Math.random() * 100)));  
  13.                     } catch (InterruptedException e) {  
  14.                         e.printStackTrace();  
  15.                     }  
  16.                     if(ATOMIC_REFERENCE.compareAndSet("abc" , "abc2" , stamp , stamp + 1)) {  
  17.                         System.out.println("我是线程:" + num + ",我获得了锁进行了对象修改!");  
  18.                     }  
  19.                 }  
  20.             }.start();  
  21.         }  
  22.         new Thread() {  
  23.             public void run() {  
  24.                 int stamp = ATOMIC_REFERENCE.getStamp();  
  25.                 while(!ATOMIC_REFERENCE.compareAndSet("abc2""abc" , stamp , stamp + 1));  
  26.                 System.out.println("已经改回为原始值!");  
  27.             }  
  28.         }.start();  
  29.     }  
  30. }  
public class AtomicStampedReferenceTest {
	
	public final static AtomicStampedReference <String> ATOMIC_REFERENCE = new AtomicStampedReference<String>("abc" , 0);
	
	public static void main(String []args) {
		for(int i = 0 ; i < 100 ; i++) {
			final int num = i;
			final int stamp = ATOMIC_REFERENCE.getStamp();
			new Thread() {
				public void run() {
					try {
						Thread.sleep(Math.abs((int)(Math.random() * 100)));
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					if(ATOMIC_REFERENCE.compareAndSet("abc" , "abc2" , stamp , stamp + 1)) {
						System.out.println("我是线程:" + num + ",我获得了锁进行了对象修改!");
					}
				}
			}.start();
		}
		new Thread() {
			public void run() {
				int stamp = ATOMIC_REFERENCE.getStamp();
				while(!ATOMIC_REFERENCE.compareAndSet("abc2", "abc" , stamp , stamp + 1));
				System.out.println("已经改回为原始值!");
			}
		}.start();
	}
}
可以看到,线程只进行了一次修改。


4、对象的属性修改类型


AtomicIntegerFieldUpdater可以对指定"类的 'volatile int'类型的成员"进行原子更新。它是基于反射原理实现的。API的解释如下:

A reflection-based utility that enables atomic updates to  designated volatile int  fields of designated classes.
This class is designed for use in atomic data structures in which several fields of the same node are independently subject to atomic
updates.

也就是保证volatile类型修改的原子性。测试程序如下:

  1. public class LongFieldTest {  
  2.       
  3.     public static void main(String[] args) {  
  4.   
  5.         // 获取Person的class对象  
  6.         Class cls = Person.class;   
  7.         // 新建AtomicLongFieldUpdater对象,传递参数是“class对象”和“long类型在类中对应的名称”  
  8.         AtomicLongFieldUpdater mAtoLong = AtomicLongFieldUpdater.newUpdater(cls, "id");  
  9.         Person person = new Person(12345678L);  
  10.   
  11.         // 比较person的"id"属性,如果id的值为12345678L,则设置为1000。  
  12.         mAtoLong.compareAndSet(person, 12345678L, 1000);  
  13.         System.out.println("id="+person.getId());  
  14.     }  
  15. }  
  16.   
  17. class Person {  
  18.     volatile long id;  
  19.     public Person(long id) {  
  20.         this.id = id;  
  21.     }  
  22.     public void setId(long id) {  
  23.         this.id = id;  
  24.     }  
  25.     public long getId() {  
  26.         return id;  
  27.     }  
  28. }  
public class LongFieldTest {
    
    public static void main(String[] args) {

        // 获取Person的class对象
        Class cls = Person.class; 
        // 新建AtomicLongFieldUpdater对象,传递参数是“class对象”和“long类型在类中对应的名称”
        AtomicLongFieldUpdater mAtoLong = AtomicLongFieldUpdater.newUpdater(cls, "id");
        Person person = new Person(12345678L);

        // 比较person的"id"属性,如果id的值为12345678L,则设置为1000。
        mAtoLong.compareAndSet(person, 12345678L, 1000);
        System.out.println("id="+person.getId());
    }
}

class Person {
    volatile long id;
    public Person(long id) {
        this.id = id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public long getId() {
        return id;
    }
}
运行后的结果如下:

id=1000




转载自http://blog.youkuaiyun.com/mazhimazh/






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值