无锁
问题提出
一个账户内有10000,1000个线程每个线程减去10,最后正确的结果应该是0。
public interface Account {
Integer getAmount();
void withdraw(Integer amount);
public static void demo(Account account){
ArrayList<Thread> threadList = new ArrayList<>();
long start = System.nanoTime();
for(int i = 0;i < 1000; i++){
Thread t = new Thread(()->{
account.withdraw(10);
});
threadList.add(t);
}
threadList.forEach(Thread ::start); //遍历1000个线程,并调用start方法启动
threadList.forEach(t->{
try {
t.join(); //保证1000个线程在主线程之前执行完毕以确保执行时间的正确
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.nanoTime();
System.out.println(account.getAmount()+"cost:"+TimeUnit.NANOSECONDS.toMillis(end-start)+"ms");
}
}
为什么不安全
class SafeAccount implements Account{
private Integer amount;
public SafeAccount(Integer amount) {
this.amount = amount;
}
@Override
public Integer getAmount() {
return amount;
}
@Override
public void withdraw(Integer amount) {
this.amount -= amount;
}
public static void main(String[] args) {
Account account = new SafeAccount(10000);
Account.demo(account);
}
}
最后结果并不是0,发生错误的原因是指令交错的原因造成的,比如线程一获得amout开始-10操作时另一个线程也开始同样的操作,使得结果发生错误。
解决思路-锁
使用synchronized
关键字保护共享变量amount
,当一个线程完成了-10操作并且释放了锁其他线程才能执行,让线程之间串行运行。这样不会造成指令交错,确保了操作的原子性。
public synchronized void withdraw(Integer amount) {
this.amount -= amount;
}
解决思路-无锁
class UnlockAccount implements Account{
private AtomicInteger balance; //使用原子整数
publicnlockAccount(Integer amount) {
balance = new AtomicInteger(amount);
}
@Override
public Integer getAmount() {
return balance.get();
}
@Override
public void withdraw(Integer amount) {
while (true){
int prev = balance.get(); //获取旧值
int next = except - amount; //操作后的新值
//将旧值prev与此时的最新值比较如果相等则将最新设置为next返回true并结束循环,如果不相等就返回false继续尝试此操作直到成功。
if (balance.compareAndSet(prev,next))
break;
}
}
public static void main(String[] args) {
Account account = new unlockAccount(10000);
Account.demo(account);
}
}
无锁方式可以允许指令的交错,但是当发生指令交错后会让这次操作失效,继续尝试直到成功。
CAS与volatile
CAS
使用原子整数在不加锁的情况下可以实现对共享变量安全的操作。
以下是AtomicInteger
的部分源码
public class AtomicInteger extends Number implements java.io.Serializable {
public AtomicInteger(int initialValue) {
value = initialValue; //给value赋初始值
}
//获取Unsafe对象,提供了非常底层的,操作线程,内存的方法。
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
//获得value属性在AtomicInteger中的偏移量,用来定位value的地址,直接操作它。
valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
//将value设置为volatile确保线程间的可见性
private volatile int value;
public final boolean compareAndSet(int expect, int update) {
/*通过vaueOffet定位到value并且比较和旧值expect是否相等。
如果相等将value值设置为update
如果不等表示这次操作失败,返回false。
*/
return unsafe.compareAndSwapInt(this, valueOffset, expect, update); //这个操作是原子性的,不会被其他线程干扰
}
}
其中关键的就是compareAndSet
,它的简称就是CAS,它属于原子操作。CAS的底层是lock cmpxchg
指令(X86架构),在单核和多核CPU下都能保证【比较-交换】的原子性。
volatile
获取共享变量时为了保证变量的可见性需要使用volatile
修饰,保证一个线程对变量的修改另一个线程可见。CAS操作必须借助volatile才能读取共享变量的最新值保证【比较-交换】的有效性。
为什么无锁效率高
- 当cas操作不成功时使用while(true)循环不断尝试,该线程一直处于running状态不会停歇。而synchronized会让没获得锁的线程陷入等待,发生上下文切换。
- 无锁情况下要保持线程不断的运行需要多个cpu的支持,如果cpu数小于线程数,虽然不会陷入阻塞,但由于没有分到时间片,仍然会进入Runnable状态,还是会导致上下文切换。所以在多核cpu下才由效果(线程数<cpu数)。
乐观锁和悲观锁
- CAS基于乐观锁的思想:最客观的估计,在当前线程执行时不怕别的线程来修改共享变量,即使修该了也没关系,可以再继续重试
synchronized
基于悲观锁的思想:最悲观的估计,在我执行时就上锁不给其他线程修改共享变量的机会,只有我执行完解锁,其他线程才可以修改共享变量。- CAS体现的是无锁并发,无阻塞并发
- 没有使用
synchronized
,线程不会陷入阻塞,这时效率提升的因素之一。 - 但是如果竞争激烈,重试频繁发生,反而影响效率。
- 没有使用
原子类
1、原子引用类
AtomicReference
AtomicMarkableReference
给共享变量一个标记,记录是否被修改过AtomicStampedReference
给共享变量附加一个版本号,记录被修改过的次数
2、ABA
多个线程将共享变量的值由A–>B,再由B–>A。其他线程在操作时不会知道共享变量是否被修改过。
static AtomicReference<String> ref = new AtomicReference<>("A");
public static void main(String[] args) throws InterruptedException {
String prev = ref.get();
other();
Thread.sleep(2000);
log.debug("change A->B{}",ref.compareAndSet(prev,"B"));
}
public static void other(){
new Thread(()->{
log.debug("change A->B {}",ref.compareAndSet(ref.get(),"B"));
},"t1").start();
new Thread(()->{
log.debug("change B->A {}",ref.compareAndSet(ref.get(),"A"));
},"t2").start();
}
如果主线程希望只要其他线程修改了共享变量,那么就算自己CAS失败,这样仅比较值是不够的,需要再加一个版本号。
AtomicStampedReference
static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A",0);
public static void main(String[] args) throws InterruptedException {
String prev = ref.getReference();
int stamp = ref.getStamp();
log.debug("start:{},stamp:{}",prev,stamp);
other();
Thread.sleep(2000);
log.debug("change A->B{}",ref.compareAndSet(prev,"B",stamp,ref.getStamp()+1));
}
public static void other(){
new Thread(()->{
log.debug("change A->B {},stamp:{}",ref.compareAndSet(ref.getReference(),"B",ref.getStamp(),ref.getStamp()+1),ref.getStamp());
},"t1").start();
new Thread(()->{
log.debug("change B->A {},stamp:{}",ref.compareAndSet(ref.getReference(),"A",ref.getStamp(),ref.getStamp()+1),ref.getStamp());
},"t2").start();
}
如果只关心是否被修改过,并不关系修改过几次,可以使用
AtomicMarkableReference
static AtomicMarkableReference<String> ref = new AtomicMarkableReference<>("A",false); //初始值设为false,即没有被修改过。
public static void main(String[] args) throws InterruptedException {
String prev = ref.getReference();
other();
Thread.sleep(2000);
log.debug("change A->B {}",ref.compareAndSet(prev,"B",false,true));
}
public static void other(){
new Thread(()->{
log.debug("change A->B {}",ref.compareAndSet(ref.getReference(),"B",false,true));
},"t1").start();
new Thread(()->{
log.debug("change B->A {}",ref.compareAndSet(ref.getReference(),"A",true,true));
},"t2").start();
}
3、原子数组
/*10个线程对长度为10的数组的每个元素分别进行1000次累加操作,线程安全的情况下数组每个元素值是1000
参数1,提供数组。原子数组或普通数组
参数2,获得数组长度的方法
参数3,自增方法,回传array,index
参数4,打印数组的方法
*/
public class SafeArray {
public static <T> void demo(
Supplier<T> arraySupplier,
Function<T, Integer> lenFun,
BiConsumer<T, Integer> putConsumer,
Consumer<T> printConsumer
) {
ArrayList<Thread> threadList = new ArrayList<>();
T array = arraySupplier.get();
int len = lenFun.apply(array);
for(int i = 0; i < len; i++){
threadList.add(new Thread(()->{
for(int j = 0; j < 1000; j++) {
putConsumer.accept(array,j%len);
}
}));
}
threadList.forEach(Thread::start);//启动所有的线程
threadList.forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
printConsumer.accept(array);
}
}
public static void main(String[] args) {
SafeArray.demo(
()-> new AtomicIntegerArray(10),
(array)->array.length(),
(array,index)->array.getAndIncrement(index), //累加操作
(array) -> System.out.println(array)
);
}
Unsafe
Unsafe对象提供了非常底层的,操作内存,线程的方法,不能直接调用,只能通过反射获取。
自定义一个原子类:
class AtomicData{
//使用volatile来保证可见性
private volatile Integer data;
private static Unsafe unsafe;
//data属性的偏移量,Unsafe可以通过偏移地址直接操作data
final static long OFFSET;
public AtomicData(Integer data) {
this.data = data;
}
static {
unsafe = UnsafeAccessor.getUnsafe();
try {
OFFSET = unsafe.objectFieldOffset(AtomicData.class.getDeclaredField("data"));
} catch (NoSuchFieldException e) {
throw new Error(e);
}
}
public int getAndUpdate(ResultOperator resultOperator){
int prev,update;
do{
prev = get();
update = resultOperator.applyAsInt(prev);
System.out.println(update);
}while (!(unsafe.compareAndSwapInt(this,OFFSET,prev,update)));
return prev;
}
public final int get(){
return data;
}
}