-
volatile:
在java的内存模型中,每个线程都有自己的工作内存,同时还有一个共享的主内存,在多个线程中如果需要读取同一个变量的值的时候,它们都会去主内存里面将该变量的值加载到自己的工作内存中,然后才可以使用那个值,这样可以提高代码的执行效率,但是各个线程在修改该变量的值的时候,因为只修改自己工作内存中的数据,而别的线程不知道修改后的结果,导致程序执行的结果与预期的结果不一致,这就是可见性问题。
这个时候就需要volatile了,一旦某个变量被volatile修饰的话,只要某个线程修改了data的值,就会在修改问自己本地内存的data的值后,强制将这个data变量最新的值刷回主内存,让主内存李data的值变成最新的值;如果此时别的线程的工作内存中也有这个变量的值,那么强制会让它失效,不允许再次读取和使用了;如果别的线程需要再次使用该变量,那么必须去主内存中重新加载该变量最新的值。
-
volatile的主要作用是保证可见性及有序性,但是不能保证原子性。
-
CAS(Compare and Set):
Atomic原子类使用较为频繁,Atomic原子类的底层用的不是传统意义上的锁机制,而是无锁化的CAS机制,通过CAS机制保证多线程修改一个数值的安全性(保证了操作的原子性)。
AtomicInteger i = new AtomicInteger(0); i.incrementAndGet();
-
原子类执行增加的过程
incrementAndGet,先先获取到AtomicInteger类原来的值,在执行增加之前先检查当前的值是否和之前获取的值一致,一致的时候才执行增加操作,不一致的话,就需要重新获取最新的值,再次执行增加的操作。
-
在大量线程并发更新AtomicInteger的时候,会导致大量的空循环,自旋转,性能和效率都不是很好。
-
Java8推出了一个新的类,LongAdder,它就是尝试使用分段CAS和自动分段迁移的方式来大幅度提升多线程高并发执行CAS操作的性能。
-
在LongAdder的底层实现中,首先有一个base值,刚开始多线程来不停累加数值,都是对base进行累加的,比如刚开始累加成了5,接着如果发现并发更新的线程数量过多,就会开始实行分段CAS的机制,也就是内部会搞一个cell数组,每个数组是一个数值分段。这时,让大量的线程去对不同的cell内部的value值累加操作,这样就把CAS计算压力分散到了不同的cell上面,可以大幅度的降低多线程并发更新同一个数值时出现的无限循环的问题,大幅度提升了多线程并发更新数值的性能和效率。而且它内部实现了自动分段迁移的机制,也就是如果某个cell的值执行CAS失败了,那么就会自动去找另外一个cell分段内的value执行CAS操作。最后,如果要从LongAdder中获取当前累加的值,就会把base值和所有cell分段数值加起来,返回给你。
-
AQS:Java并发包下很多API都是基于AQS来实现加锁和释放锁等功能的,AQS时候java并发包的基础类。比如ReentrantLock、ReentrantReadWriteLock底层都是基于AQS实现的。AQS的全程是AbstractQueuedSynchronizer,抽象队列同步器。
-
ReentrantLock内部包含了一个AQS对象,也就是AbstractQueuedSynchronizer对象,这个AQS对象就是ReentrantLock可以实现加锁和释放锁的关键性核心部件。
-
AQS对象内部有一个核心的变量叫做state,int类型,代表了加锁的状态,默认是0;还有一个关键变量用来记录当前加锁的是哪个线程,默认为null。
-
某个线程调用ReentrantLock的lock方法的时候,加锁的过程直接就是CAS操作,将state值从0变成1,加锁成功之后就可以将当前加锁线程设置成自己。如果再来一个线程尝试加锁的时候,首先查看state值,发现值不为0,在查看加锁线程发现也不是自己,此时就会加锁失败。截止第二个线程会将自己放入AQS中的一个等待队列,等第一个线程释放锁后,自己就可以尝试重新加锁了。线程1释放锁的过程也非常简单,就是将AQS内的state值减1,如果state值为0,则彻底释放锁,会将加锁线程变量设置为null。
-
会发现ReentrantLock是一个外层的API,内部的锁机制都是依赖AQS组建的,之所以以Reentrant打头,是因为它是一个可重入锁,就是可以对一个ReentrantLock对象多次执行lock和unlock,也就是对一个锁加多次。
-
每次线程1可重入加锁一次,state值加1,别的没有变化。
-
非公平锁:后面来的线程在前面的线程释放锁后,不管等待队列中是否有别的线程在等待,直接尝试加锁的操作,会导致等待的线程无法及时获得锁。在非公平锁之下,不一定先来排队的线程就会先等到加锁的机会,而是出现各种线程随意抢占的情况。ReentrantLock默认的策略就是非公平的,如果要实现公平锁,在构造ReentrantLock的时候传入一个true即可,如下:
ReentrantLock lock = new ReentrantLock(true);
-
公平锁:当一个线程尝试加速的时候,回先判断锁是否被占用;如果已经被占用,则再次检查等待队列中是否有已经等待的线程,有的话就会把自己放在等待队列的后面,等前面的线程都执行完之后,才会去尝试加锁。
-
读写锁:将一个锁拆分为读锁和写锁两个锁,在加锁的时候可以加读锁,也可以加写锁。代码如下:
ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); lock.readLock().lock(); lock.readLock().unlock(); lock.writeLock().lock(); lock.writeLock().unlock();
- 如果一个线程加了写锁,那么别的线程就不能加写锁了,同一时间只能允许一个线程加写锁
- 如果一个线程加了写锁,那么别的线程就不能加读锁了
- 如果一个线程加了读锁,那么别的线程可以随意的加读锁
- 如果一个线程加了读锁,那么别的线程不能加写锁
并发编程的基础类
最新推荐文章于 2022-07-08 13:23:53 发布