掘金 后端 ( ) • 2024-06-30 14:22

java线程同步原理主要2个概念:互斥(mutual exclusion)和可见性。

其中互斥保证了在同一时刻只有一个线程可以访问临界区,可见性保证一个线程对共享变量的修改能够及时被其他线程看到。 第3节我们简单介绍了内存可见性相关内容,并在4、5分别介绍了因缓存一致性导致的伪共享问题和代码重排序。这一章节主要了解下互斥相关。 Java提供了2种锁机制来控制多个线程对共享资源的互斥访问。下文分别从可重入性、公平性了解这2种锁的实现方式。

    1. JVM实现的synchronized内置锁,在软件层面依赖JVM
    1. JDK实现的ReentrantLock显式锁,在硬件层面依赖特殊的CPU指令

锁的名字千千万,但首先锁分为内置锁/隐式锁/自动锁和显式锁。关于锁的共享与独占,是否可重入(避免死锁),公平非公平,可中断锁的概念会在之后进行介绍。本节主要了解说明内置锁和显式锁。

image.png

synchronized

以最简单的synchronized为例,synchronized(this)中的块,可以保证同时只有一个线程执行。

public class Counter{

  private int count = 0;

  public int inc(){
    synchronized(this){
      return ++count;
    }
  }
}

synchronized使用方法

可以修饰在不同层级:修饰实例方法、修饰静态方法、修饰代码块。通过在对象->对象头->mark word标记字段中修改锁状态标志,

  1. 修饰实例方法,常量池多了ACC_SYNCHRONIZED标示符,根据标示符实现方法同步。调用指令时,会检查标示是否存在,如果设置执行线程就先获取monitor,获取后才能执行方法体,执行完后释放monitor。
public synchronized void synchronisedCalculate() {
    setSum(getSum() + 1);
}
  1. 修饰静态方法
public static synchronized void syncStaticCalculate() {
    staticSum = staticSum + 1;
}
  1. 修饰代码块,修饰代码块时会通过monitorenter和monitorexit指令获取Monitor所有权和退出Monitor。
public void performSynchronisedTask() {
    synchronized (this) {
        setCount(getCount()+1);
    }
}

锁升级

除了无锁状态,Synchronized还有偏向锁、轻量级锁、重量级锁概念。

Lock

Lock提供了一种无条件的、可轮询的、定时的以及可中断的锁获取操作,所有加锁和解锁的方法都是显式的。在 Lock的实现中必须提供与内部锁相同的内存可见性语义,但在加锁语义、调度算法、顺序保证以及性能特性等方面可以有所不同。以最简单的Lock为例,lock()加锁unlock()释放锁。Lock中while(isLocked)为自旋锁,

public class Counter{

  private Lock lock = new Lock();
  private int count = 0;

  public int inc(){
    lock.lock();
    int newCount = ++count;
    lock.unlock();
    return newCount;
  }
}

public class Lock{

  private boolean isLocked = false;

  public synchronized void lock()
  throws InterruptedException{
    while(isLocked){
      wait();
    }
    isLocked = true;
  }

  public synchronized void unlock(){
    isLocked = false;
    notify();
  }
}

Synchronized和Lock区别

无法中断、无法实现非阻塞这些都是不满足于使用隐式锁synchronized的原因。Lock在加锁和内存上提供的语义与与内置锁相同,此外它还提供了一些其他功能,包括定时的锁等待、可中断的锁等待、公平性,以及实现非块结构的加锁。

  1. Lock加解锁的顺序灵活
  2. Lock可通过lockInterruptibly中断,Synchronized不可中断
  3. Lock可以被多个线程持有例如读写锁ReadWriteLock中读锁/StampedLock实现乐观读(多个线程并发读,一个线程写),Synchronized没有并发读
  4. Lock可设置为公平锁或非公平锁
  5. Lock底层原理是AQS和CAS,Synchronized底层是互斥Mutex Lock
  6. Lock的等待和唤醒通过Condition的await和signal实现,Synchronized的等待和唤醒通过wait和notify实现。都为同步队列和等待队列的切换。
  7. Lock有和Synchronized都只有一个同步队列,但是Lock有多个等待队列,Synchronized只有一个等待队列。
  8. Lock的tryLock可以实现非阻塞,Synchronized只有阻塞
  9. Lock支持定时的锁等待,可以通过tryLock设置超时机制,synchronized无超时机制
  10. Lock可定时可轮训

相关概念

  • 线程同步

同步是在互斥的基础上,通过其他机制实现访问者对资源的有序访问。

  • 同步队列

等待竞争锁的队列

  • 等待队列

等待被唤醒后进入同步队列以获取竞争锁的队列

参考:

  1. https://jenkov.com/tutorials/java-concurrency/locks.html
  2. https://cloud.tencent.com/developer/article/1465413
  3. https://www.cnblogs.com/CarpenterLee/p/7896361.html
  4. https://www.baeldung.com/java-synchronized
  5. https://segmentfault.com/a/1190000041268785
  6. https://segmentfault.com/a/1190000023150863
  7. 《Java并发编程实战》第13章