JUC原理与应用
多线程场景可能出现的问题:数据一致性问题,数据安全性问题
synchronized锁:
-
作用于实例方法,当前实例加锁,进入同步代码前要获得当前实例的锁;
-
作用于静态方法,当前类加锁,进去同步代码前要获得当前类对象的锁;
-
作用于代码块,这需要指定加锁的对象,对所给的指定对象加锁,进入同步代码前要获得指定对象的锁。
lock锁:
-
是JDK提供的锁机制,相比synchronized关键字来进行同步锁,功能更加强大,它为锁提供了一个框架,该框架允许更灵活地使用锁
-
ReentrantLock 它是独占锁,是指只能被独自占领,即同一个时间点只能被一个线程锁获取到的锁。
-
ReentrantReadWriteLock 它包括子类ReadLock和WriteLock。ReadLock是共享锁,而WriteLock是独占锁。
-
LockSupport 它具备阻塞线程和解除阻塞线程的功能,并且不会引发死锁。
-
collections(集合类):主要是提供线程安全的集合, 比如:
1)ArrayList对应的高并发类是CopyOnWriteArrayList,
2)HashSet对应的高并发类是 CopyOnWriteArraySet,
3)HashMap对应的高并发类是ConcurrentHashMap
线程与进程
线程和进程的区别:
一个java程序代表一个进程,一个进程可以包含多个线程
线程的状态:
新生(NEW),阻塞(BLOCKED),运行(RUNNABLE),停止(TERMINATED),等待(WAITING),超时等待(TIMED_WAITING)
并发与并行
单核 cpu 下,线程实际还是 串行执行 的。操作系统中有一个组件叫做任务调度器,将 cpu 的时间片( windows下时间片最小约为 15 毫秒)分给不同的程序使用,只是由于 cpu 在线程间(时间片很短)的切换非常快,人类感觉是同时运行的 。总结为一句话就是: 微观串行,宏观并行 , 一般会将这种线程轮流使用 CPU 的做法称为并发( concurrent)。 多核 cpu 下,每个核都可以调度运行线程,这时候线程可以是并行(Parallel)的。简单来说: 并发(concurrent ) 是同一时间段分别应对多件事情的能力 并行(parallel ) 是同一时间段同时动手做多件事情的能力
voliate关键字的作用
-
可见性:保证每一个线程在修改完变量之后,其他线程可以获取到修改后的值
-
原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
-
有序性:即程序执行的顺序按照代码的先后顺序执行,处理器有时会在不影响最终结果的的情况下对代码进行重新排序运行
作用:
-
voliate关键字保证了数据的可见性,用voliate修改的属性会保证在多线程情况下,在被修改后可以让其他线程获取到正确的数据
-
voliate虽然保证了数据的可见性,但是没有保证数据的原子性,即多个线程同时操作的情况下仍旧有 (同时获取主存 - 线程a阻塞,线程2获取主存并修改,线程1修改主存,线程1,2提交),也就是说单单一个voliate关键词无法保证多线程情况下数据的准确性
-
所以voliate常与java.util.concurrent.atomic 包下提供的原子操作类配合使用, 原子类保证数据的原子性,voliate保证数据的可见性
-
voliate可以一定程度上保证数据的有序性,voliate关键字可以静止指令重排
-
voliate代码修饰的变量更改后会强制立即写入主存,所以其他线程会获取到最新的数据
[^] 指令重排:指jvm虚拟机可能会在保证程序结果一致的情况下,将jdk源代码进行重新排序并且执行,
CAS(compare and swap)
cas概念:
比较并交换,cas致力于解决多线程并行情况下使用锁时带来的性能耗损问题,cas是一种乐观锁的轻量级实现方式
cas包含三个操作数,内存位置(V),预期原值(A),新值(B),如果内存位置的值(V)与预期原值所匹配(A),那么就将内存的值变为新值,否则则进行循环(即自旋操作),一直循环到成功或指定次数为止
cas的应用场景:
jdk1.5引入的 java.util.concurrent.atomic 原子类包下的所有类都是基于cas的思想实现的
cas优点:
-
cas是一种非阻塞的轻量级乐观锁,一个线程的阻塞或失败等不会影响到其他线程
cas缺点:
-
aba问题: 线程a将属性由a改成了b,然后又将b改成了a,此时虽然属性没有问题,但是a已经不是原来的a了。这时进来的b会将此时的a判断为原来的a
-
aba问题解决方案:添加版本号,用版本号来判断数据的版本。也可以使用AtomicMarkableReference来判断
-
-
cas只能保证一个共享变量的原子操作。
-
循环时间长,cpu开销大(自旋时间较长的情况下)