Java 并发锁

1 为什么要加锁

所先JVM将内存划分成2个区域

  • 主内存:所有线程共享的内存区域,存储所有的共享变量
  • 工作内存:每个线程独有的内存区域,存储该线程使用到的共享变量的副本

线程对变量的操作(读取、赋值)必须在工作内存完成,从主内存读取变量到工作内存,在对工作内存的变量进行操作,返回主内存。因此,当不同线程同时操作一个变量时,就会出现操作的数据实际值和主内存不一致。

2 需要解决那些问题

2.1 可见性问题 

  当一个共享变量被其他线程改变值后,其他线程无法立即感知到这个变量的值。

2.2 原子性

  无法确保对同一个共享变量操作是原子性的。

2.3 有序性

  操作系统可能对一些操作指令进行重新排序,从而提高运行速度。例如一个对象赋值操作。 A a =  new A()。 单例模式时,会判断对象是否是空对象,所有要两次判断非空

  原本顺序:

    1. 分配内存空间

    2. 初始化对象

    3.  将引用指向内存

  重排序后顺序:

    1. 分配内存空间

    2. 将引用指向内存
    3. 初始化对象

3 锁升级过程

虚拟机对象头分为两部分信息:

  • 第一部分:用于存储对象自身运行时数据,32BIts(32位系统)或者64Bits(64位系统),也称为Mark Work,  其中有2Bits用来存储锁标识。
  • 第二部分:用于存储指向方法区对象类型数据的指针

锁优化过程:

1. 锁消除

  如果不会出现同步问题,java编译之后,就会忽略同步

2. 偏向锁(只记录该线程ID)

  偏向锁就是在无竞争(只有一个线程)的情况下把整个同步都消除。当锁对象第一次被线程获取的时候,虚拟机将对象头中的标志位设位01,偏向模式。同时使用CAS把获取到这个锁的线程ID记录在对象的Mark Word之中,如果CAS操作成功,以后该线程在进入时,都不需要在进行相关的锁操作。

3. 轻量级锁(一个一个来, 很慢, 不会有竞争, 能更换成功)

  在无竞争的情况下使用CAS消除数据在同步使用的互斥量。CAS将对象的MarkWord更新为指向Lock Record(线程堆栈,用于存储MarkWord的拷贝)的指针,如果更新成功,将对象头Mark Word里面的锁标志位更新成00。

4. 锁粗化

  如果一个代码快有特别多的同步,虚拟机就会执行锁粗化,因为其实对多个对象加锁其实比一个对象加锁开销大的很多,虽然可能每个线程等待的时间会少点。

5. 自旋锁和自适应自旋

       当获取不到锁时,不在挂起,而是让他进行自旋等待。自旋等待次数通过-XX:PreBlockSpin来改变。jdk1.6引入自适应自旋锁。自适应意味着自旋的时间不在固定,根据任务执行的长短来决定自旋等待时常。

5. 锁的公平性

  • 公平锁:先请求的线程先获得锁,后请求的线程进入等待队列排队,直到前面的线程释放锁后再依次获取
  • 非公平锁:新请求锁的线程先尝试拿锁,如果拿到直接获取到锁,而无需排队等待,即使等待队列中已有线程在排队。

4 常见锁类型

2.1 Synchronized

synchronized 能解决资源的原子性、可见性和有序性。底层实现依赖JVM的锁机制和对象头结构。JVM层面的锁,自动加锁和释放锁,非公平锁,设计到锁升级。

public class SynchronizedDemo {

    int i = 0;

    public static void main(String[] args) {
        SynchronizedDemo synchronizedDemo = new SynchronizedDemo();
        synchronizedDemo.init1();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(synchronizedDemo.i);
    }

    public void init1() {
        Thread[] threads = new Thread[100];
        for (int i = 0; i < 100; i++) {
            threads[i] = new Thread(this::fun1);
            threads[i].start();
        }

    }

    /**
     * 方法上锁
     */
    synchronized void fun1() {
        i++;
    }
}

2.2 ReentranLock

 

posted @ 2025-10-07 11:18  一花一世界!  阅读(3)  评论(0)    收藏  举报