锁的分类
- 公平锁与非公平锁
- 独占锁与共享锁
- 悲观锁与乐观锁
- 自旋锁与可重入锁
- 偏向锁与轻量级锁与重量级锁
悲观锁与乐观锁
悲观锁:当线程访问一个临界资源时,不管它是否还会不会被其它线程访问,会首先进行上锁,当任务执行完再释放锁。没有获取到锁的线程会进入到阻塞等待状态,阻塞后获取到临界资源则进入就绪状态,在cpu调度后则进入运行状态。线程阻塞再到唤醒运行成本较高。
乐观锁:当线程访问一个临界资源时,先假设其是空闲的,当遇到有资源竞争时,通过自旋轮询目标状态,并在空闲时进行访问。乐观锁属于无锁机制,没有竞争锁的流程,涉及线程也不会阻塞等待。
公平锁与非公平锁
公平锁:按线程请求临界资源的先来后到顺序分配锁,采用队列实现。
- 优点:所有线程能够按请求顺序得到资源,不会饿死在队列中。
- 缺点:个别任务执行较长时,会导致较多的任务阻塞,由此带来更多的任务阻塞唤醒的cpu开销。
非公平锁:不是按照请求顺序来分配锁,而是通过资源竞争的方式来获取锁。
- 优点:增加任务吞叶量,减少对线程的阻塞唤醒开销。非公平锁通常比公平锁效率高。
- 缺点:可能导致某些线程始终得不到执行,导致饿死。
独占锁与共享锁
独占锁:只允许一个线程访问临界资源,其它的线程都会阻塞等待。
共享锁:多个线程同时持有锁。如ReentrantReadWirteLock,读读共享、读写互斥、写入互斥、写读互斥。
自旋锁与可重入锁
自旋锁:没有获取到锁的线程不阻塞,而是通过CAS(Compare and swap)循环控制不断尝试的获取锁。自旋锁为不可重入锁,同一线程需要获取锁也是需要自旋等待。
- CAS参数(a,expectValue,newValue): a是目标内存地址,expectValue为要修改的原值,newValue为目标值。表示把目标内存地址的值修改为newValue值,而expectValue主要用于验证,a地址的原始值是否是expectValue.
- ABA问题:ABA问题,是指一个线程进行两次锁操作过程中,有另外一个或多个线程将值改变之后又改回去了。中间锁的过程发生了变化,在并发中可能出现问题。虽然目标值是一样,但是中间间隙已经发生了很多操作,导致结果状态异常。
- 解决ABA问题:原子引用
AtomicReference会存在ABA问题,可用带版本的原子引用AtomicStampedReference解决ABA问题。可根据业务需求选择。原理就是增加一个版本号进行控制,在值与版本号一致的情况下才认为是一样的。
// 新建一个原子引用,用于多个线程在抢占资源时,做锁操作。
AtomicReference<Thread> threadAtomicReference = new AtomicReference<>();
// 获取锁
threadAtomicReference.compareAndSet(null,thread1);
// 释放锁
threadAtomicReference.compareAndSet(thread1,null);
threadAtomicReference.compareAndSet(null,thread2);
threadAtomicReference.compareAndSet(thread2,null);
可重入锁:同一个线程中,有多个同步方法都需要同一把锁,不需要重复获取,可直接进行。可重入锁机制主要用于避免死锁,即线程自己等待自己的情况。
偏向锁与轻量级锁与重量级锁
Synchronized锁(内置锁)升级过程中的锁的不同状态。锁升级是不可逆的过程。
锁是针对对象而言,锁住的是一个对象,表现在对象头中。对象头中有_mark(markword)信息区,markword(64位or32位),其中锁标志位占两位,其余为其它信息。
- 线程访问一个同步资源,会首先查看对象头信息,检查锁标志位。
- 锁标志位(01)-->是否偏向锁--->0为无锁,1为偏向锁
- 当有一个线程访问对象时,升级为偏向锁并记录线程ID(主要用于当线程重入时,不用再获取锁).
- 有其它线程来竞争访问该对象或同步块时,锁升级为轻量级锁。
- 锁标志位(00)-->竞争线程不会阻塞,进入自旋尝试获取锁,访问资源。
- 线程栈中分配锁记录,拷贝对象头中的markword到原持有偏向锁的线程栈中,将对象中轻量级锁指向原持有偏向锁的线程的锁记录
- 竞争线程也拷贝对象头中的markword到线程栈中的锁记录,并尝试自旋获取锁,将对象中的轻量级锁指向线程栈中的锁记录。
- 锁膨胀,自旋一定次数(如10次)获取锁失败则升级为重量级锁
- 锁标志位(11)--> 重量级锁(monitor),在jdk1.6之前,可以直接认为对应到操作系统中的互斥量(mutex).这种同步成本较高,包括系统调用引起的内核态与用户态切换、线程阻塞造成的线程切换等,因些后来被称为重量级锁。
- 锁释放,各线程执行完了,同步块又变成无锁状态。(这大概是从锁记录中判断,并释放对象锁)
参考: www.cnblogs.com/mingyueyy/p… www.jianshu.com/p/36eedeb3f…
有哪些锁
- LockSupport
- Semaphore
- Lock
- ReadLock
- WriteLock
- ReadLockView
- WriteLockView
- ReentrantLock
- ReadWriteLock
- ReadWriteLockView
- ReentrantReadWriteLock
- Synchronized
其它
AtomicStampedReference的简单用例
public class AtomicMarkableReferenceTest {
private static final Integer UPDATE_NUM = 100;
private static AtomicStampedReference<Integer> atomicStampedReference=
new AtomicStampedReference<>(127,1);
public static void main(String[] args) {
new Thread(()->{
Integer value = atomicStampedReference.getReference();
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + " : 当前值为:" + value + " 版本号为:" +
stamp);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace(); }
// 注意:如果引用类型是 Long、Integer、Short、Byte、Character 一定一定要注意值的缓存区间!
// 比如 Long、Integer、Short、Byte 缓存区间是在-128~127,会直接存在常量池中,而不在这个区间内对象的值
// 则会每次都 new 一个对象,那么即使两个对象的值相同,CAS 方法都会返回 false
// 所以注意,这里expectedReference最好使用引用value,避免值不在缓存区间中,而导致新建的对象与原对象不匹配。(地址不一样)
if(atomicStampedReference.compareAndSet(127,UPDATE_NUM,1,stamp+1)){
System.out.println(Thread.currentThread().getName()+"当前值:"
+atomicStampedReference.getReference()+"版本号:"+atomicStampedReference.getStamp());
}else {
System.out.println("版本号不同更新失败");
}
},"线程A").start();
}
}
原子类实现CAS无锁同步机制
public class AtomicTryLock {
private AtomicLong atomicLong= new AtomicLong(0);
private Thread lockCurrentThread;
public boolean lock(){
// 自旋,尝试获取锁
for(;;){
boolean b = atomicLong.compareAndSet(0, 1);
if(b){
lockCurrentThread=Thread.currentThread();
return true;
}
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public boolean unlock(){
if(lockCurrentThread!=Thread.currentThread()){
return false;
}
return atomicLong.compareAndSet(1,0);
}
public static void main(String[] args) {
AtomicTryLock atomicTryLock = new AtomicTryLock();
IntStream.range(1, 5).forEach(i->new Thread(()->{
try {
boolean lock = atomicTryLock.lock();
if(lock){
System.out.println(Thread.currentThread().getName()+"获取锁成功");
}else{
System.out.println(Thread.currentThread().getName()+"获取锁失败");
}
}catch (Exception e){
}finally {
atomicTryLock.unlock();
}
}).start());
}
}