您的位置:首页 > 编程语言 > Java开发

JAVA多线程之——并发包JUC——Atomic

2017-03-26 23:02 363 查看
前面学习了基础的多线程知识。今天开始学习JAVA的并发包java.util.concurrent。java并发包包括 java.util.concurrent、java.util.concurrent.atomic、java.util.concurrent.locks包。今天开始学习atomic包下的内容’

概念

java从jdk1.5开始引入了并发包。其中java.util.concurrent.atomic包,方便在无锁的情况下,进行原子操作。原子变量的底层使用的是CPU的原子指令,但是不同的CPU有不同的架构,指令也就不同。所以也有可能需要提供某种内部的锁机制。所以不能保证线程绝对不会堵塞。

Atomic分类

atomic包下面总共有12个类。根据起作用可以分为四类

1.原子更新基本类型

AtomicBoolean 原子更新布尔类型

AtomicInteger 原子更新整型

AtomincLong 原子更新长整型

原子更新整型的常用方法:

int get() 获取当前值

void set(int newValue) 设置为给定值。

int getAndAdd(int delta) 以原子方式将给定值与当前值相加。

int decrementAndGet() 以原子方式将当前值减 1。

int incrementAndGet() 以原子方式将当前值加 1。

void lazySet(int newValue) 最后设置为给定值。

方法可以通过API查找。我们主要是了解,为什么原子类型的操作就是线程安全的?底层又是如何进行原子操作的呢?

对JVM有过一定了解的应该都知道CAS操作。CAS操作有3个操作数,内存值M,预期值E,新值U,如果M==E,则将内存值修改为B,否则啥都不做。就是当且仅当内存值与当前值一致,才进行值的更新。否则不更新。这样就保证了atomic包下面的操作的原子行。那它是如何实现的呢?看一看源码:

public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;

// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;

static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}

private volatile int value;

/**
* Creates a new AtomicInteger with the given initial value.
*
* @param initialValue the initial value
*/
public AtomicInteger(int initialValue) {
value = initialValue;
}
}


这是AtomicInteger中的一段源码。我们可以看到其中有一个Unsafe类,一个volatile 修饰的value. 这个value就是先保证了AtomicInteger操作的可见性。然后Unsafe保证对这个value值的操作都是CAS操作。这样就保证了其原子性。Unsafe源码的一部分:

/**
* 比较obj的offset处内存位置中的值和期望的值,如果相同则更新。此更新是不可中断的。
*
* @param obj 需要更新的对象
* @param offset obj中整型field的偏移量
* @param expect 希望field中存在的值
* @param update 如果期望值expect与field的当前值相同,设置filed的值为这个新值
* @return 如果field的值被更改返回true
*/
public native boolean compareAndSwapInt(Object obj, long offset, int expect, int update);


Unsafe中就是通过这样的方式来实现CAS操作。同理AtomicBoolean 和AtomicLong也是这样的方式。但是java中对long类型和double类型的操作是不具有原子性的。这个在今后学习JVM中会详细学习。还有CAS概念。

2.原子更新数组类型

理解了原子更新基本类型,对于其它类型就比较好理解了。

通过原子的方式更新数组里的某个元素,Atomic包提供了以下三个类:

AtomicIntegerArray 原子更新整型数组

AtomicLongArray 原子更新长整型数组

AtomicReferenceArray 原子更新引用数组

AtomicIntegerArray常用的方法:

int get(int i) 获取位置 i 的当前值。

void set(int i, int newValue) 将位置 i 的元素设置为给定值。

int length() 返回该数组的长度。

int getAndAdd(int i, int delta) 以原子方式将给定值与索引 i 的元素相加。

int getAndDecrement(int i) 以原子方式将索引 i 的元素减 1。

int getAndIncrement(int i) 以原子方式将索引 i 的元素加 1。

AtomicIntegerArray(int length) 创建给定长度的新 AtomicIntegerArray。

AtomicIntegerArray(int[] array) 创建与给定数组具有相同长度的新 AtomicIntegerArray,并从给定数组复制其所有素。

先来看一下AtomicIntegerArray的部分源码:

private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final int base = unsafe.arrayBaseOffset(int[].class);
private static final int shift;
private final int[] array;
public AtomicIntegerArray(int length) {
array = new int[length];
}
public AtomicIntegerArray(int[] array) {
// Visibility guaranteed by final field guarantees
this.ar
4000
ray = array.clone();
}
public final int getAndAdd(int i, int delta) {
long offset = checkedByteOffset(i);
while (true) {
int current = getRaw(offset);
if (compareAndSetRaw(offset, current, current + delta))
return current;
}
}


