并发学习记录10:共享模型之无锁
作者:互联网
一个小例子引入
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
interface Account {
Integer getBalance();
void withdraw(Integer amount);
static void demo(Account account) {
List<Thread> ts = new ArrayList<>();
long start = System.nanoTime();
for (int i = 0; i < 1000; i++) {
ts.add(new Thread(() -> {
account.withdraw(10);
}));
}
ts.forEach(Thread::start);
ts.forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.nanoTime();
System.out.println("账户余额:" + account.getBalance() + "\ntime cost:" +
(end - start) / 1000 + "ms");
}
}
//线程不安全的版本,多线程执行取款操作会出现问题
class AccountUnsafe implements Account {
private Integer balance;
public AccountUnsafe(Integer balance) {
this.balance = balance;
}
@Override
public Integer getBalance() {
return balance;
}
@Override
public void withdraw(Integer amount) {
balance = balance - amount;
}
}
//用锁实现的线程安全的版本
class AccountSafe implements Account {
private Integer balance;
public AccountSafe(Integer balance) {
this.balance = balance;
}
@Override
public Integer getBalance() {
return balance;
}
@Override
public void withdraw(Integer amount) {
synchronized (this) {
balance = balance - amount;
}
}
}
//用cas实现的线程安全的版本,compare and set
class AccountSafe01 implements Account {
private AtomicInteger balance;
public AccountSafe01(Integer balance) {
this.balance = new AtomicInteger(balance);
}
@Override
public Integer getBalance() {
return balance.get();
}
@Override
public void withdraw(Integer amount) {
while (true) {
int prev = balance.get();
int next = prev - amount;
if (balance.compareAndSet(prev, next)) {
break;
}
}
}
}
public class TestAccount {
public static void main(String[] args) {
// Account.demo(new AccountUnsafe(10000));
// Account.demo(new AccountSafe(10000));
Account.demo(new AccountSafe01(10000));
}
}
以上是一个模拟银行取款的测试,分别用synchronized和cas无锁机制实现了线程安全的操作。
cas和volatile
withdraw的cas方法实现:
public void withdraw(Integer amount) {
while(true) {
// 需要不断尝试,直到成功为止
while (true) {
// 比如拿到了旧值 1000
int prev = balance.get();
// 在这个基础上 1000-10 = 990
int next = prev - amount;
/*
compareAndSet 正是做这个检查,在 set 前,先比较 prev 与当前值
- 不一致了,next 作废,返回 false 表示失败
比如,别的线程已经做了减法,当前值已经被减成了 990
那么本线程的这次 990 就作废了,进入 while 下次循环重试
- 一致,以 next 设置为新值,返回 true 表示成功
*/
if (balance.compareAndSet(prev, next)) {
break;
}
}
}
}
最关键的操作就是compareAndSet操作,简称CAS,CAS底层有机制保证它一定是原子性的
volatile
获取共享变量时,为了保证共享变量的可见性,需要用volatile来修饰
它可以用来修饰成员变量和静态成员变量,可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作volatile变量都是直接操作主存。即另一个线程对volatile变量的修改,对另一个线程可见
volatile仅仅保证了共享变量的可见性,让其他线程能看到最新值,但并不能解决指令交错的问题
CAS必须借助volatile才能读取到共享变量的最新值来实现比较并交换的效果。
为什么无锁效率高?
无锁情况下,即使重试失败,线程也在告诉运行没有停歇,而synchronized会让线程在获得锁的时候发生上下文切换,进入阻塞。
CAS的特点
结合CAS和volatile可以实现无锁并发,适用于线程数少,多核CPU的场景下
CAS是基于乐观锁的思想,最乐观的估计,就算别的线程来修改了我的共享变量,我也可以识别读取并再修改一次
synchronized是基于悲观锁的思想,最悲观的估计,需要防着其他线程来修改共享变量,只有等我这个线程修改完了这个共享变量解锁之后,其他线程才能来修改
CAS体现的是无锁并发,无阻塞并发。但是如果竞争非常激烈,导致修改的重试频繁发生,反而效率会受到影响
标签:10,Account,之无锁,并发,amount,线程,Integer,balance,public 来源: https://www.cnblogs.com/wbstudy/p/16654952.html