线程安全:当多个线程访问某一个类时,不管运行时环境采用何种调度方式或者这些线程如何交替执行,并且主调代码中不需要外的同步或协同操作,这个类都能表现出正确的行为,那么这个类就是线程安全的。
内存模型的三大特性:
保证原子性的方法:
线程不安全示例:
public class AtomicExample1 {
//请求总数
private static int clientTotal = 5000;
//线程数
private static int threadTotal = 200;
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
System.out.println("count : "+count);
}
private static void add(){
count++;
}
}
输出结果永远少于5000,是线程不安全的,
而只需使用AutomicInteger原子类代替int类型即可解决线程安全的问题。
线程安全:
public class AtomicExample1 {
//请求总数
private static int clientTotal = 5000;
//线程数
private static int threadTotal = 200;
private static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
System.out.println("count : "+count.get());
}
private static void add(){
count.incrementAndGet();
}
}
CompareAndSwap就是原子类保证操作原子性的核心方法,即CAS
AtomicInteger的incrementAndGet()方法的实现:
调用了unsafe类中的getAndAddInt方法,该方法在unsafe类中的实现:
var1是当前对象,var2是当前对象中的值,var4是期望增加的值,var5是从主内存中取出来的值。
通过从底层中取出来的值var5与当前值var2进行相比较,如果底层值与当前值相同则更新,否则继续从底层中取值,直到两值相等为止。
(其中compareAndSwapInt()、getIntVolatile()都是被native方法,即jdk的底层实现的方法)
AtomicLong和LongAdder
LongAdder是jdk8新增的一个类
AtomicLong中存在的问题:原子类保证原子操作是放在一个死循环里进行的,当线程竞争激烈时,线程修改值不断失败导致不断循环,从而增加了性能的开销。
LongAdder就是用来解决这个问题的。
原理:jvm允许将一个long,double 64位的基本类型的读操作和写操作拆分成两个32位的操作。LongAdder就是基于这个思想,它可以将内部核心数据进行分离,分离成一个数组,线程访问时通过hash等算法预测到其中一个数字进行计数,最终的计数结果则为这个数组的累加的和。热点数据value会被分成多个单元的cell,每个cell独自维护内部的值,当前对象的值为所有的cell累积合成。当线程竞争不激烈时,通过对base的直接修改完成;当线程竞争激烈时,通过把压力分散到各个节点上增加性能。
缺点:在统计的时候如果有数据并发更新,会导致统计结果不准确。
在大多数情况下使用LongAdder是最好的选择,但在需要统计结果准确时(如全局流水号),或者线程竞争不激烈的情况下,使用AtomicLong是简单快捷的选择。
compareAndSet
compareAndSet主要用于保证代码只执行一次
在AtomicReference中
//AtomicReference
public class AtomicExample3 {
private static AtomicReference<Integer> count = new AtomicReference<>(0);
public static void main(String[] args) {
count.compareAndSet(0,4);//4
count.compareAndSet(2,4);//n
count.compareAndSet(4,8);//8
count.compareAndSet(2,4);//n
System.out.println("count : "+count);
}
}
输出结果为:8
AtomicIntegerFieldUpdater
//AtomicIntegerFieldUpdater
public class AtomicExample4 {
private static AtomicIntegerFieldUpdater<AtomicExample4> updater = AtomicIntegerFieldUpdater.newUpdater(AtomicExample4.class,"count");
private volatile int count = 100;
public static void main(String[] args) {
AtomicExample4 atomicExample4 = new AtomicExample4();
if (updater.compareAndSet(atomicExample4, 100, 120)) {
System.out.println(" 1 update success");
}
if (updater.compareAndSet(atomicExample4, 100, 120)) {
System.out.println(" 2 update success");
}else {
System.out.println(" 2 update filed");
}
}
}
newUpdater中指定要修改的变量的名称,指定的变量必须为非static的且被volatile修饰的变量。
AtomicStampedReference
该类主要用于解决CAS的ABA问题,通过stamp值的比较来解决。
例如:线程1修改了变量后,把版本号设置为1,线程2修改后把版本号设置为2,通过对版本号的比较来防止ABA问题出现。
AtomicBoolean
用来保证代码只被执行一次
//AtomicBoolean
public class AtomicExample5 {
//请求总数
private static int clientTotal = 5000;
//线程数
private static int threadTotal = 200;
private static AtomicBoolean isHappened = new AtomicBoolean(false);
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
test();
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
}
private static void test(){
if (isHappened.compareAndSet(false,true)){
System.out.println("executed...");
}
}
}
synchronized:依赖jvm 详细
lock:依赖特殊的cpu指令,代码实现,ReentrantLock
详细
synchronized:不可中断锁,适合竞争不激烈的环境,可读性好。
Lock:可中断锁,多样化同步,竞争激烈时能维持常态。
Atomic:竞争激烈时能维持常态,比Lock性能好;只能同步一个值。
导致不可见的原因:
保证可见性:
JMM对synchronize的规定:
通过加入内存屏障和禁止重排序优化来实现
class CheckedSingleObject{
private volatile static CheckedSingleObject instance = null;
private CheckedSingleObject() {
}
public static CheckedSingleObject getInstance() {
if (instance == null) {
synchronized (CheckedSingleObject.class){
if (instance == null) {
instance = new CheckedSingleObject();
}
}
}
return instance;
}
}
java内存模型,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序执行结果的正确性,却会影响到多线程执行结果的正确性。
保证有序性:volatile、synchronized、lock
1. 单一线程原则
在一个线程内,按照代码顺序,在程序前面的操作先行发生于后面的操作。
2. 管程锁定规则
一个 unlock 操作先行发生于后面对同一个锁的 lock 操作。
3. volatile 变量规则
对一个 volatile 变量的写操作先行发生于后面对这个变量的读操作。
4. 线程启动规则
Thread 对象的 start() 方法调用先行发生于此线程的每一个动作。
5. 线程终结规则
线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join() 方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行 。
6. 线程中断规则
对线程 interrupt() 方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过 interrupted() 方法检测到是否有中断发生。
7. 对象终结规则
一个对象的初始化完成(构造函数执行结束)先行发生于它的 finalize() 方法的开始。
8. 传递性
如果操作 A 先行发生于操作 B,操作 B 先行发生于操作 C,那么操作 A 先行发生于操作 C。