一、无锁解决线程安全问题
1.引例
原子整数使用:
AtomicInteger balance = new AtomicInteger();
package panw.unlocked;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;
public class AcountTest {
public static void main(String[] args) {
UnsafeAccount unsafeAccount = new UnsafeAccount(10000);
Account.demo(unsafeAccount);
// SafeAccount safeAccount = new SafeAccount(10000);
// Account.demo(safeAccount);
}
}
interface Account{
Integer getBalance();
void withDraw(Integer amount);
static void demo(Account account){
ArrayList<Thread> list = new ArrayList<>();
long start = System.nanoTime();
for (int i = 0; i < 1000; i++) {
list.add(new Thread(()->{
account.withDraw(10);
}));
}
list.forEach(Thread::start);
list.forEach((t)->{
try {
t.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
long end = System.nanoTime();
System.out.println(account.getBalance() + " cost: " + (end - start) / 1000_000 + " ms");
}
}
class UnsafeAccount implements Account{
private Integer balance;
public UnsafeAccount(Integer balance) {
this.balance = balance;
}
@Override
public Integer getBalance() {
return this.balance;
}
@Override
public void withDraw(Integer amount) {
synchronized (this){
this.balance -= amount;
}
}
}
class SafeAccount implements Account{
private AtomicInteger balance;
public SafeAccount(int balance) {
this.balance = new AtomicInteger(balance);
}
@Override
public Integer getBalance() {
return balance.get();
}
@Override
public void withDraw(Integer amount) {
// balance.getAndAdd(-amount);
while (true){
int prev = balance.get();
int next = prev - amount;
if (balance.compareAndSet(prev,next)){
break;
}
}
}
}
以上的代码UnsafeAccount只有加上了synchronized
代码块才能保证操作的原子性,但是这样相比于CAS(compare and swap)效率查了很多,那么CAS到底是什么呢?下面我们来看看
二、CAS与volatile
CAS是原子操作的一种,可用于在多线程编程中实现不被打断的数据交换操作,从而避免多线程同时改写某一数据时由于执行顺序不确定性以及中断的不可预知性产生的数据不一致问题。
1.三个操作参数
- 如果相等,则说明没有其它线程来修改过这个值,所以把内存V的的值更新成B(swap)
如果不相等,说明V上的值被修改过了,不更新,而是返回当前V的值,再重新执行一次任务再继续这个过程。
3.volatile
volatile 仅仅保证了共享变量的可见性,让其它线程能够看到新值,但不能解决指令交错问题(不能保证原子性)
CAS 必须借助 volatile 才能读取到共享变量的新值来实现【比较并交换】的效果4.CAS特点
结合 CAS 和 volatile 可以实现无锁并发,适用于线程数少、多核 CPU 的场景下。
CAS 是基于乐观锁的思想:乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我再重试。
- synchronized 是基于悲观锁的思想:悲观的估计,得防着其它线程来修改共享变量,我上了锁你们都别想改,我改完了解开锁,你们才有机会。
CAS 体现的是无锁并发、无阻塞并发
AtomicBoolean
- AtomicInteger
- AtomicLong
AtomicInteger
package panw.JUC;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerTest {
public static void main(String[] args) {
AtomicInteger ai = new AtomicInteger(1);
ai.getAndIncrement();
ai.incrementAndGet();
ai.getAndDecrement();
ai.decrementAndGet();
ai.getAndAdd(5);
ai.getAndAdd(-5);
ai.getAndUpdate((a)->a*10);
for (int i = 0; i < 3; i++) {
int andAccumulate = ai.getAndAccumulate(5, (a, b) -> a * b);
System.out.println(andAccumulate);
}
}
}
四、原子引用
AtomicReference和AtomicInteger非常类似,不同之处就在于AtomicInteger是对整数的封装,而AtomicReference则对应普通的对象引用。也就是它可以保证你在修改对象引用时的线程安全性。
package panw.JUC;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicReference;
public class AtomicReferenceTest {
public static void main(String[] args) {
SafeAccount safeAccount = new SafeAccount(new BigDecimal(10000));
Account.demo(safeAccount);
}
}
interface Account {
BigDecimal getBalance();
void withDraw(BigDecimal amount);
static void demo(Account account) {
ArrayList<Thread> list = new ArrayList<>();
long start = System.nanoTime();
for (int i = 0; i < 1000; i++) {
list.add(new Thread(() -> {
account.withDraw(BigDecimal.TEN);
}));
}
list.forEach(Thread::start);
list.forEach((t) -> {
try {
t.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
long end = System.nanoTime();
System.out.println(account.getBalance() + " cost: " + (end - start) / 1000_000 + " ms");
}
}
class SafeAccount implements Account {
private AtomicReference<BigDecimal> balance;
public SafeAccount(BigDecimal balance) {
this.balance = new AtomicReference<BigDecimal>(balance);
}
@Override
public BigDecimal getBalance() {
return balance.get();
}
@Override
public void withDraw(BigDecimal amount) {
// balance.getAndAdd(-amount);
while (true){
BigDecimal prev = balance.get();
BigDecimal next = prev.subtract(amount);
if (balance.compareAndSet(prev,next)){
break;
}
}
}
}
五、ABA问题
1.示例
package panw.JUC;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.atomic.AtomicReference;
@Slf4j
public class ABATest {
static AtomicReference<String> atomicReference = new AtomicReference<>("A");
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
String s = atomicReference.get();
other();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.debug("A->C "+ atomicReference.compareAndSet("A","C"));
}, "t1");
t1.start();
t1.join();
log.debug("最终:"+atomicReference.get());
}
static void other() {
new Thread(()->{
log.debug("A->B "+ atomicReference.compareAndSet("A","B"));
}).start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(()->{
log.debug("B->A "+ atomicReference.compareAndSet("B","A"));
}).start();
}
}
主线程仅能判断出共享变量的值与初值 A 是否相同,不能感知到这种从 A 改为 B 又 改回 A 的情况,如果主线程希望:
只要有其它线程【动过了】共享变量,那么自己的 cas 就算失败,这时,仅比较值是不够的,需要再加一个版本号,JUC提供了以下的实现
2.AtomicStampedReference
package panw.JUC;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
@Slf4j
public class AtomicStampedReferenceTest {
private static AtomicStampedReference<String> str = new AtomicStampedReference<>("A",0);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
String pre = str.getReference();
int stamp = str.getStamp();
other();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.debug("change A->C stamp " + stamp + str.compareAndSet(pre, "C", stamp, stamp+1));
});
t1.start();
t1.join();
}
static void other() {
new Thread(()->{
int stamp = str.getStamp();
log.debug("A->B stamp " + stamp + str.compareAndSet("A","B",stamp,stamp+1));
}).start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(()->{
int stamp = str.getStamp();
log.debug("B->A stamp " + stamp + str.compareAndSet("B","A",stamp,stamp+1));
}).start();
}
}
3.AtomicMarkableReference
package panw.JUC;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.atomic.AtomicMarkableReference;
@Slf4j
public class AtomicMarkableReferenceTest {
static AtomicMarkableReference<String> str = new AtomicMarkableReference<>("A",false);
public static void main(String[] args) {
new Thread(()->{
String pre = str.getReference();
other();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.debug("A->C " + str.compareAndSet("A","C",false,true));
}).start();
}
static void other() {
new Thread(()->{
log.debug("A->A " + str.compareAndSet("A","A",false,true));
}).start();
}
}
两种区别
- AtomicStampedReference 需要我们传入整型变量作为版本号,来判定是否被更改过
AtomicMarkableReference需要我们传入布尔变量作为标记,来判断是否被更改过
六、原子数组
AtomicIntegerArray
- AtomicLongArray
- AtomicReferenceArray
具体的使用其实和上面相同,只是操作时候带上数据下标即可。
七、原子更新器
- AtomicReferenceFieldUpdater // 域 字段
- AtomicIntegerFieldUpdater
- AtomicLongFieldUpdate
原子更新器用于帮助我们改变某个对象中的某个属性
package panw.JUC;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
public class AtomicReferenceFieldUpdaterTest {
public static void main(String[] args) {
Person person = new Person();
AtomicReferenceFieldUpdater<Person, String> updater =AtomicReferenceFieldUpdater.newUpdater(Person.class,String.class,"name");
System.out.println("更新前"+person);
updater.compareAndSet(person,null,"panw");
System.out.println("更新后"+person);
}
}
class Person{
volatile String name;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
从上面的例子可以看出,原子更新器是通过newUpdater来获取实例的。其中传入了三个参数
- 拥有属性的类的Class
- 属性的Class
- 属性的名称
内部实现类
AtomicReferenceFieldUpdater
为抽象类,该类内部有一个自己的实现类**AtomicReferenceFieldUpdaterImpl**
private static final class AtomicReferenceFieldUpdaterImpl<T,V>
extends AtomicReferenceFieldUpdater<T,V>
newUpdater
方法:
@CallerSensitive
public static <U,W> AtomicReferenceFieldUpdater<U,W> newUpdater(Class<U> tclass,
Class<W> vclass,
String fieldName) {
return new AtomicReferenceFieldUpdaterImpl<U,W>
(tclass, vclass, fieldName, Reflection.getCallerClass());
}
**AtomicReferenceFieldUpdaterImpl**
构造方法:
AtomicReferenceFieldUpdaterImpl(final Class<T> tclass,
final Class<V> vclass,
final String fieldName,
final Class<?> caller) {
final Field field;
final Class<?> fieldClass;
final int modifiers;
try {
field = AccessController.doPrivileged(
new PrivilegedExceptionAction<Field>() {
public Field run() throws NoSuchFieldException {
return tclass.getDeclaredField(fieldName);
}
});
modifiers = field.getModifiers();
sun.reflect.misc.ReflectUtil.ensureMemberAccess(
caller, tclass, null, modifiers);
ClassLoader cl = tclass.getClassLoader();
ClassLoader ccl = caller.getClassLoader();
if ((ccl != null) && (ccl != cl) &&
((cl == null) || !isAncestor(cl, ccl))) {
sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass);
}
fieldClass = field.getType();
} catch (PrivilegedActionException pae) {
throw new RuntimeException(pae.getException());
} 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) &&
tclass.isAssignableFrom(caller) &&
!isSamePackage(tclass, caller))
? caller : tclass;
this.tclass = tclass;
this.vclass = vclass;
this.offset = U.objectFieldOffset(field);
}
八、LongAdder
LongAdder是JDK8新增的原子操作类,它提供了一种新的思路,AtomicLong的性能瓶颈是由于大量线程同时更新一个变量造成的,那么能不能把这个变量拆分出来,变成多个变量,然后让线程去竞争这些变量,最后合并即可?LongAdder的设计精髓就在这里,通过将变量拆分成多个元素,降低该变量的并发度,最后进行合并元素,变相的减少了CAS的失败次数。
九、Unsafe
Unsafe 对象提供了非常底层的,操作内存、线程的方法,Unsafe 对象不能直接调用,只能通过反射获得
package panw.JUC;
import sun.misc.Unsafe;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
public class UnsafeGetTest {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
Class<Unsafe> unsafeClass = Unsafe.class;
Constructor<Unsafe> constructor = unsafeClass.getDeclaredConstructor();
constructor.setAccessible(true);
Unsafe unsafe = constructor.newInstance();
Field field = Teacher.class.getDeclaredField("name");
long offset = unsafe.objectFieldOffset(field);
System.out.println(offset);
Person person = new Person();
unsafe.compareAndSwapObject(person,offset,null,"张三");
System.out.println(person);
}
}
class Teacher{
volatile String name;
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
'}';
}
}