各类锁
乐观锁:读多写少,总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。
悲观锁:写多,总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁。java中的悲观锁就是synchronized,AQS则是先尝试CAS去获取锁,获取不到才转换为悲观锁
公平锁:多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁
非公平锁:多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁
独占锁:独占锁又叫排他锁,是指该锁一次只能被一个线程所持有。当线程T对数据D加上独占锁后,其他线程就不能对D再加任何类型的锁,排他锁既能读也能写。
共享锁:共享锁指的是该锁可被多个线程所持有,如果线程T对数据D加上共享锁后,其他线程就只能对D加共享锁,不能加排他锁,持有共享锁的线程只能读取数据不能写数据。
可重入锁:一个线程在持有一个锁的时候,它内部能再次(多次)申请该锁。可以理解为锁的一个标识。该标识具备计数器功能。标识的初始值为0,表示当前锁没有被任何线程持有。每次线程获得一个可重入锁的时候,该锁的计数器就被加1。每次一个线程释放该锁的时候,该锁的计数器就减1。直到计数器为0,其他的线程才可以再次获取
自旋锁:自旋是一种锁优化机制,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态[1]之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。但自旋会占用cpu资源,一但竞争激烈或者持有锁的线程需要长时间占有锁,将会导致性能下降
[1]线程的调度是在内核态运行的,而线程中的代码是在用户态运行
阻塞锁:让线程进入阻塞状态进行等待,当获得相应的信号(唤醒,时间) 时,才可以进入线程的就绪状态,就绪状态的所有线程,通过竞争,进入运行状态,不占用cpu资源,但切换状态需要的时间比自旋长
偏向锁:偏向于第一个访问锁的线程,如果同步锁只有一个线程在访问,不存在多线程竞争,那么就会给线程加一个偏向锁,如果遇到其他线程抢占锁,那么就会将锁恢复到轻量级锁。
轻量级锁:偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁。轻量级锁使用CAS操作尝试将对象的Mark Word更新为指向Lock Record(位于线程栈中)的指针,如果更新成功当前线程获得锁,如果失败当前线程尝试使用自旋来获取锁,如果自旋达到阈值后还没获取锁就升级为重量级锁。
重量级锁:重量级锁是依赖对象内部的监视器锁(monitor)来实现的,而monitor又依赖操作系统的互斥量(mutex)来实现的,这种同步方式的成本非常高,包括系统调用引起的内核态与用户态切换、线程阻塞造成的线程切换等
CAS操作(Compare-and-swap):比较与替换。被称为无锁优化,主要的三个操作数
V(内存地址) A(旧的预期值) B(新值) 只有V的值和A的值相等时才将V的值修改为B否则失败,
通常会结合自旋来不断重试。CAS操作会产生ABA的问题[1]。
CAS操作是cpu指令上支持的,也就是说是一个原子操作中间不能被打断。
[1]ABA: 线程1准备用CAS将变量的值由A替换为B,在此之前,线程2将变量的值由A替换为C,又由C替换为A,然后线程1执行CAS时发现变量的值仍然为A,所以CAS成功
java底层是调用的Unsafe类下的 compareAndSwapInt
原子类
java.util.concurrent.atomic包下的AtomicInteger、AtomicLong、AtomicBoolean等
AtomicInteger类主要是通过CAS、volatile和Unsafe的native方法来保证原子操作,从而避免Synchronized的高额开销。
对比几种线程安全的自增记数
static long count = 0;
static AtomicLong count2 = new AtomicLong(0);
static LongAdder count3 = new LongAdder();
static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(1000);
// hutool工具类
StopWatch stopWatch = new StopWatch();
// synchronized
CountDownLatch latch = new CountDownLatch(1000);
stopWatch.start("synchronized");
for (int i = 1; i <= 1000; i++) {
executorService.submit(() -> {
for (int j = 1; j <= 10000; j++) {
synchronized (lock){
count++;
}
}
latch.countDown();
});
}
latch.await();
stopWatch.stop();
// atomicLong
CountDownLatch latch2 = new CountDownLatch(1000);
stopWatch.start("atomicLong");
for (int i = 1; i <= 1000; i++) {
executorService.submit(() -> {
for (int j = 1; j <= 10000; j++) {
count2.incrementAndGet();
}
latch2.countDown();
});
}
latch2.await();
stopWatch.stop();
// longAdder
CountDownLatch latch3 = new CountDownLatch(1000);
stopWatch.start("longAdder");
for (int i = 1; i <= 1000; i++) {
executorService.submit(() -> {
for (int j = 1; j <= 10000; j++) {
count3.increment();
}
latch3.countDown();
});
}
latch3.await();
stopWatch.stop();
System.out.println(count + " " + count2 + " " + count3);
System.out.println(stopWatch.prettyPrint());
executorService.shutdownNow();
}
count=10000000 count2=10000000 count3=10000000
StopWatch '': running time = 918428800 ns
---------------------------------------------
ns % Task name
---------------------------------------------
677230400 074% synchronized
106811300 012% atomicLong
134387100 015% longAdder
结果表明在线程数和循环数足够大时longAdder的效率最高,atomicLong次之,synchronized垫底。当减少线程数或循环次数时,longAdder和atomicLong所用的时间差不多,synchronized还是最慢的。
博客介绍了各类锁和原子类。锁包括乐观锁、悲观锁、公平锁等多种类型,详细说明了其特点和实现方式,如乐观锁用版本号机制和CAS算法,悲观锁用synchronized等。还介绍了CAS操作及问题。原子类以AtomicInteger为例,说明其通过CAS等保证原子操作,对比了几种线程安全自增记数效率。
2750

被折叠的 条评论
为什么被折叠?



