本文介绍: 是 Java用于实现线程同步关键字,它提供了对代码块、方法以及静态方法同步支持。以下是关于可以用于实现对象同步,确保同一时刻只有一个线程能够访问同步代码块或方法。对象锁的粒度可以是对象实例实例方法)或类(静态方法)。可以直接修饰方法,使整个方法具有同步性。此时,锁对象是方法所属的对象实例修饰实例方法时,锁对象是方法调用实例修饰静态方法时,锁对象是类的 Class 对象。还可以用于修饰代码块,指定锁的粒度更加灵活。

背景

多线程环境下同时访问共享资源出现一些数据问题,此关键字就是用来保证线程安全解决这一问题

内存可见的问题

在了解synchronized之前先了解一下java内存模型,如下图
image.png

  1. 线程1去主内存获取x的值读入本地内存此时x值为1,进行运算x+1此时线程1的x值为2然后写入主内存;
  2. 此时在线1先入主内存之前,此时线程2去主内存读取x的值,它读取到的值是1
  3. 最后x的值在主内存里的值是2线程2读取到的是1,出现了内存不可见的问题

synchronized关键字使用方式

修饰方法

public class SynchronizedMethodExample {

    private int counter = 0;

    // synchronized 修饰的方法
    public synchronized void increment() {
        // 这里操作原子的,同一时刻只有一个线程能够执行
        counter++;
        System.out.println("Incremented counter to: " + counter);
    }

    public static void main(String[] args) {
        SynchronizedMethodExample example = new SynchronizedMethodExample();

        // 创建多个线程,同时调用 increment 方法
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                example.increment();
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                example.increment();
            }
        });

        // 启动线程
        thread1.start();
        thread2.start();

        try {
            // 等待两个线程执行完毕
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 打印最终的计数器
        System.out.println("Final counter value: " + example.counter);
    }
}

由于 synchronized 修饰了 increment 方法,保证了同一时刻只有一个线程能够执行该方法。因此,两个线程在执行 increment 方法时会互斥,不会同时对 counter 进行操作
执行结果

Incremented counter to: 1
Incremented counter to: 2
Incremented counter to: 3
Incremented counter to: 4
Incremented counter to: 5
Incremented counter to: 6
Incremented counter to: 7
Incremented counter to: 8
Incremented counter to: 9
Incremented counter to: 10
Final counter value: 10

修饰同步代码

public class SynchronizedBlockExample {

    private int counter = 0;
    private final Object lockObject = new Object();  // 用于同步的对象

    public void increment() {
        // 一些非同步代码

        synchronized (lockObject) {
            // 需要同步的代码块
            counter++;
            System.out.println("Incremented counter to: " + counter);
        }

        // 继续执行非同步的代码
    }

    public static void main(String[] args) {
        SynchronizedBlockExample example = new SynchronizedBlockExample();

        // 创建多个线程,同时调用 increment 方法
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                example.increment();
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                example.increment();
            }
        });

        // 启动线程
        thread1.start();
        thread2.start();

        try {
            // 等待两个线程执行完毕
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 打印最终的计数器
        System.out.println("Final counter value: " + example.counter);
    }
}

这个例子中,increment 方法包含了一个同步的代码块,使用 synchronized (lockObject)counter 进行递增操作。由于使用lockObject 作为同步对象,保证了两个线程在执行同步代码块时是互斥的,不会同时对 counter 进行操作
执行结果:

Incremented counter to: 1
Incremented counter to: 2
Incremented counter to: 3
Incremented counter to: 4
Incremented counter to: 5
Incremented counter to: 6
Incremented counter to: 7
Incremented counter to: 8
Incremented counter to: 9
Incremented counter to: 10
Final counter value: 10

修饰静态方法

public class SynchronizedStaticMethodExample {

    private static int counter = 0;

    // synchronized 修饰的静态方法
    public static synchronized void increment() {
        // 这里操作原子的,同一时刻只有一个线程能够执行
        counter++;
        System.out.println("Incremented counter to: " + counter);
    }

    public static void main(String[] args) {
        // 创建多个线程,同时调用静态方法 increment
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                SynchronizedStaticMethodExample.increment();
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                SynchronizedStaticMethodExample.increment();
            }
        });

        // 启动线程
        thread1.start();
        thread2.start();

        try {
            // 等待两个线程执行完毕
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 打印最终的计数器
        System.out.println("Final counter value: " + SynchronizedStaticMethodExample.counter);
    }
}

