1.CAS: 比较和交换算法. 乐观锁(通过算法的形式达到了原子性)
a++:
* 1.从主内存中拷贝变量到线程的工作内存中, 保存主内存中变量的副本
* 2.线程在工作内存中操作变量副本
* 3.将工作内存中的值同步到主内存, 比较副本值和主内存中的值
* 相同: 同步到主内中
* 不相同: 从第一步从新开始(循环)
* 通常CAS配合Volatile使用
* 通过Volatile和CAS实现锁的效果, CAS无锁的操作, 没有死锁的问题
缺点
1. 多线程操作时每次修改值时,可能多次出现内存值和副本值不同的情况,需要循环执行多次
2. 可能出现ABA问题
为了解决ABA问题,一般都会在操作时设置一个int类型版本号(version),每次对内存中变量操作后都让版本号加1。
当需要修改变量时,除了比较副本中值和内存值以外,还需要比较版本号是否相同。JDK中AtomicStampedReference就是这种方式,
提供了全局int属性
1_1原子类
原子性:操作过程中不允许其他线程干扰, 可以理解为数据操作是整体,整体只有成功或失败,不允许出现部分成功部分失败
AtomicInteger:
底层使用的是volatile和cas
incrementAndGet(): 先加1,在获取,类似于++num。
getAndIncrement():先获取,后加1,类似于num++。
addAndGet(int);先在原值上增加指定值,后获取结果。
getAndSet(int):先获取,后设置值。
decrementAndGet():先减1,在获取,类似--num。
getAndDecrement():先获取,在减1,类似num--。
set(int):设置值。
get()返回value值。
2.Volatile:
是JMM中三大特性之一,在多线程情况下.,可以保证共享数据的可见性,有序性但是不能保证数据操作的原子性(数据操作是整体,整体只有成功或失败,不允许出现部分成功部分失败),多线程情况下volatile修饰的变量线程不安全.
可见性
* 线程操作副本值后, 立刻同步到主内存中
* 变量被修改, 其他线程能立即知道这个变量的变化,其他线程使用该变量前, 从主内存中拷贝最新的变量
* 实现原理:
* 多核cpu情况下,为了提高处理速度,cpu不直接对主内存进行操作,而是在cpu和内存之间加入了高速缓存,进行数据操作时,先把数据从主内存拷贝到高速缓存
* 中,cpu直接操作高速缓存中的数据,,在多处理器情况下,可能会出现高速缓存数据不一致的问题,引入可见性,为了保证缓存数据一致性,加入缓存一致性协议,底层是
* 用总线嗅探机制实现,当变量发生变化时,副本的值和主内存的值不一致,处理器就将副本值设置为无效状态,使用前,再从主内存中拷贝最新的值.
* 下面是对上面的总结:
* 添加了缓存一致性协议, 拷贝 | 同步 变量值时, 都需要经过缓存一致性协议,
* 总线嗅探机制, 变量值发生修改, 副本中的值和主内存中的值不一致, 使用前,
* 将副本值设置为无效状态, 使用时, 拷贝最新的主内存中的变量值
有序性:
* java编译器生成字节码文件时,会在指令序列中插入内存屏障(CPU处理器的指令)来禁止CPU处理器重排序
* volatile 尽量少用.否则会导致总线风暴,当某个变量不想被指令重排,保证可见性,可以使用volatile修饰
* 可以用synchronized锁进行代替
3.JUC中的锁:
JUC中的锁底层实现为AQS:
* 全名是AbstractQueueSynchronizer,是并发容器下locks包的一个类
* AQS: 底层为队列, FIFO(先进先出)
* 队列是由双向链表实现
* 存储要执行操作资源的线程.
* 被请求的资源被占用,其他线程则去队列排队
* 被操作的资源有一个state属性, state>0 上锁了 state=0 没有加锁
* state需要使用CAS操作
*
4. 重入锁: 一个线程允许多次持有同一把锁
* ReentrantLock: 重入锁 synchronized: 重入锁
* ReentrantLock: 公平锁 非公平锁
* 公平锁: 新的执行任务的线程在队列中排队
* 非公平锁: 先抢锁抢到了就执行, 没有抢到再去排队
*
* Condition: 本身是一个接口,,通过lock.newCondition() 实例化,
* 线程等待 唤醒线程
* condition.await()
* condition.single()
* condition.singleAll()
ReentrantReadWriteLock: 读写锁
* 读锁: 共享锁
* 多个线程可以同时持有
* 写锁: 排它锁
* 只能一个线程持有
LockSupport.park() : 暂停线程
* LockSupport.unPark() : 恢复线程
* 释放锁: 必须使用在有所的代码块中
不释放锁:
* 1.可以用在有锁的代码块中
* 2.可以用在没有锁的代码块中
4_1synchronized和lock的区别
- 类型不同
synchronized是关键字。修饰方法, 修饰代码块
Lock是接口
- 加锁和解锁机制同步
synchronized是自动加锁和解锁,遇到异常自动释放锁,程序员不需要控制。
Lock必须由程序员控制加锁和解锁过程,需要注意出现异常不会自动解锁
如果要在抛异常的时候释放锁,在finnally中释放锁
- 异常机制
synchronized碰到没有处理的异常,会自动解锁,不会出现死锁。
Lock碰到异常不会自动解锁,会出现死锁。所以写Lock锁时都是把解锁放入到finally{}中。
- Lock功能更强大
Lock里面提供了tryLock()/isLocked()方法,进行判断是否上锁成功。synchronized因为是关键字,所以无法判断。
- Lock性能更优
如果多线程竞争锁特别激烈时,Lock的性能更优。如果竞争不激烈,性能相差不大。
- 线程通信方式不同
synchronized 使用wait()和notify()线程通信。
Lock使用Condition的await()和signal()通信。
- 暂停和恢复方式不同
synchronized 使用suspend()和resume()暂停和恢复,这俩方法jdk1.2过时了。
Lock使用LockSupport中park()和unpark()暂停和恢复,这俩方法没有过时。
5.Tools:
* CountDownLatch: 计数器
* cdl.await(): 计数器的值>0, 线程阻塞
* 计数器的值=0, 线程继续执行
* cdl.countDown(): 调用一次计数器的值-1
*
6.并发集合类:
ArrayList: 线程不安全的
* Vector: 线程安全, get add 都有锁
* CopyOnWriteArrayList: 写时复制
* 添加元素时, 拷贝数组, 向新数组中添加
* 读取元素可以在原数组中操作, 可以并发的读
并发集合类:主要是提供线程安全的集合
比如:
1. ArrayList对应的并发类是CopyOnWriteArrayList
2. HashSet对应的并发类是 CopyOnWriteArraySet
3. HashMap对应的并发类是ConcurrentHashMap
CopyOnWriteArrayList
写时复制
通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后往新的容器里添加元素
添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
对于读操作远远多于写操作的应用非常适合,特别在并发情况下,可以提供高性能的并发读取。
CopyOnWrite容器只能保证数据的最终一致性,不能保证数据实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。
CopyOnWriteArrayList重点源码
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
//创建不可改变的对象
final transient Object lock = new Object();
//volatile修饰的Object类型的数组, 保证了数组的可见性, 有序性
private transient volatile Object[] array;
//获取元素, 根据下标获取元素, 支持多线程查询
public E get(int index) {
return elementAt(getArray(), index);
}
//设置数组
final void setArray(Object[] a) {
array = a;
}
//添加元素, 写时复制
public boolean add(E e) {
//加锁
synchronized (lock) {
//获取当前数组
Object[] es = getArray();
//获取数组的长度
int len = es.length;
//复制旧数组, 长度+1, 创建一个新数组
es = Arrays.copyOf(es, len + 1)