线程安全性
当多个线程同时访问某个类时,不管采取何种线程调度方法,在主调代码中不需要采取额外的同步或者协同,这个类都能表现出正确的行为,那么这个类就是线程安全的.
线程安全的三个特性
原子性:提供了互斥访问,同一时刻只能有一个线程来对它进行操作.
可见性:一个线程对主内存的修改会被其他线程所看到.
有序性:编译器和处理器可能会为了性能对操作指令进行重新排序,重新排序后对单个线程的程序执行不会有影响,但对多个线程的程序执行却会造成影响.
模拟线程不安全案例:
//模拟线程不安全
public class AtomicTest1 {
private static final int clientTotal = 5000;
private static final int threadTotal = 200;
private static int count = 0;
public static void main(String[] args) throws Exception {
ExecutorService es = Executors.newCachedThreadPool();
final CountDownLatch ctl = new CountDownLatch(clientTotal);
final Semaphore semaphore = new Semaphore(threadTotal);
for (int i = 0; i < clientTotal; i++) {
es.execute(() -> {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (Exception e) {
e.printStackTrace();
}
ctl.countDown();
});
}
ctl.await();
es.shutdown();
System.out.println("count:" + count);
}
public static void add() {
count++;
}
}
几次运行结果:
显然出现了线程不安全的情况.
我们可以使用java.util.current.atomic包下提供的类进行解决
//模拟线程安全
public class AtomicTest2 {
private static final int clientTotal = 5000;
private static final int threadTotal = 200;
private static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws Exception {
ExecutorService es = Executors.newCachedThreadPool();
final CountDownLatch ctl = new CountDownLatch(clientTotal);
final Semaphore semaphore = new Semaphore(threadTotal);
for (int i = 0; i < clientTotal; i++) {
es.execute(() -> {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (Exception e) {
e.printStackTrace();
}
ctl.countDown();
});
}
ctl.await();
es.shutdown();
System.out.println("count:" + count);
}
public static void add() {
count.getAndIncrement();
}
}
多次执行后,结果依旧正确:
其中getAndIncrement方法的实现,调用了sun.misc.unsafe包下的unsafe方法
compareAndSwap,这就是我们熟悉的CAS自旋锁,Jdk1.8中的concurrentHashMap也有用到它.
提到CAS就顺便说一下CAS中的ABA问题,ABA即一个线程在把A的值改成了B,另外一个线程有把B改回了A,此时再来一个线程操作时发现A的值并没有改变,但实际上A的值是有被操作过的,为了避免在这种情况下CAS锁失效.atomic包提供了AtomicStampReference类来解决ABA问题,每次对值的改变操作,都会使得版本号发生改变,这样就可以避免ABA问题.
顺便提一下,AtomicLong,AtomicLong作用和AtomicInteger类似,只不过数据类型不一样,之所以提AtomicLong是为了引出jdk8中新提供的LongAdder,它们功能相仿,但性能却有差别,在上面的源码中我们可以看到,getAndAdd方法使用do while循环调用cas实现的,这在并发比较高的情况下,循环耗费较多性能,所以在LongAdder对其进行了改进,将对数字的读写操作通过类似hash的算法分散到不同的cell中,最后再对cell累加得出计算后的值,在高并发的场景下,分散带来的性能提升是显而易见的,不过也因此付出了精度损失的代价,累加后的结果可能会出现小幅度误差.
JUC下的atomic包提供了很多可以保证原子性的类,其中常见常用的如图:
对比字段的期望值和更新值AtomicReference:
public class AtomicTest4 {
public static void main(String[] args) {
AtomicReference atomicReference = new AtomicReference<>(1);
atomicReference.compareAndSet(1,2);
atomicReference.compareAndSet(2,4);
atomicReference.compareAndSet(3,5);
atomicReference.compareAndSet(4,6);
System.out.println(atomicReference.get());
}
}
结果:
AtomicXXXFieldUpdater实现对类中指定的volatile对应类型字段进行原子性操作.
public class AtomicTest5 {
@Getter
public volatile int count = 100;
private static AtomicTest5 atomicTest5 = new AtomicTest5();
private static AtomicIntegerFieldUpdater updater = AtomicIntegerFieldUpdater.newUpdater(AtomicTest5.class, "count");
public static void main(String[] args) {
if (updater.compareAndSet(atomicTest5,100,200)){
System.out.println("success::"+atomicTest5.getCount());
}else if (updater.compareAndSet(atomicTest5,500,800)){
System.out.println("success::"+atomicTest5.getCount());
}else {
System.out.println("fail:"+atomicTest5.getCount());
}
}
}
结果:
使用AtomicBoolean来确保某段代码/方法只被执行一次:
public class AutomicTest6 {
private static final int clientTotal = 5000;
private static final int threadTotal = 2000;
private static AtomicBoolean isHappend = new AtomicBoolean(false);
public static void main(String[] args) throws Exception {
ExecutorService es = Executors.newCachedThreadPool();
final CountDownLatch ctl = new CountDownLatch(clientTotal);
final Semaphore semaphore = new Semaphore(threadTotal);
for (int i = 0; i < clientTotal; i++) {
es.execute(() -> {
try {
semaphore.acquire();
test();
semaphore.release();
} catch (Exception e) {
e.printStackTrace();
}
ctl.countDown();
});
}
ctl.await();
es.shutdown();
}
private static void test() {
if (isHappend.compareAndSet(false, true)) {
System.out.println("executed");
}
}
}
结果:
AtomicIntegerArray,相当于是一个Int数组,但其中的元素可用原子更新.
public class AtomicTest7 {
public static void main(String[] args) {
AtomicIntegerArray array = new AtomicIntegerArray(10);
array.set(5,50);
ExecutorService es = Executors.newCachedThreadPool();
for (int i = 0; i < 50; i++) {
es.execute(()->{
array.compareAndSet(5,50,80);
});
}
es.shutdown();
System.out.println(array.get(5));
}
}
结果:
关于atomic包就分享到这里,感兴趣可以翻阅Jdk1.8-api,翻api+适当读点源码对加深理解非常有帮助,最后感谢阅读,文中如有不正之处或有更好的建议还望各位看客不吝赐教.