【Java】JUC并发(synchronized进阶、ReentrantLock可重入锁)

Synchronized进阶

1、锁升级

Java 6之后对 synchronized 进行了改进,引入了锁升级机制,锁会根据线程的竞争情况进行升级。方向为 : **无锁 --> 偏向锁 --> 轻量级锁 --> 重量级锁,**可以一定程度减少系统的开销。

无锁:一个线程没有被执行时,就会处于无锁状态。

偏向锁:当只有一个线程执行时,会从无锁升级为偏向锁。

轻量级锁 :当有多个线程交替执行同步块时,会从偏向锁升级为轻量级锁。

重量级锁:当多个线程并发时,会从轻量级锁升级为重量级锁。

2、锁升级过程

2.1、无锁 ==> 偏向锁

当只有一个线程执行时,会从无锁升级为偏向锁。会在对象头中的 Mark Word 中存入该线程的ID。此时,锁的状态为偏向锁。

2.2、偏向锁 ==> 轻量级锁

当有多个线程交替执行同步块时,会从偏向锁升级为轻量级锁。这时在线程的栈中创建锁记录(Lock Record),并通过CAS(Compare and Swap)操作将对象头中的 Mark Word 复制到锁记录中,并将 Mark Word 执行锁记录地址。

2.3、轻量级锁 ==> 重量级锁

当多个线程并发时,会从轻量级锁升级为重量级锁。也就是当多个线程开始竞争锁时,就要从轻量级锁升级为重量级锁。

3、锁降级

锁降级,通常是从高级别转变到低级别的过程。一般会发生在:GC垃圾回收的过程中,JVM 会将索状态重置为无锁状态。

4、Synchronized实现原理

synchronized实现原理主要基于对象头Monitor (监视器)机制

4.1、对象头

通常将锁的状态保存在对象头中,对象头主要包含两个部分:Mark WordKlass Pointer

4.1.1、Mark Word
  • 长度:32位 JVM 中占32位(4字节),64位 JVM 中占64位(8字节)
  • 作用 :主要存储对象的 哈希值、GC分代年龄、锁状态 等运行时的数据。
  • 特点:Mark Word 的位布局会根据对象的锁状态动态改变。
4.1.2、Klass Pointer
  • 长度:32位 JVM 中占32位(4字节),64位 JVM 中默认开启指针压缩占32位(4字节),不开启是占64位(8字节)。
  • 作用:指向对象所属类的元数据,JVM 通过指针确定对象是那个类的实例。
  • 特点:堆内存超过32GB时自动关闭指针压缩,恢复为8字节。

4.2、Monitor (监视器)

通常存在于线程栈的锁记录中

5、线程安全的单例模式

java 复制代码
public class Test02 {
    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        Singleton s3 = Singleton.getInstance();

        System.out.println(s1.hashCode());
        System.out.println(s2.hashCode());
        System.out.println(s3.hashCode());
    }
}
// 线程安全的单利模式
class Singleton{
    // 构造函数的私有化
    private Singleton(){}

    // 静态的内部类
    private static class Holder{
        // 单例对象
        private static final Singleton singleton = new Singleton();
    }
    public static Singleton getInstance() {
        return Holder.singleton;
    }
}



2003749087
2003749087
2003749087

ReentrantLock可重入锁

1、什么事可重入锁

ReentrantLock是java.util.concurrent.locks 包下的一个类,它提供了比 Synchronized 更灵活、强大的锁机制。"可重入"意味着同一线程可以多次获取同一把锁,而不会被阻塞。

2、核心方法

2.1、锁操作方法

|----------------------------------------------|------------------------|
| 方法签名 | 描述 |
| void lock() | 获取锁,如果锁被其他线程持有,则阻塞当前线程 |
| void lockInterruptibly() | 获取锁,但允许等待时中断 |
| boolean tryLock() | 尝试以非阻塞的方式获取锁 |
| boolean trayLock(long timeout,TimeUnit unit) | 在指定时间内尝试获取锁 |
| void unlock() | 释放锁 |

2.2、状态查询方法

|---------------------------------|--------------|
| 方法签名 | 描述 |
| boolean isHeldByCurrentThread() | 查询当前线程是否持有锁 |
| boolean isLocked() | 查询锁是否被任何线程持有 |
| boolean isFair() | 判断锁是否为公平锁 |

2.3、条件变量方法

|--------------------------|-----------------|
| 方法签名 | 描述 |
| Condition newCondition() | 创建一个与当前锁绑定的条件变量 |

3、使用案例

3.1、锁的基本使用

java 复制代码
public class Test04 {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();

        Thread t1 = new Thread(()->{
            counter.add();
        });
        Thread t2 = new Thread(()->{
            counter.dec();
        });
        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println(counter.getValue());
    }
}

class Counter{
    private static int value = 0;

    // 创建锁对象
    private final ReentrantLock lock =new ReentrantLock();

    public static int getValue() {
        return value;
    }

    public void add(){
        lock.lock();
        try {
            for (int i =0 ;i <10000;i++){
                value++;
            }
        } finally {
            lock.unlock();
        }

    }    public  void dec(){
        lock.lock();
        try {
            for (int i =0 ;i <10000;i++){
                value--;
            }
        } finally {
            lock.unlock();
        }
    }
}

3.2、支持中断锁的获取

java 复制代码
public class demo1 {
    public static void main(String[] args) throws InterruptedException {
        // 创建ReentrantLock锁对象
        ReentrantLock lock = new ReentrantLock();

        Thread t1 = new Thread(()->{
            try {
                lock.lockInterruptibly();
                try {
                    System.out.println("线程任务执行!");
                } finally {
                    lock.unlock();
                }
            } catch (InterruptedException e) {
                System.out.println("线程中断!");
                return;
            }
            System.out.println("子线程结束!");
        });

        t1.start();
        t1.interrupt();
        System.out.println("主线程结束!");
    }
}


主线程结束!
线程中断!

4、synchronized和synchronized的区别

|---------|-----------------|----------------------------|
| 特性 | Synchronized | synchronized |
| 锁的获取与释放 | 隐式调用,由JVM自动完成 | 显示调用,手动调用lock()和unlock()方法 |
| 可中断性 | 不可中断 | 可以中断,使用lockInterruptibly() |
| 超时机制 | 不支持 | 支持,使用tryLock(timeout) |
| 公平性 | 非公平 | 非公平和公平都支持 |
| 条件变量 | 单一wait/notify | 支持条件对象Condition |
| 锁状态检测 | 无法判断 | 通过方法可以获取 |
| 锁实现机制 | 监听器 Monitor | AQS |