线程安全

线程安全:当多个线程访问某一个类时,不管运行时环境采用何种调度方式或者这些线程如何交替执行,并且主调代码中不需要外的同步或协同操作,这个类都能表现出正确的行为,那么这个类就是线程安全的。
内存模型的三大特性

  • 原子性:提供互斥访问,同一时刻内只有一个线程对它进行修改。
  • 可见性:一个线程对主内存的修改可以及时被其他线程观察到。
  • 有序性:一个线程观察到其他线程中指令执行顺序,由于指令重排序的存在,该观察结果一般无序。

原子性

保证原子性的方法:

  1. 使用原子类:

线程不安全示例

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();
    }
}
原子类保证操作原子性的原理(CAS:unsafe.Compare-And-Swap):

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...");
        }
    }
}
  1. 使用锁:

synchronized:依赖jvm 详细

lock:依赖特殊的cpu指令,代码实现,ReentrantLock
详细

小结

synchronized:不可中断锁,适合竞争不激烈的环境,可读性好。
Lock:可中断锁,多样化同步,竞争激烈时能维持常态。
Atomic:竞争激烈时能维持常态,比Lock性能好;只能同步一个值。

可见性

导致不可见的原因

  1. 线程交叉执行
  2. 重排序结合线程交叉执行
  3. 共享变量更新后的值没有及时在主内存和工作内存中及时更新

保证可见性

  1. synchronize:

JMM对synchronize的规定

  • 线程解锁前必须把共享变量的最新值刷新到主内存。
  • 线程加锁前必须先清空工作内存中共享变量的值,从而使用共享变量的值时都必须从主内存中读取最新值(加锁和解锁都是同一把锁)。
  1. volatile:

通过加入内存屏障禁止重排序优化来实现

  • 对volatile变量写操作时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量值刷新到主内存
    在这里插入图片描述
  • 对volatile变量读操作时,会在读操作之前加入一条load屏障指令,从主内存中读取共享变量
    在这里插入图片描述
    volatile并不能保证操作的原子性,所以被volatile修饰的变量并不能保证线程安全。
    volatile的应用:
    双检查单例模式:
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。

总结

  1. 原子性:Atomic包,CAS算法、synchronized、Lock
  2. 可见性: synchronized、volatile
  3. 有序性:先行发生原则

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值