这个例子中,increment 方法是一个静态方法使用 synchronized 修饰。由于是静态方法,它锁定的是整个类的 Class 对象。两个线程无法同时调用 increment 方法,确保了对 counter 的递增操作是线程安全的。

Incremented counter to: 1
Incremented counter to: 2
Incremented counter to: 3
Incremented counter to: 4
Incremented counter to: 5
Incremented counter to: 6
Incremented counter to: 7
Incremented counter to: 8
Incremented counter to: 9
Incremented counter to: 10
Final counter value: 10

synchronized原理

java对象的组成

在讲原理之前我们先了解一下java对象的组成:
image.png
实例数据

对齐填充

对象头

ObjectMonitor

HotSpot虚拟机源码ObjectMonitor.hpp

ObjectMonitor::ObjectMonitor() {
    _header       = NULL;         // 监视器头部,用于保存状态信息
    _count        = 0;            // 计数器,用于记录监视器的使用次数
    _waiters      = 0;            // 等待线程数
    _recursions   = 0;            // 当前线程对该锁的递归次数
    _object       = NULL;         // 监视的对象
    _owner        = NULL;         // 拥有锁的线程
    _WaitSet      = NULL;         // 等待队列,存储等待该锁的线程
    _WaitSetLock  = 0 ;           // 用于保护等待队列的锁
    _Responsible  = NULL ;        // 释放锁的线程
    _succ         = NULL ;        // 后继监视器
    _cxq          = NULL ;        // 入口等待队列
    FreeNext      = NULL ;        // 空闲监视器链表的下一个
    _EntryList    = NULL ;        // 入口列表
    _SpinFreq     = 0 ;           // 自旋频率
    _SpinClock    = 0 ;           // 自旋时钟
    OwnerIsThread = 0 ;           // 拥有者是否为线程
}

ObjectMonitor.hpp 是 HotSpot 虚拟机(OpenJDK 的默认虚拟机实现)中用于实现对象监视器的头文件。对象监视器在 Java 中由 synchronized 关键字提供支持,用于实现多线程之间的同步。以下是对 ObjectMonitor.hpp 的一些关键部分的简要解释

  1. ObjectMonitor 结构体:
    ObjectMonitor 是一个结构体,表示对象监视器。它包含了维护监视器状态和控制线程访问的各种信息。主要字段包括:
  1. ObjectMonitor 头部header):
    ObjectMonitor头部包含了一系列标志位,用于表示锁的状态、等待队列的状态等。这些标志位在字节层面上被设置检查,以进行对锁的操作。一些常见的标志位有:
  1. 等待队列(WaitSet):
    ObjectMonitor 中包含一个等待队列,用于存储等待该锁的线程。线程在等待队列中等待时,它会进入等待状态,直到被唤醒。等待队列的管理涉及到线程的入队出队等操作。
  2. 线程入队出队
    ObjectMonitor 定义了一些方法,用于线程的入队出队操作。例如
  1. 锁的状态转换
    ObjectMonitor 定义了一些方法来实现锁状态的转换例如无锁轻量级锁、从轻量级锁到重量级锁等。这些状态的转换涉及到了底层原子操作和 CAS(Compare and Swap)等机制
  2. 适应性自旋锁:
    HotSpot 虚拟机中的 ObjectMonitor 还包括适应性自旋锁的机制,该机制用于在获取锁时进行自旋,以避免线程进入阻塞状态。适应性自旋锁的目标是根据程序运行时的历史信息来动态调整自旋次数,以提高性能

image.png

  1. 当多个线程同时访问同步代码块时,首先会进入EntryList中,然后通过CAS的方式尝试将Monitor中的owner字段设置当前线程,同时count加1,若发现之前的owner的值就是指向当前线程的,recursions需要加1。如果CAS尝试获取失败,则进入到EntryList中;

  2. 当获取锁的线程调用wait()方法,则会将owner设置null,同时count减1,recursions减1,当前线程加入到WaitSet中,等待被唤醒;

  3. 当前线程执行完同步代码块时,则会释放锁,count减1,recursions减1。当recursions的值为0时,说明线程已经释放了锁.

synchronized作用于同步代码块的字节指令

在Java中,synchronized 作用于同步代码块的字节指令主要涉及到 monitorentermonitorexit 指令。这两个指令用于实现监视器(monitor)的进入退出,即获取和释放锁。
以下是一个简单的Java同步代码块的例子

