- 乐观锁:乐观锁假设认为数据一般情况下不会产生并发冲突,所以在数据进行提交更新的时候,才会正式对数据是否产生并发冲突进行检测,如果发现并发冲突了,则返回用户错误的信息,让用户决定如何去做。
- CAS(乐观锁): 全称Compare And Swap,字面意思:“比较并交换"
线程不安全的解决方案:
1.加锁
2.ThreadLocal
3.Atomic*(乐观锁的实现)
/**
* Atomic*乐观锁实现
*/
public class ThreadDemo91 {
private static AtomicInteger count = new AtomicInteger(0);
//最大循环次数
private static final int MAXSIZE = 100000;
public static void main(String[] args) throws InterruptedException {
//i++
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < MAXSIZE ; i++) {
//count++;
count.getAndIncrement();
}
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < MAXSIZE ; i++) {
//count--;
count.getAndDecrement();//i--;
}
}
});
t2.start();
t1.join();
t2.join();
System.out.println("结果"+count);
}
题:CAS底层实现原理?
答:Java层面CAS的实现是unsafe类,unsafe类调用了C++的本地方法,通过调用操作系统的Atomic::cmpxchg(原子指令)来实现CAS操作的。
乐观锁性能比较高
CAS缺点:
存在ABA问题:AtomicInteger存在ABA问题(A预期旧值,B表示新值)
ABA问题统一的解决方案:增加版本号,每次修改之后更新版本号。
题: AtomicStampedReference和AtomicReference的区别:
答:AtomicStampedReference没有ABA问题 AtomicReference有ABA问题
/**
* ABA问题解决演示
* 核心是增加了版本号
*/
public class ThreadDemo94 {
private static AtomicStampedReference money = new AtomicStampedReference(100,1);
public static void main(String[] args) throws InterruptedException {
//转账线程1(-100)
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
//第一次转账
boolean result = money.compareAndSet(100,0,1,2);
System.out.println("第一次转账(-100)"+result);
}
});
t1.start();
t1.join();
//转入100
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
boolean result = money.compareAndSet(0,100,2,3);
System.out.println("转入100(+100)"+result);
}
});
t3.start();
t3.join();
//转账线程2(-100)
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
boolean result = money.compareAndSet(100,0,1,2);
System.out.println("第二次转账(-100)"+result);
}
});
t2.start();
}
}
Integer高速缓存范围-128—127
设置应用程序的参数(-D),设置Integer高速缓存的最大值:
-Djava.lang.Integer.IntegerCache.high=1000
解决方案:调整integer的高速缓存边界值
悲观锁:认为通常情况下会出现并发冲突,所以在一开始就会进行加锁。 包含:synchronized
共享锁和非共享锁:
共享锁:一把锁可以被多个线程所拥有
非共享锁(独占锁):一把锁只能被一个线程所拥有,synchronized
读写锁中的读锁就是共享锁
读写锁:将一把锁分成两个,一个用于读数据的锁(也叫做读锁)另一把所叫做写锁,读锁是可以被多个线程同时拥有的,写锁只能被一个线程拥有读写锁的具体实现:ReentrantReadWriteLock
读写锁的优势:锁的粒度更加小,性能也更高
注意:读写锁中读锁写锁是互斥的(防止同时读写所产生的脏数据
* 读写锁演示
*/
public class ThreadDemo96 {
public static void main(String[] args) {
//创建一个读写锁
ReentrantReadWriteLock readWriteLock =
new ReentrantReadWriteLock();//默认是非公平的
//读锁
ReentrantReadWriteLock.ReadLock readLock =
readWriteLock.readLock();
//写锁
ReentrantReadWriteLock.WriteLock writeLock =
readWriteLock.writeLock();
//线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(10,10,
0, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000));
//任务1:执行读锁
executor.execute(new Runnable() {
@Override
public void run() {
//加锁
readLock.lock();
try{
//业务逻辑处理
System.out.println("执行了读锁操作"+Thread.currentThread().getName()
+ new Date());
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放锁
readLock.unlock();
}
}
});
//任务2:执行读锁
executor.execute(new Runnable() {
@Override
public void run() {
readLock.lock();
try{
//执行业务逻辑
System.out.println("执行了读锁操作"+Thread.currentThread().getName()+
new Date());
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放锁
readLock.unlock();
}
}
});
//任务3:执行写锁
executor.execute(new Runnable() {
@Override
public void run() {
writeLock.lock();
try{
System.out.println("执行了写锁操作"+Thread.currentThread().getName()+
new Date());
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
writeLock.unlock();
}
}
});
//任务4:执行写锁
executor.execute(new Runnable() {
@Override
public void run() {
writeLock.lock();
try{
System.out.println("执行了写锁操作"+Thread.currentThread().getName()+
new Date());
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
writeLock.unlock();
}
}
});
}
}
公平锁:锁的获取顺序必须和线程方法的先后顺序保持一致 ,
new ReentrantLock(true)
非公平锁:锁的获取顺序和线程获取锁的先后顺序无关(默认锁策略),new ReentrantLock()/ new ReentrantLock(false)/synchronized
非公平锁优点:性能比较高 公平锁优点:执行是有序的,所以结果也是可预期的
- 可重入锁:当一个线程获取一个锁之后,可以重复的进入
* 可重入锁(synchronized)
*/
public class ThreadDemo97 {
//创建锁
private static Object lock = new Object();
public static void main(String[] args) {
//第一次进入锁
synchronized (lock){
System.out.println("第一次进入所");
synchronized (lock){
System.out.println("第2次进入所");
}
题:你是怎么理解乐观锁和悲观锁的,具体怎么实现呢?
答:1.乐观锁——》CAS——》Atomic* CAS是由V(内存值)A(预期旧值)B(新值)组成,执行的时候使用V==A对比,如果结果为true,表明没有并发冲突,可以直接修改,否则不能修改。CAS是通过调用C++实现提供的unsafe中的本地方法(Compare
And SwapXXX)来实现的,c++是通过调用操作系统Atomic::cmpxchg(原子指令)来实现的
2.悲观锁——》synchronized在Java中是将锁的ID存放到对象头来实现的,synchronized在JVM层面是通过监视器锁来实现,synchronized在操作系统层面是通过互斥锁mutex实现。
- 自旋锁:通过死循环一直尝试获取锁
自旋锁的缺点:如果发生死锁则会一直自 旋 (循环),所以会带来一定的额外开销。
synchronized锁优化(锁消除)
JDK1.6锁升级的过程:
(1) 无锁
(2) 偏向锁(第一个线程第一次访问)将线程ID存储在对象头中的偏向锁标识。
(3)轻量级锁(自旋)
(4) 重量级锁