1. 原子性
原子性是指:多个操作作为一个整体,不能被分割与中断,也不能被其他线程干扰。如果被中断与干扰,则会出现数据异常、逻辑异常。
多个操作合并的整体,我们称之为复合操作。一个复合操作,往往存在前后依赖关系,后一个操作依赖上一个操作的结果。如果上一个操作结果被其他线程干扰,对于当前线程看来整个复合操作的结果便不符合预期。同理线程也不能在复合操作中间被中断,中断必须发生在进入复合操作之前或者等到复合操作结束之后。
保证原子性就是在多线程环境下,保证单个线程执行复合操作符合预期逻辑。
典型的复合操作:『先检查后执行』和『读取—修改—写入』
如何保证线程的一致性
使用较多的三种方式:
内置锁(同步关键字):synchronized;
显示锁:Lock;
自旋锁:CAS;
当然这三种实现方式和保证同步的机制上都有所不同,在这里我们不做深入的说明。
加锁 synchronized ,什么是锁,锁实现的就是将你内部的操作保证你操作的时候没有别人修改过。
乐观锁
乐观锁也称为自旋锁也就是cas,这种机制大致就行,我修改一个值,比如a=3,我需要给他+1操作 ,此时在我+1操作的时候会判断a是不是还是等于3,如果等于三则进行+1操作 cas内部机制是原子的,不会出现判断了之后被别的线程修改了,我才又执行加一的操作
ABA情况
上面说了乐观锁会又判断操作然后修改。
假设 我预计的值为 1,有一个线程把他改为2了又改为1了那我是不是就不知道了,此时就有了版本号的概念。则每次操作这个值版本号会更新。
此时又会有问题,这样的话不影响最终的结果啊
但是如果是对象类型呢 假设你需要对象a内有属性被更改了,或者逻辑被更改了,但是引用指向的对象并没有改此时你将无法感知
悲观锁
悲观锁之前先大致说一下用户态于内核态
笼统来说 用户态无法执行类似操作系统的东西,当需要用到时候需要申请,需要耗费大量的资源
我的代码程序就是用户态
悲观锁的概念呢就是,我开始以为你会被别人修改,我来的时候就把你锁定住,别的线程来了,就先等待挂起,我执行完了你再进行抢夺这个锁,抢到了再执行
锁定这个操作需申请所以开销很大
乐观锁效率与悲观锁效率
乐观锁:
优点 不需要申请资源,所以一开始开销比较小
缺点 以为你的线程当不满足条件的时候会一直自旋占用资源所以当,并发量高的时候则不建议用乐观锁
悲观锁:
优点 当线程多的时候,只有一个线程执行,其他线程是挂起的,不会消费资源所以线程多的时候效率高于乐观锁
缺点 一开始开销大
synchronized 的锁升级机制
偏向锁 ----> 乐观锁(轻量级锁) ----> 悲观锁(重量级锁)
当有竞争的时候 由偏向锁升级为轻量级
当竞争到达设定的值得时候 轻量级 升级为重量级锁
中间还有匿名偏向锁的过程,了解即可
当并发高的时候,可以关闭偏向锁,或者直接升级为重量级锁,详情查一下api即可,给予jdk8来说 之前synchronized 是直接重量级锁
2. 可见性
可见性问题是指,一个线程修改的共享变量,其他线程是否能够立刻看到。对于串行程序而言,并不存在可见性问题,前一个操作修改的变量,后一个操作一定能读取到最新值。但在多线程环境下如果没有正确的同步则不一定。
如何保持可见性
如何保持数据可见性
此时就有一个关键字 Volatile
知道了问题大致就行猜到 Volatile 的作用了,就是将我们修改的值,立刻同步内存并同步到所有引用中去
3. 有序性
有序性问题是指从观察到的结果推测,代码执行的顺序与代码组织的顺序不一致。
使用synchronized、volatile,加锁lock等方式一般及可以保证线程的可见性与有序性。