通过观察源码,其实AtomicIntegerArray类,跟AtomicInteger的原理相似。其自身拥有一个 int[]的数组array。如果我们调用构造器构造的时候没有传入数组,则直接初始化自身数组,否则,对我们的数组进行克隆。也就是说,它操作的都是自身的数组,对我们传入的数组是不会有任何操作。而对数组中的数据的操作也是通过Unsafe类来实现其原子性的操作。

3.原子更新引用类型

原子更新基本类型,每次只能更新一个变量,而当需要更新多个变量,比如一个对象的多个属性时候,我们就必须用到原子更新引用类型类——AtomicReference. 其常用的方法有:

V get() 获取当前值。

void set(V newValue) 设置为给定值。

V getAndSet(V newValue) 以原子方式设置为给定值,并返回旧值。

boolean compareAndSet(V expect, V update) 如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。

void lazySet(V newValue) 最终设置为给定值。

先看一个示例:

public class AtomicTest3 {

public static void main(String[] args) {
final AtomicReference<Student>  reference  = new AtomicReference<Student>();
final Student s1 = new Student("张三", 18);
Student s2 = new Student("李四", 20);
reference.set(s1);
reference.compareAndSet(s1, s2);
System.out.println(reference.get());

}

static class Student{
private String name;
private int  age;
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}

}

}


运行结果:

Student [name=李四, age=20]


看来是没什么问题。再看一个示例:

public class AtomicTest {

public static void main(String[] args) {
AtomicReference<Student>  reference  = new AtomicReference<Student>();
final Student s1 = new Student("张三", 18);

Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
s1.setName("李四");
Thread.currentThread().sleep(2000);
s1.setAge(20);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}
});

Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(s1.toString());
}
});

t1.start();
t2.start();

}

static class Student{
private String name;
private int  age;
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}

}

}


运行结果:

Student [name=李四, age=18]


这个结果通过前面学习我们可以知道原因。因为没有同步操作。OK,那可能就会想,我们今天学习了AtomicReferece。刚好是更新一个对象。那我们可以用上。示例:

public class AtomicTest2 {

public static void main(String[] args) {
final AtomicReference<Student>  reference  = new AtomicReference<Student>();
final Student s1 = new Student("张三", 18);
reference.set(s1);
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
reference.get().setName("李四");
Thread.currentThread().sleep(2000);
reference.get().setAge(20);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}
});

Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(reference.get().toString());
}
});

t1.start();
t2.start();

}

static class Student{
private String name;
private int  age;
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}

}

}


结果:

Student [name=李四, age=18]


我们发现,结果并没有对真个对象进行我们预期的“原子操作”。为什么? 看源码:

private static final long serialVersionUID = -1848883965231344442L;

private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;

static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicReference.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}

private volatile V value;


我们看到 有一个变量 V value。这意味着,我们所有的原子操作都是在操作这整个对象。如果对这个对象单独的属性进行操作,那必然会导致这个对象的原子性不一致。同理,如果在AtomicInteger类,加入我们连续的多次调用自增1的方法,那也是不能保证原子性的。

4.原子更新字段

如果我们只需要某个类里的某个字段,那么就需要使用原子更新字段类,Atomic包提供了以下三个类:

AtomicIntegerFieldUpdater 原子更新整型的字段的更新器

AtomicLongFieldUpdater 原子更新长整型的字段更新器

AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更数据和数据的版本号,可以解决使用CAS进行原子更新时,可能出现的ABA问题。

原子更新字段类都是抽象类,每次使用都时候必须使用静态方法newUpdater创建一个更新器。原子更新类的字段的必须使用public volatile修饰符。AtomicIntegerFieldUpdater的示例:

public static void main(String[] args) {
Student s1 = new Student("张三", 18);
AtomicReferenceFieldUpdater  updater = AtomicReferenceFieldUpdater.newUpdater(Student.class, Integer.class, "age");
System.out.println(updater.get(s1));
updater.set(s1, 20);
System.out.println(updater.get(s1));
System.out.println(s1);

}

static class Stud
f867
ent{
private String name;
volatile Integer  age;
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
}

}


运行结果:

18
20
Student [name=张三, age=20]


这里需要注意的是

1.字段必须用volatile修饰 2.字段不能修饰为private。源码:

AtomicReferenceFieldUpdaterImpl(Class<T> tclass,
Class<V> vclass,
String fieldName,
Class<?> caller) {
Field field = null;
Class fieldClass = null;
int modifiers = 0;
try {
field = tclass.getDeclaredField(fieldName);
modifiers = field.getModifiers();
sun.reflect.misc.ReflectUtil.ensureMemberAccess(
caller, tclass, null, modifiers);
sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass);
fieldClass = field.getType();
} catch (Exception ex) {
throw new RuntimeException(ex);
}

