一、synchronized前置知识
- 偏向锁→轻量锁(cas自旋)→重量级锁
- 重入锁概念/悲观锁与乐观锁
- 基于CAS手写-重入锁
- 基于CAS手写类似synchronized锁的升级过程
补充概念:
偏向锁: 同一个线程在没有其他线程竞争锁的情况下,可以一直复用我们的锁,不会重复获取锁。
轻量锁: 多个线程同时获取锁,只有一个线程获取锁,没有获取到锁的线程会通过CAS不断重试(可以配置重试次数)
重量级锁: 重试多次如果没有获取到锁,则当前线程会变成阻塞—-用户态
切换到内核态/上下文切换
CAS
优点:不需要用户态切换内核态 一直在我们用户空间中自旋。
缺点: 有可能会非常的消耗到cpu资源。
二、重入锁
重入锁:当前线程如果已经获取到锁,则不会重复的获取锁,而是直接复用。
我们的synchronized/lock
锁如果不具有重入性,当前线程递归调用方法中有可能会发生死锁的问题。
1. 演示重入锁
public class Test01 {
private static DemoLock demoLock = new DemoLock();
public static void main(String[] args) {
a();
}
// public static synchronized void a() {
// System.out.println("a");
// b();
// }
//
// public static synchronized void b() {
// System.out.println("b");
// c();
// }
//
// public static synchronized void c() {
// System.out.println("c");
// }
public static void a() {
demoLock.lock();
System.out.println("a");
b();
demoLock.lock();
}
public static void b() {
demoLock.lock();
System.out.println("b");
c();
demoLock.lock();
}
public static void c() {
demoLock.lock();
System.out.println("c");
demoLock.lock();
}
}
2. 改造重入锁代码
public class DemoLock {
/**
* lockState===0没有任何线程获取到该锁
* lockState===1已经有线程获取到该锁
*/
private AtomicInteger lockState = new AtomicInteger(0);
/**
* 当前谁持有了该锁
*/
private Thread ownerLockThread;
/**
* 记录锁的重入次数
*/
private int recursions;
/**
*
*/
public void lock() {
if (ownerLockThread != null && ownerLockThread == Thread.currentThread()) {
// 重入次数+1
recursions++;
return;
}
/**
* 两个线程同时执行 cas 操作 将锁的状态从0改成===11
* 最终只有一个线程修改成功 自旋
*/
for (; ; ) {
if (lockState.compareAndSet(0, 1)) {
ownerLockThread = Thread.currentThread();
recursions++;
return;
}
// 获取失败 重试获取锁
}
}
public void unLock() throws Exception {
/**
* 如果不是当前线程获取的锁 无法释放锁
*/
if (ownerLockThread != Thread.currentThread()) {
throw new Exception("当前线程没有获取到锁无法释放锁");
}
recursions--;
if (recursions == 0) {
for (; ; ) {
// 释放锁 需要通过 cas 将 11 == 变成0
if (lockState.compareAndSet(1, 0)) {
return;
}
}
}
}
public static void main(String[] args) throws InterruptedException {
DemoLock demoLock = new DemoLock();
// 当前主线程调用lock方法
demoLock.lock();
demoLock.lock();
}
}
三、轻量级改造重量级锁
Lock锁属于轻量级吗?
Lock 执行cas 竞争 —-属于量级锁 如果多次获取锁(自旋失败),为了避免消耗cpu的,将该线程变为阻塞状态,就会变为重量级锁。
应用场景:
public class DemoLock {
/**
* lockState===0没有任何线程获取到该锁
* lockState===1已经有线程获取到该锁
*/
private AtomicInteger lockState = new AtomicInteger(0);
/**
* 当前谁持有了该锁
*/
private Thread ownerLockThread;
/**
* 记录锁的重入次数
*/
private int recursions;
/**
* 没有获取到锁的线程
*/
private LinkedBlockingDeque<Thread> notLockThreadList = new LinkedBlockingDeque<>();
/**
*
*/
public void lock() {
if (ownerLockThread != null && ownerLockThread == Thread.currentThread()) {
// 重入次数+1
recursions++;
return;
}
/**
* 两个线程同时执行 cas 操作 将锁的状态从0改成===11
* 最终只有一个线程修改成功 自旋
*/
int spinCount = 0;
for (; ; ) {
// 如果自旋次数超过10次+
if (spinCount > 10) {
// 当前线程直接阻塞 该过程为重量级锁
notLockThreadList.offer(Thread.currentThread());
LockSupport.park();
}
if (lockState.compareAndSet(0, 1)) {
ownerLockThread = Thread.currentThread();
recursions++;
return;
}
// 自旋次数+1
spinCount++;
}
}
public void unLock() throws Exception {
/**
* 如果不是当前线程获取的锁 无法释放锁
*/
if (ownerLockThread != Thread.currentThread()) {
throw new Exception("当前线程没有获取到锁无法释放锁");
}
recursions--;
if (recursions == 0) {
for (; ; ) {
// 释放锁 需要通过 cas 将 11 == 变成0
if (lockState.compareAndSet(1, 0)) {
// 唤醒正在阻塞的线程, 进入到获取锁的状态
notifyNotLockThread();
return;
}
}
}
}
private void notifyNotLockThread() {
/**
* 唤醒所有的线程 非公平锁的形式
*/
notLockThreadList.forEach((t) -> {
LockSupport.unpark(t);
});
}
public static void main(String[] args) throws Exception {
cal();
}
public static void cal() throws Exception {
DemoLock demoLock = new DemoLock();
try {
demoLock.lock();
new Thread(() -> {
System.out.println("子线程获取锁开始>");
demoLock.lock();
System.out.println("子线程获取锁结束>");
}).start();
System.out.println("模拟主线程正在处理逻辑开始");
Thread.sleep(500);
System.out.println("模拟主线程正在处理逻辑结束");
} catch (Exception e) {
} finally {
demoLock.unLock();
}
}
}
四、公平锁与非公平锁
公平锁:就是比较公平,根据请求锁的顺序排列,先来请求的就先获取锁,后来获取锁就最后获取到, 采用队列存放 类似于吃饭排队。
非公平锁:不是根据根据请求的顺序排列, 通过争抢的方式获取锁。
比如当前 abcd 四个线程,假设a线程获取到锁,在释放锁时:
- 公平锁:
根据bcd 请求锁的顺序排列获取锁,B先获取锁那这把锁就给我们的线程B。 - 非公平锁:
bcd 同时竞争这把锁;谁能够抢成功 谁就获取锁成功。
New ReentramtLock()(true)—公平锁
New ReentramtLock()(false)—非公平锁
非公平锁效率比公平锁效率要高。Synchronized是非公平锁
public class Thread002 implements Runnable {
private static int count = 0;
private static Lock lock = new ReentrantLock(true);
@Override
public void run() {
while (count < 200) {
// try {
// Thread.sleep(100);
// } catch (Exception e) {
//
// }
// lock.lock();
// System.out.println(Thread.currentThread().getName() + ",count:" + count);
// count++;
createCount();
// lock.unlock();
}
}
public synchronized void createCount() {
System.out.println(Thread.currentThread().getName() + ",count:" + count);
count++;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(new DemoLock()).start();
}
}
}
五、偏向锁/轻量级锁/重量级锁应用场景
Synchronized 锁的升级过程
t1 线程 获取到锁 1毫秒就释放锁,t1线程1毫秒之后又唤醒t2线程竞争锁资源
t2竞争锁资源 阻塞—就绪—cpu调度—竞争锁资源
锁的升级过程。
t1 线程获取到锁之后 60s 才会释放锁,t2线程自旋60s t2直接阻塞就可以–
1.偏向锁:加锁和解锁不需要额外的开销,只适合于同一个线程访问同步代码块,无需额外的开销,如果多个线程同时竞争的时候,会撤销该锁。
2.轻量级锁:竞争的线程不会阻塞,提高了程序响应速度,如果始终得不到锁的竞争线程,则使用自旋的形式,消耗cpu资源,适合于同步代码块执行非常快的情况下,自旋(jdk1.7以后智能自转)
3.重量级锁: 线程的竞争不会使用自旋,不会消耗cpu资源,适合于同步代码执行比较长的时间。
偏向锁与重入锁之间有什么区别?
偏向锁与重入锁实现的思路基本上相同
偏向锁:当前只有一个线程的情况下,没有其他的线程竞争锁,该锁一直会被我们的该线程持有,不会立即释放锁;有另外的一个线程竞争该锁,就会撤销我们的偏向锁 线程id
重入锁:当前线程如果已经获取到了锁,当前线程中的方法如果有需要获取锁的话,则直接复用。
释放锁,判断重入的次数,如果为0, cas 改状态(1,0)
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/131218.html