(1)乐观锁VS悲观锁
1》乐观锁:
他认为一般情况下不会出现问题,所有他在使用的时候不会加锁,只有在数据修改的时候才会判断有没有锁竞争,如果没有就会直接修改数据,如果有则会返回失败信息给用户自行处理。
乐观锁的经典事项:CAS(Compare And Swap)对比并且替换
- 1)CAS实现
(V【内存中的值】,A【预期的旧值】,B【新值】)
每次把V和A进行对比,如果V==A则将V修改为B,如果不相等,则不修改。进行自旋对比和替换。
- 2)CAS实现原理是什么?
public final native boolean compareAndSwapObject(Object var1,long var2,Object var3)
CAS在java中是通过UnSafe实现,UnSafe是本地类和本地方法,它是C/C++实现的原生方法,通过调用操作系统的Atomic::cmpxchg(原子指令)来实现的。
- 3)CAS的应用:Atomic*
private static AtomicInteger count = new AtomicInteger(0);
- 4)在多线程中实现i++,i–保证线程安全的方法:
1.加锁
2.ThreadLocal
3.AtomicInteger
- 5)CAS(乐观锁)存在的问题:ABA问题
- ABA问题:
(1)在我给别人转账100元的情况下点击了两次提交的按钮(V=100,A=100,B=0)
第一次提交时V=100,A=100,V=A则V=B=0; 第二次提交时V=0,A=100 ,V!=A,停止执行,这种情况下是正常的。
(2)但是当我在第二次提交的时候,有人在我第二次提交前给我也转了100元。 第一次提交V=100,A=100,V=A则V=B=0;
在中间是别人给我转钱V=100 第二次提交V=100,A=100,则V==A,V=B=0,此时又进行了一次转账。
这个就是ABA问题在经历了一段时间一定事件后A又变成A。
- 6)如何解决ABA问题?
使用版本号(AtomicStampedReference),每次修改的时候判断预期的旧值和版本号,每次成功修改之后更换版本号,这样即使预期的值和V值相等,但是因为版本号不同,也不能进行修改,从而解决了ABA问题。
private static AtomicStampedReference money = new AtomicStampedReference(100,1);
public static void main(String[] args) throws InterruptedException {
//两次转账
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
boolean resule1= money.compareAndSet(100,0,1,2);//旧值,新值,旧版本号,新版本号
System.out.println(resule1);//true
}
});
thread.start();
//增加100
Thread thread3= new Thread(new Runnable() {
@Override
public void run() {
boolean resule2= money.compareAndSet(0,100,2,3);
System.out.println(resule2);//true
}
});
thread3.start();
thread.join();
thread3.join();
Thread thread2= new Thread(new Runnable() {
@Override
public void run() {
boolean resule2= money.compareAndSet(100,0,1,2);
System.out.println(resule2);//false
}
});
thread2.start();
thread2.join();
}
注意事项:AtomicReference有ABA问题AtomicStampedReference是解决ABA问题的.AtomicStampedReference它对比里面的旧值对比的是引用。
这里就有一个高速缓存机制,当取的值是范围里面的时,会直接按值对比,超过此范围就会对比地址。Integer(-128–127)
2》悲观锁
悲观锁是认为只要执行多线程就会出现问题,所以在进入方法后都会进行加锁。 悲观锁的实现就是synchronize。
(2)公平锁VS非公平锁:
公平锁:1;一个线程释放锁,2;主动唤醒需要得到锁的线程。
非公平锁:抢占式执行,当一个线程释放锁,另一个线程刚好执行到获取锁的代码就可以获取到锁。 使用new
ReentrantLock(true)来设置公平锁,默认是false,非公平锁,synchronize是非公平锁
(3)独占锁VS共享锁
独占锁:指的是这一把锁只能被这一个线程拥有。(synchronize)
共享锁:指的是这把锁可以被多个线程同时拥有。(ReadWriteLock中的读锁就是共享锁)将锁的粒度更加细化,从而提高锁的性能。
(4)可重入锁:
一个线程在拥有了一把锁后,可以重复的进入(synchronize,ReentrantLock)
private static Object object = new Object();
public static void main(String[] args) {
synchronized (object){
System.out.println("进入主方法");
synchronized (object){
System.out.println("重复进入方法");
}
}
}
(5)自旋锁:
相当于死循环,一直循环尝试获取锁(synchronize)
(6)偏向锁:
在线程初次访问的时候,将线程的ID放入对象头偏向锁ID的字段中,每次线程访问的时候先判断线程的ID是否等于对象投中的ID,如果相等则说明这个线程用于此锁就可以正常执行代码,否则表明线程不拥有此锁,只能通过自旋的方式尝试