if (vclass != fieldClass)
throw new ClassCastException();
if (vclass.isPrimitive())
throw new IllegalArgumentException("Must be reference type");

if (!Modifier.isVolatile(modifiers))
throw new IllegalArgumentException("Must be volatile type");

this.cclass = (Modifier.isProtected(modifiers) &&
caller != tclass) ? caller : null;
this.tclass = tclass;
if (vclass == Object.class)
this.vclass = null;
else
this.vclass = vclass;
offset = unsafe.objectFieldOffset(field);
}

void targetCheck(T obj) {
if (!tclass.isInstance(obj))
throw new ClassCastException();
if (cclass != null)
ensureProtectedAccess(obj);
}

void updateCheck(T obj, V update) {
if (!tclass.isInstance(obj) ||
(update != null && vclass != null && !vclass.isInstance(update)))
throw new ClassCastException();
if (cclass != null)
ensureProtectedAccess(obj);
}

public boolean compareAndSet(T obj, V expect, V update) {
if (obj == null || obj.getClass() != tclass || cclass != null ||
(update != null && vclass != null &&
vclass != update.getClass()))
updateCheck(obj, update);
return unsafe.compareAndSwapObject(obj, offset, expect, update);
}

public boolean weakCompareAndSet(T obj, V expect, V update) {
// same implementation as strong form for now
if (obj == null || obj.getClass() != tclass || cclass != null ||
(update != null && vclass != null &&
vclass != update.getClass()))
updateCheck(obj, update);
return unsafe.compareAndSwapObject(obj, offset, expect, update);
}


其中调用了 sun.reflect.misc.ReflectUtil.ensureMemberAccess(

caller, tclass, null, modifiers);这个方法。一直跟踪这个方法:

public static boolean More ...verifyMemberAccess(Class currentClass,
105                                             // Declaring class of field
106                                             // or method
107                                             Class  memberClass,
108                                             // May be NULL in case of statics
109                                             Object target,
110                                             int    modifiers)
111    {
112        // Verify that currentClass can access a field, method, or
113        // constructor of memberClass, where that member's access bits are
114        // "modifiers".
115
116        boolean gotIsSameClassPackage = false;
117        boolean isSameClassPackage = false;
118
119        if (currentClass == memberClass) {
120            // Always succeeds
121            return true;
122        }
123
124        if (!Modifier.isPublic(getClassAccessFlags(memberClass))) {
125            isSameClassPackage = isSameClassPackage(currentClass, memberClass);
126            gotIsSameClassPackage = true;
127            if (!isSameClassPackage) {
128                return false;
129            }
130        }
131
132        // At this point we know that currentClass can access memberClass.
133
134        if (Modifier.isPublic(modifiers)) {
135            return true;
136        }
137
138        boolean successSoFar = false;
139
140        if (Modifier.isProtected(modifiers)) {
141            // See if currentClass is a subclass of memberClass
142            if (isSubclassOf(currentClass, memberClass)) {
143                successSoFar = true;
144            }
145        }
146
147        if (!successSoFar && !Modifier.isPrivate(modifiers)) {
148            if (!gotIsSameClassPackage) {
149                isSameClassPackage = isSameClassPackage(currentClass,
150                                                        memberClass);
151                gotIsSameClassPackage = true;
152            }
153
154            if (isSameClassPackage) {
155                successSoFar = true;
156            }
157        }
158
159        if (!successSoFar) {
160            return false;
161        }
162
163        if (Modifier.isProtected(modifiers)) {
164            // Additional test for protected members: JLS 6.6.2
165            Class targetClass = (target == null ? memberClass : target.getClass());
166            if (targetClass != currentClass) {
167                if (!gotIsSameClassPackage) {
168                    isSameClassPackage = isSameClassPackage(currentClass, memberClass);
169                    gotIsSameClassPackage = true;
170                }
171                if (!isSameClassPackage) {
172                    if (!isSubclassOf(targetClass, currentClass)) {
173                        return false;
174                    }
175                }
176            }
177        }
178
179        return true;
180    }


在这个方法中如果是私有的就会返回false。于是就会抛出异常。

if (!verifyMemberAccess(currentClass, memberClass, target, modifiers)) {
95             throw new IllegalAccessException("Class " + currentClass.getName() +
96                                              " can not access a member of class " +
97                                              memberClass.getName() +
98                                              " with modifiers \"" +
99                                              Modifier.toString(modifiers) +
100                                             "\"");
101        }


至于为什么不能声明私有,为何要抛出这个异常,目前对于sun.reflect.misc.ReflectUtil还没有深入研究。所以暂时做个笔记。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java 多线程 并发