进阶学习之旅-多线程之Synchronized(Synchronized的使用、锁的存储、锁升级原理、wait&notify)

1.学习收获

  • 学习方法
  • 如何保证线程安全性?
  • Synchronized的基本使用
  • 锁的存储
  • Synchronized锁的升级原理
  • wait/notify实现线程通信

2.学习方法

场景->需求->解决方案->应用->原理

  • 场景: 多线程场景
  • 需求:多线程并行产生的线程安全性
  • 解决方案:加锁(Synchronized)
  • 应用:synchronized的几种使用方式,对象锁、静态方法锁、代码块
  • 原理:偏向锁(cas乐观锁) -> 轻量级锁(自旋锁) -> 重量级锁(mutex互斥)

3.如何保证线程安全性

多线程并行环境下会产生线程安全问题,可通过管理数据状态的访问,即通过 synchronized 锁保证数据的安全性还要保证性能。

4.synchronized的基本使用

/**
 * @PackageName: com.raven.multithreaded.synchronizedtheory
 * @ClassName: BasicUse
 * @Blame: raven
 * @Date: 2021-08-14 15:46
 * @Description: synchronized 基本使用
 * <p>
 * 2种表现形式
 * 2种作用范围 (对象锁or类锁)区别是 是否挂对象跨线程被保护
 * 控制锁的范围由对象的生命周期而决定!!!
 * 1.修饰实例方法
 * 2.修饰静态方法
 * 3.修饰代码块
 */
public class BasicUse {
    private final Object lock;

    public BasicUse(Object lock) {
        this.lock = lock;
    }
    /**
     * 修饰静态方法
     * 锁对象是BasicUse.class(对象锁)
     * 是类锁,不同实例访问该方法会出现锁竞争
     * 锁的范围较大(整个方法代码都会上锁)
     */
    public synchronized static void staticMethod() {
        System.out.println("synchronized修饰静态方法");
    }

    /**
     * 修饰费静态方法
     * 锁对象是BasicUse对象
     * 是对象锁,不同BasicUse实例间不会出现锁竞争
     * 锁的范围较大(整个方法代码都会上锁)
     */
    public synchronized void nonStaticMethod() {
        System.out.println("synchronized修饰非静态方法");
    }

    /**
     * 修饰代码块
     * 锁对象是lock对象,不同BasicUse实例传入同一个lock对象时会出现锁竞争
     * 锁的范围较小
     */
    public void codeBlockLock() {
        System.out.println("before sth..");
        synchronized (lock) {
            System.out.println("锁对象为 lock ");
        }
        System.out.println("after sth..");
    }

    /**
     * 修饰代码块
     * 锁对象是BasicUse的class类对象,会出现锁竞争
     * 锁的范围较小
     */
    public void codeBlockClass() {
        System.out.println("before sth..");
        synchronized (BasicUse.class) {
            System.out.println("锁对象为 BasicUse的class类对象 ");
        }
        System.out.println("after sth..");
    }
    /**
     * 修饰代码块
     * 锁对象是BasicUse对象,不同的BasicUse实例间不会出现锁竞争
     */
    public void codeBlockThis(){
        System.out.println("before sth..");
        synchronized (this) {
            System.out.println("锁对象为 BasicUse对象 ");
        }
        System.out.println("after sth..");
    }

}

/**
 * @PackageName: com.raven.multithreaded.synchronizedtheory
 * @ClassName: BasecUseTest
 * @Blame: raven
 * @Date: 2021-08-14 16:08
 * @Description: 模拟使用不同方式的锁
 */
public class BasicUseTest {

    public static void main(String[] args) {
        // 模拟使用synchronized修饰静态方法的锁
//        useStaticMethodLock();
        Object lock = new Object();
        BasicUse basicUse1 = new BasicUse(lock);
        BasicUse basicUse2 = new BasicUse(lock);

        // 模拟使用synchronized修饰非静态方法的锁
//        useNonStaticMethodLock(basicUse1, basicUse2);

        // 模拟使用synchronized修饰代码块 -> 锁对象为BasicUse对象的class对象
//        useCodeBlockMethodLock(basicUse1, basicUse2);

        // 模拟使用synchronized修饰代码块 -> 锁对象为传入的Object对象,不同实例不同线程间仍然线程安全
        useCodeLockMethodLock(basicUse1, basicUse2);


    }

    private static void useCodeLockMethodLock(BasicUse basicUse1, BasicUse basicUse2) {
        new Thread(() -> {
            basicUse1.codeBlockLock();
        }).start();
        new Thread(() -> {
            basicUse2.codeBlockLock();
        }).start();
    }

    private static void useCodeBlockMethodLock(BasicUse basicUse1, BasicUse basicUse2) {
        new Thread(() -> {
            basicUse1.codeBlockClass();
        }).start();
        new Thread(() -> {
            basicUse2.codeBlockClass();
        }).start();
    }

    private static void useNonStaticMethodLock(BasicUse basicUse1, BasicUse basicUse2) {
        new Thread(() -> {
            basicUse1.nonStaticMethod();
        }).start();
        new Thread(() -> {
            basicUse2.nonStaticMethod();
        }).start();
    }

    public static void useStaticMethodLock() {
        new Thread(() -> {
            BasicUse.staticMethod();
        }).start();
        new Thread(() -> {
            BasicUse.staticMethod();
        }).start();
    }
}

5.锁存储以及锁升级原理?

5.1锁的实现

