【JUC并发编程系列】深入理解Java并发机制:从synchronized到CAS锁升级全过程(三、synchronized 前置知识)

【JUC并发编程系列】深入理解Java并发机制:从synchronized到CAS锁升级全过程(三、synchronized 前置知识)

1. 基本概念

  1. 偏向锁→轻量锁(cas自旋)→重量级锁
  2. 重入锁概念/悲观锁与乐观锁
  3. 基于CAS手写-重入锁
  4. 基于CAS手写类似synchronized锁的升级过程

补充概念:

  • 偏向锁: 同一个线程在没有其他线程竞争锁的情况下,可以一直复用我们的锁,不会重复获取锁。

  • 轻量锁: 多个线程同时获取锁,只有一个线程获取锁,没有获取到锁的线程会通过CAS不断重试(可以配置重试次数)

  • 重量级锁: 重试多次如果没有获取到锁,则当前线程会变成阻塞----用户态切换到内核态/上下文切换

CAS :

  • 优点:不需要用户态切换内核态,一直会在我们用户空间中自旋。

  • 缺点:有可能会非常的消耗到cpu资源(因为要做自旋)。

如何处理解决呢?

  • 控制循环次数

2. 重入锁

重入锁:当前线程如果已经获取到锁,则不会重复的获取锁,而是直接复用。

比如:我们的synchronized/lock

锁如果不具有重入性: 当前线程 递归调用方法中 有可能会发生死锁的问题。

3. 演示重入锁

使用 synchronized

public class Test01 {
    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");
    }
}

输出 abc

使用自己通过CAS实现的锁 不具备可重入性

public class Test02 {
    private static TestLock testLock = new TestLock();

    public static void main(String[] args) {
        a();
    }

    public static synchronized void a() {
        testLock.lock();
        System.out.println("a");
        b();
        testLock.unlock();

    }

    public static synchronized void b() {
        testLock.lock();
        System.out.println("b");
        c();
        testLock.unlock();
    }

    public static synchronized void c() {
        testLock.lock();
        System.out.println("c");
        testLock.unlock();
    }
}

输出 a

使用jdk并发包中自带的 ReentrantLock

public class Test03 {
    private static ReentrantLock reentrantLock = new ReentrantLock();

    public static void main(String[] args) {
        a();
    }

    public static synchronized void a() {
        reentrantLock.lock();
        System.out.println("a");
        b();
        reentrantLock.unlock();

    }

    public static synchronized void b() {
        reentrantLock.lock();
        System.out.println("b");
        c();
        reentrantLock.unlock();
    }

    public static synchronized void c() {
        reentrantLock.lock();
        System.out.println("c");
        reentrantLock.unlock();
    }
}

输出 abc

4. 改造重入锁代码

public class TestLock {
    /**
     * initialValue = 0 表示没有线程使用
     * initialValue = 1 表示有一个线程正在使用
     */
    private AtomicInteger lockState = new AtomicInteger(0);
    /**
     * 获取到锁的线程
     */
    private Thread ownerLockThread;
    /**
     * 记录锁的重入次数
     */
    private int recursions;

