阻塞锁的缺点
- 被阻塞的线程下优先级高
- 拿到锁的线程一直不释放锁怎么办?
- 大量的竞争,消耗CPU,并且带来死锁或者其他的安全问题他
- 太重了,不实用与小的操作
CAS原理
CAS(Compare And Swap),利用了现代处理器都支持的CAS的指令,循环这个指令,直到成功为止
三个运算符:一个内存地址V,一个期望值A,一个修改值B
基本思路
for(;;){
if(V.value == A){
V.value = B
}
}
如果地址V上的值和期望的值A相等,就给地址V赋给新值B,如果不是,不做任何操作。
循环(死循环,自旋)里不断的进行CAS操作
CAS的问题
- ABA问题:
- 若CPU在读内存地址的时候值为A,让后地址的值被改为B,之后又被改为A,CPU在做比较的时候发现地址的值没变A--->B---->A
- 解决办法,引入版本号,记录每次操作后的版本A1--->B2---->A3
- 开销问题
- 只能保证一个共享变量的原子操作
JDK中相关原子类的使用
-
更新基本类型类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
-
更新数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
-
更新引用类型:AtomicReference
- AtomicMarkableReference 带版本号的,返回值为boolean ,有没有动过
- AtomicStampedReference 动过几次
-
原子更新字段类: AtomicReferenceFieldUpdater,AtomicIntegerFieldUpdater,AtomicLongFieldUpdater
具体使用如下
UseAtomicInteger
public class UseAtomicIn {
static AtomicInteger atomicInteger = new AtomicInteger(10);
public static void main(String[] args){
System.out.println(atomicInteger.getAndIncrement());
System.out.println(atomicInteger.incrementAndGet());
System.out.println(atomicInteger.get());
}
}
//输出
//10
//12
//12
UseAtomicArray
public class UseAtomicArr {
static int[] a = new int[]{1,2};
static AtomicIntegerArray integerArray = new AtomicIntegerArray(a);
public static void main(String[] args){
integerArray.getAndSet(0,3);
System.out.println(integerArray.get(0));
System.out.println(a[0]);
}
}
//输出
//3
//1
UseAtomicReference
public class UseAtomicReferenc {
static class User{
private String name;
private Integer age;
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
static AtomicReference<User> userRef = new AtomicReference<User>();
public static void main(String[] args){
User u = new User("sam",22);
userRef.set(u);
User max = new User("max", 27);
userRef.compareAndSet(u,max);
System.out.println(userRef.get().getAge());
System.out.println(userRef.get().getName());
System.out.println(u.getAge());
System.out.println(u.getName());
}
}
//27
//max
//22
//sam
UseAtomicStampedReference
public class UseAtomicStampedReference {
static AtomicStampedReference<String> asr =
new AtomicStampedReference<String>("Sam",0);
public static void main(String[] args) throws InterruptedException {
final int oldStamp = asr.getStamp();
final String oldRef = asr.getReference();
System.out.println(oldRef+"==========="+oldStamp);
Thread rghtThread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" 当前变量值 "+oldRef+" 当前版本戳 "+oldStamp+" -"
+asr.compareAndSet(oldRef,oldRef+"me",oldStamp,oldStamp+1));
}
});
Thread wrongThread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" 当前变量值 "+asr.getReference()+" 当前版本戳 "+asr.getStamp()+" -"
+asr.compareAndSet(asr.getReference(),asr.getReference()+"me",oldStamp,oldStamp+1));
}
});
rghtThread.start();
rghtThread.join();
wrongThread.start();
wrongThread.join();
System.out.println(asr.getReference()+"==========="+asr.getStamp());
}
}
//输出
//Sam===========0
//Thread-0 当前变量值 Sam 当前版本戳 0 -true
//Thread-1 当前变量值 Samme 当前版本戳 1 -false
//Samme===========1
小测试
现在有一个残缺的AtomicInteger类只实现了线程安全的:
get方法和compareAndSet()方法
请在理解了循环CAS后尝试自行实现它的递增方法
public class Test {
static AtomicInteger a = new AtomicInteger(0);
private static void increa(){
for (;;){
int i = a.get();
if(a.compareAndSet(i, ++i))
break;
}
}
public static void main(String[] args){
System.out.println(a.get());
increa();
System.out.println(a.get());
}
}
//输出
//0
//1