public class SynchronizedExample {
    private static final Object lock = new Object();

    public void synchronizedMethod() {
        synchronized (lock) {
            // 同步代码块
            // ...
        }
    }
}

对应字节码大致如下

public synchronizedMethod()V
   L0
    LINENUMBER 7 L0
    GETSTATIC SynchronizedExample.lock : Ljava/lang/Object;
    DUP
    ASTORE 1
    MONITORENTER   // monitorenter 指令,获取锁
    // 同步代码块的字节指令
    // ...
   L1
    LINENUMBER 9 L1
    ALOAD 1
    MONITOREXIT    // monitorexit 指令,释放锁
    ATHROW

解释一下上述字节码:

  1. GETSTATIC SynchronizedExample.lock : Ljava/lang/Object;: 获取 lock 字段的值,即获取锁对象。
  2. DUP: 复制栈顶数值,用于后续的 ASTORE 1 操作。
  3. ASTORE 1: 将锁对象的引用存储到局部变量1。
  4. MONITORENTER: 进入监视器,即获取锁。如果锁已经被其他线程占用当前线程将阻塞等待。
  5. 同步代码块的具体实现
  6. ALOAD 1: 将之前存储的锁对象引用加载栈顶
  7. MONITOREXIT: 退出监视器,即释放锁。
  8. ATHROW: 抛出异常,确保在任何情况下都会释放锁。

synchronized作用于方法字节码指令

以下是一个简单例子演示synchronized 修饰方法的字节码指令:

public class SynchronizedMethodExample {
    private int counter = 0;

    public synchronized void synchronizedMethod() {
        counter++;
    }
}

对应的字节码可能类似于:

public class SynchronizedMethodExample {
    private int counter;

    public SynchronizedMethodExample() {
        counter = 0;
    }

    public synchronized void synchronizedMethod() {
        // 获取锁
        monitorenter

        try {
            // 同步代码块
            counter++;
        } finally {
            // 释放锁
            monitorexit
        }
    }
}

这个例子中:

  1. monitorenter 指令:在进入同步代码块之前获取锁。
  2. monitorexit 指令:在同步代码块执行完毕后释放锁。

synchronized锁的优化

JDK1.5之前,synchronized属于重量级锁,重量需要依赖于底层操作系统Mutex Lock实现然后操作系统需要切换用户态和内核态,这种切换的消耗非常大,所以性能相对来说并不好。

  1. 偏向锁(Biased Locking):
    • 程序启动时,对象的锁大多数情况下只被一个线程所持有。为了提高性能引入了偏向锁机制。
    • 当一个线程获取了对象的锁后,会在对象头的 Mark Word 中记录这个线程的 ID,表示这个锁被偏向于该线程。之后,该线程再次进入同步块时,无需再竞争锁,直接获得。
    • 偏向锁的目标是降低无竞争情况下的锁操作的开销。
  2. 轻量级锁(Lightweight Locking):
  3. 自旋锁和自适应自旋锁:
  4. 消除和锁粗化:

总结

synchronized 是 Java 中用于实现线程同步的关键字,它提供了对代码块、方法以及静态方法的同步支持。以下是关于 synchronized 锁的总结

  1. 对象锁:
  1. 方法锁:
  • synchronized 可以直接修饰方法,使整个方法具有同步性。此时,锁对象是方法所属的对象实例
  • 修饰实例方法时,锁对象是方法调用的实例;修饰静态方法时,锁对象是类的 Class 对象。
  1. 代码块锁:
  • synchronized可以用于修饰代码块,指定锁的粒度更加灵活。
  • 在代码块中,需要指定一个对象作为锁,多个线程只有在获取了相同的锁时才会争夺执行权。
  1. 锁的释放:
  • 当一个线程获得了对象锁后,其他线程必须等待该线程释放锁才能进入同步代码块或方法。
  • 如果线程执行的同步代码块出现异常,锁会被自动释放。
  1. 偏向锁、轻量级锁和重量级锁:
  1. 重入性:
  1. 性能优化

总体而言,synchronized 是一种简单有效的同步机制,用于确保多线程程序中对共享资源的安全访问。然而,在一些高并发场景下,可能需要考虑使用更灵活的锁机制,如 java.util.concurrent 包中提供的锁。

参考

https://zhuanlan.zhihu.com/p/377423211
https://juejin.cn/post/6973571891915128846

原文地址:https://blog.csdn.net/qq_36649893/article/details/134795964

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_40292.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注