    /**
     * 获取锁
     * 当前线程如果已经获取到锁则直接复用,不需要再次获取
     */
    public void lock() {
        //当前线程是否已经获取到锁
        if (ownerLockThread == Thread.currentThread()) {
            //重入次数+1
            recursions++;
            return;
        }
        /**
         * 将 initialValue 从 0 => 1
         * 如果有两个线程同时获取锁,最终只会有一个线程获取成功
         * 没有获取到锁会进行自旋
         */
        while (true) {
            if (lockState.compareAndSet(0, 1)) {
                //重入次数+1
                recursions++;
                //记录获取到锁的线程
                ownerLockThread = Thread.currentThread();
                return;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * 释放锁
     */
    public void unlock() {
        if (recursions > 0) {
            //重入次数-1
            recursions--;
            return;
        }
        //当重入次数为0的时候才会释放锁
        while (true) {
            if (lockState.compareAndSet(1, 0)) {
                return;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

5. 轻量级改造重量级锁

1722674464503

import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;

/**
 * @author 赵立
 */
public class TestLock {
    /**
     * initialValue = 0 表示没有线程使用
     * initialValue = 1 表示有一个线程正在使用
     */
    private AtomicInteger lockState = new AtomicInteger(0);
    /**
     * 获取到锁的线程
     */
    private Thread ownerLockThread;
    /**
     * 记录锁的重入次数
     */
    private int recursions;
    /**
     * 重试三次没有获取到锁被阻塞状态的线程
     */
    private LinkedBlockingDeque<Thread> notLockThreads = new LinkedBlockingDeque();

    /**
     * 获取锁
     * 当前线程如果已经获取到锁则直接复用,不需要再次获取
     */
    public void lock() {
        //当前线程是否已经获取到锁
        if (ownerLockThread == Thread.currentThread()) {
            //重入次数+1
            recursions++;
            return;
        }
        /**
         * 将 initialValue 从 0 => 1
         * 如果有两个线程同时获取锁,最终只会有一个线程获取成功
         * 没有获取到锁会进行自旋
         */
        /**
         * 每个线程获取锁的重试次数
         */
        int spinCount = 0;
        while (true) {
            if (spinCount > 3) {
                //重试次数大于三次 让我们当前线程变为阻塞状态
                notLockThreads.offer(Thread.currentThread());
                LockSupport.park();
                //唤醒此线程之后将重试次数改为0次
                spinCount = 0;
            }
            if (lockState.compareAndSet(0, 1)) {
                //重入次数+1
                recursions++;
                //记录获取到锁的线程
                ownerLockThread = Thread.currentThread();
                return;
            }
            spinCount++;//重试次数+1
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * 释放锁
     */
    public void unlock() {
        if (recursions > 0) {
            //重入次数-1
            recursions--;
            return;
        }
        //当重入次数为0的时候才会释放锁
        while (true) {
            if (lockState.compareAndSet(1, 0)) {
                //唤醒所有没有获取到锁的线程,从新开始竞争锁的资源
                notifyNotLockThreads();
                return;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * 唤醒所有没有获取到锁的线程
     */
    public void notifyNotLockThreads() {
        notLockThreads.forEach((t) -> LockSupport.unpark(t));
    }
}

6. 公平锁与非公平锁

  • 公平锁:就是比较公平,根据请求锁的顺序排列,先来请求的就先获取锁,后来获取锁就最后获取到, 采用队列存放,类似于吃饭排队。

  • 非公平锁:不是根据根据请求的顺序排列, 通过争抢的方式获取锁。

比如当前 abcd 四个线程,假设A线程获取到锁,在释放锁时:

  • 公平锁:根据bcd 请求锁的顺序排列获取锁,B先获取到锁,然后B释放后再给C,以此类推。

  • 非公平锁:Bcd 同时竞争这把锁;谁能够抢成功 谁就获取锁成功。

非公平锁效率比较高

Synchronized是非公平锁

img

//公平锁
new  ReentramtLock()(true)
//非公平锁
new  ReentramtLock()(false)

公平锁演示

public class Test04 implements Runnable {
    private static int count = 0;
    // fair 为 true 时为公平锁
    private static Lock lock = new ReentrantLock(true);

    @Override
    public void run() {
        while (count < 200) {
            createCount();
        }
    }

    public void createCount() {
        System.out.print(Thread.currentThread().getName() + ",count:" + count+";   ");
        count++;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(new Test04()).start();
        }
    }
}

非公平锁演示

public class Test04 implements Runnable {
    private static int count = 0;
    // fair 为 true 时为公平锁
    private static Lock lock = new ReentrantLock(false);

    @Override
    public void run() {
        while (count < 200) {
            createCount();
        }
    }

    public 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 Test04()).start();
        }
    }
}

上一章节自定义的锁是非公平锁

/**
 * 唤醒所有没有获取到锁的线程
 */
public void notifyNotLockThreads() {
    /**
     * 非公平锁 会唤醒所有的阻塞线程
     */
    notLockThread.forEach((t) -> LockSupport.unpark(t));
   
}

改造为公平锁

/**
 * 唤醒所有没有获取到锁的线程
 */
public void notifyNotLockThreads() {  
    /**
     * 公平锁 取出当前队列中的第一个 取出成功时同时删除
     */
	LockSupport.unpark(notLockThreads.poll());
}

7. 偏向锁/轻量级锁/重量级锁应用场景

Synchronized 锁的升级过程

  • t1 线程 获取到锁 1毫秒就释放锁 t1线程1毫秒之后又唤醒t2线程竞争锁资源,t2竞争锁资源从阻塞=>就绪=>cpu调度=>竞争锁资源,在这个其中就会有锁的升级过程,由偏向锁 => 轻量级锁

  • 如果 t1 线程获取到锁之后 60s 才会释放锁 t2线程自旋60s ,那么 t2 直接阻塞就可以了

  1. 偏向锁:加锁和解锁不需要额外的开销,只适合于同一个线程访问同步代码块,无需额外的开销,如果多个线程同时竞争的时候,会撤销该锁。

  2. 轻量级锁竞争的线程不会阻塞,提高了程序响应速度,如果始终得不到锁的竞争线程,则使用自旋的形式,消耗cpu资源,适合于同步代码块执行非常快的情况下,自旋(jdk1.7以后智能自转)

  3. 重量级锁: 线程的竞争不会使用自旋,不会消耗cpu资源,适合于同步代码执行比较长的时间。

偏向锁与重入锁之间有什么区别?

  • 偏向锁与重入锁实现的思路基本上相同

  • 偏向锁(对象头mark word 中会记录线程 id)

    • 当前只有一个线程的情况下,没有其他的线程竞争锁,该锁一直会被我们的该线程持有,不会立即释放锁

    • 必须要有另外的一个线程竞争该锁,才会撤销我们的偏向锁

  • 重入锁(会记录该线程的重入次数)

    • 当前线程如果已经获取到了锁,当前线程中的其他方法如果有需要获取锁的话,则直接复用

    • 当重入次数减到 0 的情况下才会通过 cas 改状态 (1,0) 即释放锁

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值