Java锁
乐观锁:
乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重复读-比较-写操作。
Java中的乐观锁基本都是通过CAS操作实现的,CAS是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败。
CAS:
CAS(Compare And Swap/Set)比较并交换,CAS算法的过程是这样:它包含三个参数CAS(V,E,N) ,V表示需要更新的变量(内存值),E表示预期值(旧的),N表示新值。当且仅当V值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。最后, CAS返回当前V的真实值。
CAS操作时抱着乐观的态度进行的(乐观锁),它总是认为自己可以成功完成操作。当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均会失败。失败的线程不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。基于这样的原理,CAS操作即使没有锁,也可以发现其他线程对当前线程的干扰,并进行恰当的处理。
ABA问题:
CAS会导致“ABA问题”。CAS算法实现一个重要前提需要取出内存中某时刻的数据,而在下时刻比较和替换,那么在这个时间差类会导致数据的变化。
比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出
A,并且two进行了一下操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然时A,然后one操作成功,尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。
部分乐观锁的实现是通过版本号(version)的方式来解决ABA问题,乐观锁每次在执行数据修改的操作时,都会带上一个版本号,一旦版本号和数据的版本号一致就可以执行修改操作并对版本号执行+1操作,否则就执行失败。因为每次操作的版本号都会随之增加,所以不会出现ABA问题,因为版本号只会增加不会减少。
AQS
AbstractQueuedSynchronizer(抽象队列式同步器),AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,(它用了模板方法设计模式)如常用的ReentrantLock/Semaphore/CountDownLatch
悲观锁
悲观锁就是悲观思想,即认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改,所以每次在读写数据的时候都会上锁,这样别人想读写这个数据就会被block直到拿到锁。Java中的悲观锁就是Synchronized,AQS框架下的锁则是先尝试CAS乐观锁去获取锁,获取不到,才会转换为悲观锁,如ReentrantLock
自旋锁
自旋锁原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待
竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋)等待有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。
线程自旋时需要消耗CPU的,说白了就是让CPU在做无用功,如果一直获取不到锁,
那线程也不能一直占用CPU自旋做无用功,所以需要设定一个自旋等待的最大时间。
如果持有锁的线程执行时间超过自旋等待的最大时间仍没有释放锁,就会导致其它争用锁的线程在最大时间内还是获取不到锁,这时争用线程会停止自旋进入阻塞状态。
自旋锁的优缺点
自旋锁尽可能的减少线程的阻塞,这对于锁的竞争不激烈,且占用锁时间非常短的代码来说性能有大幅度的提升,因为自旋的消耗会小于线程阻塞挂起再唤醒的操作的消耗,这些操作会导致线程发生两次上下文切换!
但是如果锁的竞争激烈,或者持有锁的线程需要更长时间占用锁执行同步块,这时候就不适合使用自旋锁了,因为自旋锁再获取锁前一直都是占用cpu做无用功,同时有大量线程在竞争一个锁,会导致获取锁的时间很长,线程自旋的消耗大于线程阻塞挂起操作的消耗,其它需要cpu的线程又不能获取到cpu,造成cpu的浪费。所以这种情况下我们要关闭自旋锁。
Synchronized同步锁
synchronized它可以把任意一个非NULL的对象当作锁。他属于独占式的悲观锁,同时属于可重入锁。
Synchronized作用范围
作用于方法时,锁住的是对象的实例(this);
当作用域静态方法时,锁住的时Class实例,又因为Class的相关数据存储在永久代PermGen(jdk1.8则是metaspace),永久代时全局共享的,因此静态方法锁相当于类的一个全局锁,会锁所有调用该方法的线程;
synchronized作用于一个对象实例时,锁住的是所有以该对象为锁的代码块,它有多个队列,当多个线程一起访问某个对象监视器的时候,对象监视器会将这些线程存储在不同的容器中。
ReentrantLock
ReentrantLock继承接口Lock并实现了接口中定义的方法,它是一种可重入锁,除了能完成Synchronized锁能完成的所有工作外,还提供了诸如可响应中断锁、可轮询锁请求、定时锁等避免多线程死锁的方法。
ReentrantLock与synchronized
ReetrantLock通过方法lock()与unlock()来进行
标签:Java,CAS,版本号,线程,自旋,操作 From: https://www.cnblogs.com/doremi429/p/16842461.html