目录
无锁实现线程安全
无锁与synchronized效率对比
原子整数
原子引用类型
ABA问题
原子数组
字段更新器
原子累加器LongAdder
LongAdder源码分析
Unsafe
cas修改对象属性值
案例
对于银行取钱来说,需要保证线程安全,一个1w的账户由1k个线程取10块,来保证取完后为0
import java.util.ArrayList;
public class demo7 {
public static void main(String[] args) {
Amount unsafeAmount = new UnsafeAmount(10000);
Amount.demo(unsafeAmount);
}
}
class UnsafeAmount implements Amount {
private Integer money;
public UnsafeAmount(Integer money) {
this.money = money;
}
@Override
public Integer getMoney() {
return money;
}
@Override
public void withdraw(Integer money) {
this.money -= money;
}
}
interface Amount {
public Integer getMoney();
public void withdraw(Integer money);
static void demo(Amount amount) {
ArrayList<Thread> threads = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
threads.add(new Thread(() -> {
amount.withdraw(10);
}));
}
long start = System.nanoTime();
threads.forEach(Thread::start);
threads.forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.nanoTime();
System.out.println(amount.getMoney() + " 时间:" + (end - start) / 100000);
}
}
170 时间:444
可以通过对withdraw()加锁的方式来保证线程安全,也可以使用不加锁的方式保证线程安全
无锁实现线程安全
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;
public class demo7 {
public static void main(String[] args) {
Amount safeAmount = new SafeAmount(10000);
Amount.demo(safeAmount);
Amount amountCas = new AmountCas(10000);
Amount.demo(amountCas);
}
}
//使用不加锁的方式实现线程安全
class AmountCas implements Amount {
public AtomicInteger atomicInteger;
public AmountCas(Integer money) {
this.atomicInteger = new AtomicInteger(money);
}
@Override
public Integer getMoney() {
return atomicInteger.get();
}
@Override
public void withdraw(Integer money) {
while (true) {
int prev = atomicInteger.get();
int next = prev - money;
if (atomicInteger.compareAndSet(prev, next)) {
break;
}
}
}
}
class SafeAmount implements Amount {
Integer money;
public SafeAmount(Integer money) {
this.money = money;
}
@Override
public Integer getMoney() {
return money;
}
@Override
public void withdraw(Integer money) {
synchronized (this) {
this.money -= money;
}
}
}
interface Amount {
public Integer getMoney();
public void withdraw(Integer money);
static void demo(Amount amount) {
ArrayList<Thread> threads = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
threads.add(new Thread(() -> {
amount.withdraw(10);
}));
}
long start = System.nanoTime();
threads.forEach(Thread::start);
threads.forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.nanoTime();
System.out.println(amount.getMoney() + " 时间:" + (end - start) / 100000);
}
}
0 时间:485
0 时间:390
可以看出来,使用AtomicInteger比使用synchronized更加快速。能够实现线程安全主要依靠compareAndSet()方法。首先它是一个原子操作不用担心线程上下文切换问题,它会对比两个参数中的前者与atomicInteger中的值是否相同,如果相同,返回flase,如果不同,则会修改atomicInteger中的值为后者。
CAS必须使用volatile才可以实现,因为我们需要知道最新的数据,来保证compareAndSet()的准确性。
无锁与synchronized效率对比
无锁之所以比synchronized更快,是因为无锁状态下是死循环,而synchronized是线程上下文切换,相应的,无锁状态下更依赖CPU的性能。没有分到CPU时间片时,线程会进入可运行状态,也会进入线程上下文切换,但比synchronized切换次数更少。
原子整数
是JUC包下的方法。包括了AtomicBoolean、Atomicinteger、AtomicLong。
都是通过CAS的方式实现线程安全。
AtomicInteger atomic = new AtomicInteger();
atomic.incrementAndGet();//自增并且返回
atomic.getAndIncrement();//取值并自增
atomic.getAndAdd(value);//取值并增加value
atomic.updateAndGet(value -> value*2);//lambda表达式自定义选择运算方式并返回。
原子引用类型
AtomicReference、AtomicStampedReference
对于银行取钱案例也可以使用AtomicReference来保证线程安全。
ABA问题
public class demo8 {
static AtomicReference<String> str = new AtomicReference<>("A");
public static void main(String[] args) throws InterruptedException {
String prev = str.get();
Thread.sleep(1000);
System.out.println(str.compareAndSet(prev,"C"));
}
}
将A改为C是没有问题的,但是如果其他线程将A修改为B后再修改回来呢?是否还是可以将A修改为C呢?
public class demo8 {
static AtomicReference<String> str = new AtomicReference<>("A");
public static void main(String[] args) throws InterruptedException {
String prev = str.get();
ABA();
Thread.sleep(1000);
System.out.println(str.compareAndSet(prev,"C"));
}
public static void ABA(){
new Thread(()->{
String prev = str.get();
str.compareAndSet(prev,"B");
}).start();
new Thread(()->{
String prev = str.get();
str.compareAndSet(prev,"A");
}).start();
}
}
true
答案是也可以修改,因此我们可以知道,主线程是无法感知其他线程对str变量的修改,只要执行compareAndSet()方法时,prev的值与当前的值相同就可以完成修改。
如果要实现其他线程对其修改值后,我们需要主线修改失败,则需要使用AtomicStampedRefernece类,除了要制定属性值外还要指定版本号
public class demo8 {
static AtomicStampedReference<String> str = new AtomicStampedReference<>("A",0);
public static void main(String[] args) throws InterruptedException {
String prev = str.getReference();//获取值
int stamp = str.getStamp();//获取版本号
System.out.println("初始版本号:"+stamp);
ABA();
Thread.sleep(1000);
System.out.println(str.compareAndSet(prev,"C",stamp,stamp+1));
}
public static void ABA(){
new Thread(()->{
String prev = str.getReference();
int stamp = str.getStamp();
System.out.println("修改A为B的版本号:"+stamp);
str.compareAndSet(prev,"B",stamp,stamp+1);
}).start();
new Thread(()->{
String prev = str.getReference();
int stamp = str.getStamp();
System.out.println("修改B为A的版本号:"+stamp);
str.compareAndSet(prev,"A",stamp,stamp+1);
}).start();
}
}
初始版本号:0
修改A为B的版本号:0
修改B为A的版本号:1
false
AtomicStampedReference是追踪更改过几次,如果是只需要知道更改过没有,可以使用AtomicMarkableReference来实现
public class demo9 {
static AtomicMarkableReference<Bag> atomic = new AtomicMarkableReference(new Bag("装满了垃圾"),true);
public static void main(String[] args) throws InterruptedException {
//如果装满了垃圾就更换垃圾袋,如果没有就不更换
Bag prev = atomic.getReference();
new Thread(()->{
System.out.println("修改垃圾袋属性");
prev.setMsg("空垃圾袋");
atomic.compareAndSet(prev,prev,true,false);
}).start();
Thread.sleep(1000);
boolean result = atomic.compareAndSet(prev, new Bag("空垃圾袋"), true, false);
System.out.println("是否更换了垃圾袋:"+result);
}
}
修改垃圾袋属性
是否更换了垃圾袋:false
原子数组
并非针对数组对象,而是针对数组内的数据变化
AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
下面案例是创建一个长度为10的数组,需要十个线程向数组中每个位置进行2000字自增。
public class demo10 {
public static void main(String[] args) {
demo(
()->new int[10],
(array)->array.length,
(array,index)->array[index]++,
(array)-> System.out.println(Arrays.toString(array))
);
}
/**
* Supplier 提供者 使用方法 ()->new Class 无参数一个结果。作用是提供一个对象
* Function 函数 使用方法 (array)->array.方法 一个参数一个结果
* Biconsumer 消费者 使用方法 (参数一,参数二)->具体操作 两个参数无结果
* Consumer 消费者 使用方法 (参数一)->具体操作 一个参数无结果
**/
private static <T> void demo(Supplier<T> arraySupplier,
Function<T, Integer> function,
BiConsumer<T, Integer> consumer,
Consumer<T> printConsumer) {
ArrayList<Thread> threads = new ArrayList<>();
T t = arraySupplier.get();
Integer len = function.apply(t);
for (int i = 0; i < len; i++) {
threads.add(new Thread(()->{
for (int j = 0; j < 10000; j++) {
consumer.accept(t,j%len);
}
}));
}
threads.forEach(Thread::start);
threads.forEach(thread -> {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
printConsumer.accept(t);
}
}
[7800, 7859, 7738, 7749, 7864, 7802, 7858, 7845, 7831, 7886]
很显然,并没有完成原本设想内容。因此我们可以使用AtomicIntegerArray对数组内容进行操作。
public class demo10 {
public static void main(String[] args) {
demo(
()->new AtomicIntegerArray(10),
(array)->array.length(),
(array,index)->array.getAndIncrement(index),
(array)-> System.out.println(array)
);
}
private static <T> void demo(Supplier<T> arraySupplier,
Function<T, Integer> function,
BiConsumer<T, Integer> consumer,
Consumer<T> printConsumer) {
ArrayList<Thread> threads = new ArrayList<>();
T t = arraySupplier.get();
Integer len = function.apply(t);
for (int i = 0; i < len; i++) {
threads.add(new Thread(()->{
for (int j = 0; j < 10000; j++) {
consumer.accept(t,j%len);
}
}));
}
threads.forEach(Thread::start);
threads.forEach(thread -> {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
printConsumer.accept(t);
}
}
[10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000]
字段更新器
针对对象中的属性修改
AtomicIntegerFieldUpdate、AtomicLongFieldUpdate、AtomicReferenceFieldUpdate
public class demo11 {
public static void main(String[] args) {
Student student = new Student();
//三个参数分别是字段所在的类,被修改字段的类类型,以及被修改字段名称
AtomicReferenceFieldUpdater updater = AtomicReferenceFieldUpdater
.newUpdater(Student.class, String.class, "name");
System.out.println(updater.compareAndSet(student, null, "张三"));
System.out.println(student);
}
}
class Student{
volatile String name;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
}
注意:被修改的字段要搭配volatile使用。
原子累加器LongAdder
比AtomicInterger中的累加效率更好
下面的案例是4个线程每个线程自增10000次,分别用AtomicInteger与LongAddder实现,观察运行时间。
public class demo12 {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
demo(
() -> new AtomicInteger(0),
(atomic) -> atomic.getAndIncrement()
);
}
for (int i = 0; i < 5; i++) {
demo(
()->new LongAdder(),
(adder)->adder.increment()
);
}
}
private static <T> void demo(Supplier<T> supplier,
Consumer<T> consumer) {
T t = supplier.get();
long start = System.nanoTime();
ArrayList<Thread> threads = new ArrayList<>();
for (int i = 0; i < 4; i++) {
threads.add(new Thread(() -> {
for (int j = 0; j < 10000; j++) {
consumer.accept(t);
}
}));
}
threads.forEach(Thread::start);
threads.forEach(thread -> {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.nanoTime();
System.out.println(t+" 时间:"+(end-start)/100_000);
}
}
40000 时间:27
40000 时间:15
40000 时间:19
40000 时间:19
40000 时间:13
40000 时间:29
40000 时间:17
40000 时间:2
40000 时间:2
40000 时间:7
可以看出来LongAdder的耗时比AtomicInteger更短
LongAdder源码分析
LongAdder之所以比AtomicInteger更快,是因为LongAdder可以开辟新的单元进行自增操作,大概就是AtomicInteger在while(true)中对一个value进行自增,但是LongAdder可以存在多个value进行自增,最后将多个value综合相加就是自增结果,减少了重试次数。
LongAdder中存在几个属性值
单元格表。非空时,大小为2的幂。
transient volatile Cell[] cells;
基本值,主要在没有争用时使用,但也用作表初始化竞争期间的回退。通过CAS更新。
transient volatile long base;
调整大小和或创建单元格时使用的自旋锁(通过CAS锁定)。为1时表示加锁
transient volatile int cellsBusy;
首先进行单元格判断,是否开辟了新的单元格(如果没有竞争即只有一个线程时,是不会开辟单元格的)如果为null则进行caseBase操作(实际上就是compareAndSet)。如果交换失败,说明是多线程操作。第二个if体内主要判断当前线程是否创建了cell如果创建了就对cell中的value值进行自加操作,如果没有创建则进入longAccumulate()方法进行具体判断
for(;;)内的三个if块对应逻辑分别如下图所示。
for(;;)中三个if分别代表着已经创建有cell、没有创建cell并且没有添加cas锁,对其进行加锁、加锁失败对base进行自加操作。
Unsafe
Unsafe提供非常底层的内存、线程操作。Unsafe对象不能通过创建获取,只能通过反射获取
public class test {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
//通过反射拿到unsafe对象
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null);
System.out.println(unsafe);
}
}
cas修改对象属性值
public class demo13 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null);
//获取对象字段偏移量
long id = unsafe.objectFieldOffset(Teacher.class.getDeclaredField("id"));
long name = unsafe.objectFieldOffset(Teacher.class.getDeclaredField("name"));
Teacher teacher = new Teacher();
unsafe.compareAndSwapInt(teacher,id,0,1);
unsafe.compareAndSwapObject(teacher,name,null,"张三");
System.out.println(teacher);
}
}
class Teacher{
volatile int id;
volatile String name;
@Override
public String toString() {
return "Teacher{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
Teacher{id=1, name='张三'}