无锁实现线程安全
public final class Singleton{
public static void main(String[] args) {
Account.demo(new AccountCas(10000));
}
}
class AccountCas implements Account{
private AtomicInteger balance;
public AccountCas(int balance){
this.balance = new AtomicInteger(balance);
}
public Integer getBalance(){
return balance.get();
}
public void withdraw(Integer amount){
while(true){
// 获取余额的最新值
int prev = balance.get();
// 要修改的余额
int next = prev - amount;
// 修改(compareAndSet是原子的)
if(balance.compareAndSet(prev,next)){// 将prev和balance对象中的进行比较,不一样,就修改失败
break;
}
}
}
}
interface Account {
Integer getBalance();
void withdraw(Integer amount);
/**
* 启动 1000 个线程, 每个线程做 -10 元 的操作
* 如果初始余额为 10000 那么正确的结果应当是 0
*/
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()
+ " cost: " + (end-start)/1000_000 + " ms");
}
}
CAS底层依赖Unsafe类
CAS的底层是带lock的cmpxchg指令(X86架构),在单核和多核下都能保证[比较-交换]的原子性。
在多核状态下,某个核执行到带lock的指令时,CPU会让总线锁住,当这个核把此指令执行完毕,再开启总线。
这个过程中不会被线程的调度机制打断,保证了多个线程对内存操作的准确性,是原子的。
CAS操作需要volatile的支持
每次CAS时,需要获取value的最新值,和prev比较,二者一样才能修改成功
public class AtomicInteger extends Number implements Serializable {
private static final long serialVersionUID = 6214790243416807050L;
private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
private static final long VALUE = U.objectFieldOffset(AtomicInteger.class, "value");
private volatile int value; // 保证该变量的可见性
cas的效率比synchronized高
无锁情况下,即使交换失败,线程也在高速运行
synchronized会让没获得锁的线程阻塞,发生上下文切换
结合CAS操作和volatile,就可以实现无锁并发
适用线程数较少,多核CPU的情况
CAS是基于乐观锁的思想,不怕别的线程来修改共享变量,其他线程改了,就重试
CAS体现的是无锁并发,无阻塞并发
一些使用了CAS方式实现的工具类
原子整数
// AtomicInteger
// AtomicBoolean
// AtomicLong
相关方法是原子的,线程安全,都是使用的CAS
updateAndGet
getAndAdd
incrementAndGet
// 等
原子引用
保护多线程对一个对象引用进行修改时的线程安全问题
// AtomicReference<V>
主线程仅能判断出共享变量的值是否与最初值相同,不能判断是否被别的线程修改过
其他线程将变量修改由从A->B->A,主线程进行CAS时是不能感知到的
如果主线程希望:只要有其他线程动过了共享变量,自己的cas就失败
这时,仅比较值是不够的,需要再加一个版本号
// AtomicMarkableReference<V> AtomicStampedReference简化版,只关心是否更改过
// AtomicStampedReference<V> 维护一个版本号,追踪原子引用的变化过程
原子数组
保护数组里的元素
// AtomicIntegerArray
// AtomicLongArray
// AtomicReferenceArray<V>
字段更新器
保护某个对象里的属性,成员变量的线程安全性
被保护的变量需要用volatile修饰
// AtomicIntegerFieldUpdater
// AtomicLongFieldUpdater
// AtomicReferenceFieldUpdater
public final class Singleton{
public static void main(String[] args) {
Student stu = new Student();
AtomicReferenceFieldUpdater<Student, String> updater =
AtomicReferenceFieldUpdater.newUpdater(Student.class, String.class, "name");
System.out.println(updater.compareAndSet(stu,null,"张三"));
System.out.println(stu);
}
}
@ToString
class Student{
volatile String name;
}
原子累加器
LongAdder // 比AtomicLong性能好很多
// 线程有竞争时,设置多个累加单元Cell,不同线程向多个累加单元上进行累加,最后将结果汇总
// 减少了CAS失败重试
// 源码:(add,longAccumulate,sum)方法 -> 详见满一航老师JUC视频
Unsafe类
提供了非常底层的,操作内存,线程的方法
public final class Singleton{
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null); // 因为该成员变量是static的,所以不需要传对象,传null就好
// 获取字段的偏移量
long idOffset = unsafe.objectFieldOffset(Teacher.class.getDeclaredField("id"));
long nameOffset = unsafe.objectFieldOffset(Teacher.class.getDeclaredField("name"));
Teacher t = new Teacher();
unsafe.compareAndSwapInt(t,idOffset,0,1);
unsafe.compareAndSwapObject(t,nameOffset,null,"张三");
System.out.println(t);
}
}
class Teacher{
volatile int id;
volatile String name;
@Override
public String toString() {
return "Teacher{" + "id=" + id + ", name='" + name + '\'' + '}';
}
}