文章目录
1 CAS算法
1.1 CAS算法
CAS算法包含三个操作数:内存位置V、预期值A以及更新值B。有且仅当内存位置V的值与预期值A相同时,才将旧值更新为新值B。否则不做任何处理。
CAS算法在Java中的具体实现就是Unsafe类。由于Java方法无法直接访问底层系统,需要通过native方法来访问,基于Unsafe类可以直接操作特定内存的数据。Unsafe内部方法操作可以像C指针一样直接操作内存,因此Java中CAS操作的执行依赖于Unsafe类的方法。Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源。
1.2 CAS存在的问题
- 空自旋问题:CAS多与自旋相结合。自旋是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,如果CAS自旋长时间不成功,会占用大量的CPU资源。
- ABA问题:比如说线程1从内存位置V中取出A,然后线程2也从内存中取出A修改为B,此时又有线程3又将B修改回A。这时候线程1进行CAS操作发现内存中仍然是A,然后执行修改操作成功。解决方案:使用带有戳记(版本号)的原子类。
2 原子类
JUC提供的原子类可以分为五大类:
- 基本类型原子类:AtomicInteger、AtomicBoolean、AtomicLong
- 数组类型原子类:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
- 引用类型原子类:AtomicReference、AtomicStampedReference、AtomicMarkableReference
- 对象属性修改原子类:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
- 累积性原子类:LongAdder、LongAccumulator
2.1 基本类型原子类
public class TestAtomicInteger {
public static final int SIZE = 50;
public static final AtomicInteger NUMBER = new AtomicInteger();
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(SIZE);
for (int i = 1; i <= SIZE; i++) {
new Thread(() -> {
try {
for (int j = 1; j <= 1000; j++) {
NUMBER.getAndIncrement();
}
} finally {
countDownLatch.countDown();
}
}, String.valueOf(i)).start();
}
// 等待上面50个线程全部计算完成后,再去获得最终值
countDownLatch.await();
System.out.println("result: " + NUMBER.get());
}
}
2.2 数组类型原子类
public class TestAtomicIntegerArray {
public static void main(String[] args) {
AtomicIntegerArray array = new AtomicIntegerArray(new int[5]);
for (int i = 0; i < array.length(); i++) {
System.out.print(array.get(i) + " ");
}
System.out.println();
System.out.println(array.getAndSet(0, 1122));
System.out.println(array.getAndIncrement(0));
System.out.println(array.get(0));
}
}
2.3 引用类型原子类
2.3.1 AtomicReference
public class TestAtomicReference {
public static void main(String[] args) {
AtomicReference<User> atomicReference = new AtomicReference<>();
User z3 = new User("张三", 18);
User li4 = new User("李四", 20);
// 设置为z3
atomicReference.set(z3);
// 如果是z3,则修改为li4
System.out.println(atomicReference.compareAndSet(z3, li4) + "\t" + atomicReference.get().toString());
// 如果是z3,则修改为li4
System.out.println(atomicReference.compareAndSet(z3, li4) + "\t" + atomicReference.get().toString());
}
@Data
@ToString
@AllArgsConstructor
static class User {
String userName;
int age;
}
}
2.3.2 AtomicStampedReference
AtomicStampedReference:带版本号的引用类型原子类
public class TestAtomicStampedReference {
public static void main(String[] args) {
// 带有戳记的引用类型原子类
Book javaBook = new Book(1, "javaBook");
AtomicStampedReference<Book> stampedReference = new AtomicStampedReference<>(javaBook, 1);
System.out.println(stampedReference.getReference() + "\t" + stampedReference.getStamp());
// 戳记+1
Book mysqlBook = new Book(2, "mysqlBook");
boolean b = stampedReference.compareAndSet(javaBook, mysqlBook, 1, 2);
System.out.println(b + "\t" + stampedReference.getReference() + "\t" + stampedReference.getStamp());
// 戳记+1
b = stampedReference.compareAndSet(mysqlBook, javaBook, 2, 3);
System.out.println(b + "\t" + stampedReference.getReference() + "\t" + stampedReference.getStamp());
}
@NoArgsConstructor
@AllArgsConstructor
@Data
static class Book {
private int id;
private String bookName;
}
}
2.3.3 AtomicMarkableReference
AtomicMarkableReference:不关心引用变量被修改了几次,只是单纯的关心是否更改过,因此不建议用来解决ABA问题
public class TestAtomicMarkableReference {
private static final AtomicMarkableReference<Integer> MARKABLE_REFERENCE = new AtomicMarkableReference<>(100, false);
public static void main(String[] args) {
new Thread(() -> {
boolean marked = MARKABLE_REFERENCE.isMarked();
System.out.println(Thread.currentThread().getName() + "\t" + "默认标识:" + marked);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
MARKABLE_REFERENCE.compareAndSet(100, 1000, marked, !marked);
}, "t1").start();
new Thread(() -> {
boolean marked = MARKABLE_REFERENCE.isMarked();
System.out.println(Thread.currentThread().getName() + "\t" + "默认标识:" + marked);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean b = MARKABLE_REFERENCE.compareAndSet(100, 2000, marked, !marked);
System.out.println(Thread.currentThread().getName() + "\t" + "t2线程CASresult: " + b);
System.out.println(Thread.currentThread().getName() + "\t" + MARKABLE_REFERENCE.isMarked());
System.out.println(Thread.currentThread().getName() + "\t" + MARKABLE_REFERENCE.getReference());
}, "t2").start();
}
}
2.4 对象属性修改原子类
以一种线程安全的方式操作非线程安全对象内的某些字段,减少锁定的范围,只关注长期、敏感性变化的某一个字段,而不是整个对象,以达到精确加锁+节约内存的目的。
public class TestAtomicIntegerFieldUpdater {
public static void main(String[] args) throws InterruptedException {
BankAccount bankAccount = new BankAccount();
CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 1; i <= 10; i++) {
new Thread(() -> {
try {
for (int j = 1; j <= 1000; j++) {
bankAccount.transMoney(bankAccount);
}
} finally {
countDownLatch.countDown();
}
}, String.valueOf(i)).start();
}
countDownLatch.await();
System.out.println(Thread.currentThread().getName() + "\t" + "result: " + bankAccount.money);
}
static class BankAccount {
String bankName = "CCB";
// 更新的对象属性必须使用 public volatile 修饰符。
public volatile int money = 0;
public void add() {
money++;
}
// 因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须
// 使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。
static final AtomicIntegerFieldUpdater<BankAccount> fieldUpdater =
AtomicIntegerFieldUpdater.newUpdater(BankAccount.class, "money");
// 不加synchronized,保证高性能原子性,局部微创小手术
public void transMoney(BankAccount bankAccount) {
fieldUpdater.getAndIncrement(bankAccount);
}
}
}
2.5 累积性原子类
public class TestAccumulator {
public static final int _1W = 10000;
public static final int threadNumber = 50;
public static void main(String[] args) throws InterruptedException {
ClickNumber clickNumber = new ClickNumber();
long startTime;
long endTime;
CountDownLatch countDownLatch1 = new CountDownLatch(threadNumber);
CountDownLatch countDownLatch2 = new CountDownLatch(threadNumber);
CountDownLatch countDownLatch3 = new CountDownLatch(threadNumber);
CountDownLatch countDownLatch4 = new CountDownLatch(threadNumber);
startTime = System.currentTimeMillis();
for (int i = 1; i <= threadNumber; i++) {
new Thread(() -> {
try {
for (int j = 1; j <= 100 * _1W; j++) {
clickNumber.clickBySynchronized();
}
} finally {
countDownLatch1.countDown();
}
}, String.valueOf(i)).start();
}
countDownLatch1.await();
endTime = System.currentTimeMillis();
System.out.println("----costTime: " + (endTime - startTime) + " 毫秒" + "\t clickBySynchronized: " + clickNumber.number);
startTime = System.currentTimeMillis();
for (int i = 1; i <= threadNumber; i++) {
new Thread(() -> {
try {
for (int j = 1; j <= 100 * _1W; j++) {
clickNumber.clickByAtomicLong();
}
} finally {
countDownLatch2.countDown();
}
}, String.valueOf(i)).start();
}
countDownLatch2.await();
endTime = System.currentTimeMillis();
System.out.println("----costTime: " + (endTime - startTime) + " 毫秒" + "\t clickByAtomicLong: " + clickNumber.atomicLong.get());
startTime = System.currentTimeMillis();
for (int i = 1; i <= threadNumber; i++) {
new Thread(() -> {
try {
for (int j = 1; j <= 100 * _1W; j++) {
clickNumber.clickByLongAdder();
}
} finally {
countDownLatch3.countDown();
}
}, String.valueOf(i)).start();
}
countDownLatch3.await();
endTime = System.currentTimeMillis();
System.out.println("----costTime: " + (endTime - startTime) + " 毫秒" + "\t clickByLongAdder: " + clickNumber.longAdder.sum());
startTime = System.currentTimeMillis();
for (int i = 1; i <= threadNumber; i++) {
new Thread(() -> {
try {
for (int j = 1; j <= 100 * _1W; j++) {
clickNumber.clickByLongAccumulator();
}
} finally {
countDownLatch4.countDown();
}
}, String.valueOf(i)).start();
}
countDownLatch4.await();
endTime = System.currentTimeMillis();
System.out.println("----costTime: " + (endTime - startTime) + " 毫秒" + "\t clickByLongAccumulator: " + clickNumber.longAccumulator.get());
}
static class ClickNumber {
int number = 0;
// 同步锁
public synchronized void clickBySynchronized() {
number++;
}
// 基本数据类型原子类
AtomicLong atomicLong = new AtomicLong(0);
public void clickByAtomicLong() {
atomicLong.getAndIncrement();
}
// LongAdder
LongAdder longAdder = new LongAdder();
public void clickByLongAdder() {
longAdder.increment();
}
// LongAccumulator
LongAccumulator longAccumulator = new LongAccumulator((x, y) -> x + y, 0);
public void clickByLongAccumulator() {
longAccumulator.accumulate(1);
}
}
}