jdk1.6之前,synchronized的实现 是重量级锁,即互斥锁,虽然能实现线程安全,但性能堪忧,在1.6之后,对内部实现做了优化,出现了锁升级的概念
。即(偏向锁(cas乐观锁) -> 轻量级锁(自旋锁) -> 重量级锁(mutex互斥))

5.1.1偏向锁

image

cas[(Compare and swap(value,expect,update)]比较、
实现原子性、
乐观锁、

5.1.2轻量级锁

image

什么是自旋?

boolean cas()

for(;;){// 自旋
    if(cas){
        return; // 标识成功
    }
}

绝大多数的线程在获取锁以后,在非常daunt的实际内都会去释放锁

自旋会占用CPU资源,所以在指定的自旋次数之后,如果还没有获得轻量级锁,锁就会升级为重量级锁(可通过preBlockSpin 设置自旋次数,或使用自适应自旋)

5.1.3重量级锁

当锁升级到重量级锁后,没有获取到锁的线程就会被阻塞 (->BLCOKED状态) 并且会产生系统级别的线程切换

每一个对象都会存在有ObjectMonitor监视器,所以任何对象都可以成为锁对象,可以通过monitor()方法获取当前对象的监视器对象
ObjectMonitor就是实现重量级锁的核心
,monitor是一种MutexLock(互斥锁),不同os会有不同的实现,所以重量级锁慢的主要原因是因为会产生系统级别的线程切换(用户态<-<->->内核态),性能开销比较大

image

image

java 同步代码块的方法会多出俩个指令  通过javap -v 类名称查看
一个是monitor(监视器)enter
一个是monitorexit

.......
    24: monitorexit
    25: goto          33
    28: astore_2
    29: aload_1
    30: monitorexit
......

image
重量级锁的实现: A线程执行monitorenter指令,争抢锁对象,如果monitorenter成功,就会获取到锁对象,其他线程拿不到锁对象就会进入同步队列(每一个被阻塞的线程都会加入到该队列),当A线程执行monitorexit指令后,随机唤醒同步队列中的一个线程,线程再次进行争抢锁对象。

5.2锁在内存中的存储

image

image

5.3锁升级原理

synchronized在不同情况下使用不同的锁:

假如有俩个线程ThreadA/ThreadB

1.当只有ThreadA去访问锁对象(大多数情况)时,使用偏向锁,将ThreadA的ThreadID 标记到锁对象中,锁标志位修改为01。
image

2.ThreadA和ThreadB交替访问,则将锁升级为轻量级锁,并通过自旋的方式尝试获取锁对象,将标志位改为00,轻量级锁将栈帧指向锁记录的指针。
image

3.多个线程同时访问时,锁会升级为重量级锁,并且会阻塞式访问。
image

6.wait 和 notify

wait、notify、notifyall
线程的通信机制

wait:将线程阻塞并加入到同步队列中、释放当前的同步锁

notify/notifyall:唤醒被阻塞的线程

public class WaitThread extends Thread {

    private Object lock;

    public WaitThread(Object object) {
        this.lock = object;
    }

    @Override
    public void run() {
        synchronized (lock) {
            System.out.println("wait before:" + Thread.currentThread().getName() + " running");
            try {
                // 会做俩件事
                // 1.将当前线程阻塞加入等待队列
                // 2.释放锁
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("wait after:" + Thread.currentThread().getName() + " running");
        }
    }
}


public class NotifyThread extends Thread {

    private Object lock;

    public NotifyThread(Object object) {
        this.lock = object;
    }

    @Override
    public void run() {
        synchronized (lock) {
            System.out.println("notify before:" + Thread.currentThread().getName() + " running");
            lock.notify();
            System.out.println("notify after:" + Thread.currentThread().getName() + " running");
        }
    }
}

public class WaitAndNotifyTest {

    public static void main(String[] args) {
        Object lock = new Object();
        WaitThread waitThread = new WaitThread(lock);
        waitThread.start();
        NotifyThread notifyThread = new NotifyThread(lock);
        // notify线程和wait线程必须使用同一把锁,notify线程才能唤醒wait线程
//        Object lock2 = new Object();
//        NotifyThread notifyThread = new NotifyThread(lock2);
        notifyThread.start();
    }
}

image

ThreadA线程执行monitorenter指令,获取到了锁对象,执行一些逻辑,当执行了lock.wait后,他会释放锁,并把当前线程加入到等待队列中(等待队列没有资格获得锁)

ThreadB线程执行monitorenter指令,获取到了锁对象,执行一些逻辑,当执行lock.notify后,会随机唤醒等待队列中的一个线程,移到同步队列, 然后执行monitorexit指令随机唤醒同步队列中的线程,再见通过锁竞争机制争取锁,执行接下来的逻辑。

相关博文链接github
多线程之多线程基础 应用场景、生命周期、中断、复位
多线程之SynchronizedSynchronized的使用、锁的存储、锁升级原理、wait&notify
多线程之内存可见性的本质volatile、JMM内存模型、Happens-Before原则
多线程之深入AQSLock锁基本使用、ReentrantLock重入锁、AQS原理分析、AQS源码分析
多线程之JUC工具类原理及Condition使用及源码分析 ================================================= 多线程之JUC工具类原理及CountDownLatch、CyclicBarrier使用及源码分析 =================================================多线程之JUC工具类原理及Semaphore使用及源码分析Condition源码分析文档 ================================================= CountDownLatch、CyclicBarrier源码分析文档 ================================================= Semaphore源码分析文档
多线程之深入ConcurrentHashMapconcurrentHashMap源码分析
多线程之阻塞队列阻塞队列源码分析
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值