线程扩展
常用专业术语:CAS(乐观锁的实现),ABA,
乐观锁:
认为一般情况下不会发生并发冲突,只有在进行数据更新的时候,才会检测并发冲突,若无冲突则直接修改,有冲突返回失败
java的乐观锁采用CAS机制实现。
CAS机制
java的CAS利用unsafe实现的,依赖的是jvm针对不同操作系统的Atomic::cmpxchg原子性指令,使用了汇编的CAS操作,并使用cpu硬件提供的lock机制保证其原子性。
CAS机制-Compare and swap,比较并交换,
操作:假设内存中原数据为v,旧的预期值A,需要修改的新值B,比较A和v是否相等, 如果相等,将B写入v–交换,返回是否成功,
CAS的缺点:ABA问题,新值和预期旧值的问题,
ABA问题:当前数据v为100,经过线程A的操作之后编程了0;此时又有线程C进行操作变成了100,而线程B需要对线程A操作之前的100进行操作,但其执行排在线程C之后了,对C之后的100进行了操作,此时出现了数据出现了错误的操作。
代码操作如下:
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
//ABA问题
public class Demo_0527_2 {
private static AtomicReference money = new AtomicReference(100);
// private static AtomicStampedReference money = new AtomicStampedReference(100,0);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
boolean result = money.compareAndSet(100,0);
System.out.println("第一次转账"+result);
}
});
t1.start();
t1.join();
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
boolean result = money.compareAndSet(0,100);
System.out.println("入账100"+result);
}
});
t3.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
boolean result = money.compareAndSet(100,0);
System.out.println("第二次转账"+result);
}
});
t2.start();
}
}
上述代码模拟转账的情况,第一次线程1转账太慢,发起了线程2进行转账,而中间又因为线程3转入了100,在线程1和3执行完成之后,线程2本不应该继续执行,但CAS机制只识别当前的值与要操作的值是否相等,实际上已经不是之前的值,因此出现了重复转账。
执行结果:
第一次转账true
入账100true
第二次转账true
ABA解决方法
ABA主要由于对比值相等进行改变,不能区分数据状态,只需要引入版本号即可,每次操作完成后,版本号+1,这时就可以根据版本号判别当前数据是否被修改过。
AtomicStampedReference(初始值,版本号);解决ABA问题;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
//ABA问题
public class Demo_0527_2 {
// private static AtomicReference money = new AtomicReference(100);
private static AtomicStampedReference money = new AtomicStampedReference(100,0);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
boolean result = money.compareAndSet(100,0,0,1);
System.out.println("第一次转账"+result);
}
});
t1.start();
t1.join();
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
boolean result = money.compareAndSet(0,100,1,2);
System.out.println("入账100"+result);
}
});
t3.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
boolean result = money.compareAndSet(100,0,1,2);
System.out.println("第二次转账"+result);
}
});
t2.start();
}
}
执行结果
第一次转账true
入账100true
第二次转账false
如果将初始值设为1000,执行结果为
第一次转账false
入账100false
第二次转账false
** 原因** Integer的高速缓存-128-127;进行compare的时候,对比的是引用,而不是值,只有一个缓存对象,超过范围就会自动创建新的变量,导致对像改变。
调整Integer高速缓存的边界值,将vm option 修改为Djava.lang.high=1000;
悲观锁
认为一定会出现并发冲突,因此在进入代码后就进行加锁,
synchronized属于悲观锁,
共享锁-非共享锁:
共享锁指的是一个锁可以被多个程序拥有,非共享锁只能被一个线程拥有;
synchronized是非共享的
读写锁
属于共享锁,就是将锁分为两部分,读锁和写锁,读锁可以被多个线程拥有,而写锁只能被一个线程拥有,ReenttrantReaderLocker;
读写锁的优点是锁的粒度更高,性能更高。
读写锁中的读锁和写锁是互斥的;防止在读写同时进行造成脏数据的问题,
import java.util.Date;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;
//读写锁
public class Demo_0527_3 {
public static void main(String[] args) {
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);//true表示公平和非公平;默认非公平
ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
ThreadPoolExecutor executor = new ThreadPoolExecutor(10,10, 10,TimeUnit.SECONDS,new LinkedBlockingDeque<>(1000));
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();
}
}
});
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();
}
}
});
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();
}
}
});
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);
非公平:
synchronized new ReentrantLock(false);
自旋锁:
通过死循环一直尝试获取锁;
缺点:线程在枪锁失败后进入阻塞,只要没抢到锁,就进行死等,如果锁没有很快被释放,线程就会一直消耗CPU的资源
while(true){
if(获得锁)return;
}
可重入锁:
允许同一个线程多次获得同一把锁;
public class Demo_0527_4 {
private static Object lock = new Object();
public static void main(String[] args) {
synchronized (lock){
System.out.println("第一次进入锁");
synchronized (lock){
System.out.println("第二次进入");
}
}
}
}
面试题:
怎么理解乐观悲观锁?怎么实现?
答:1.乐观锁->CAS->Atomic*,CAS有v内存,A预期值,B准备写入的新值,
使用A和v对比,如果结果为true,表示无并发冲突,可以直接修改v为B,否则不修改,
java通过调用C++实现的unsafe中的本地方法Compare And Swap,C++通过调用操作系统中的Atomic中的原子指令,
2.悲观锁:synchronized在java中是将锁的ID放到对象头中实现的,在JVM是利用monitor实现,在操作系统中是通过互斥锁mutex实现的,
synchronized锁优化:
JDk1.6锁升级的过程,1.无锁,2.偏向锁,3.轻量级锁。4重量级锁,