synchronized,ReentrantLock,AtomicStampedReference,AtomicMarkableReference

82 阅读8分钟

synchronized 关键字是 Java 中用于实现线程同步的一种机制。它能够保证在同一时刻只有一个线程可以执行被 synchronized 修饰的代码,从而解决多线程访问共享资源时的线程安全问题。以下是对 synchronized 关键字的详细解释:

基本概念

synchronized 可以用在方法上或代码块中,分别称为同步方法和同步代码块。它通过获取对象的监视器(Monitor)锁来实现互斥访问。

使用示例

  1. 同步方法: 在实例方法前加上 synchronized 关键字,这样该方法在同一时间只能被一个线程访问。

    public synchronized void syncMethod() {
        // 线程安全的代码
    }
    
  2. 同步代码块: 在代码块前加上 synchronize 关键字,并指定一个对象作为锁,这样可以更精细地控制同步范围。

    public void syncBlock() {
        synchronized (this) {
            // 线程安全的代码
        }
    }
    

实现原理

Synchronize 的实现依赖于对象头中的监视器(Monitor),并通过以下机制保证线程安全:

  1. 对象头: 每个 Java 对象都有一个对象头,其中包含了锁状态、对象标识、GC 标记等信息。对象头是 Monitor 的基础,当线程进入同步代码块时,会尝试获取对象头中的锁。

  2. Monitor: Monitor 是一种同步工具,用于管理线程对共享资源的访问。每个对象都关联一个 Monitor。当线程进入同步代码块时,会尝试获取 Monitor 锁,获取成功后可以进入临界区执行代码。

  3. 锁的实现

    • 偏向锁:在无竞争情况下,线程持有锁时不会进行同步操作,减少了加锁和解锁的开销。
    • 轻量级锁:在存在竞争但不激烈的情况下,使用 CAS 操作尝试获取锁,适用于短期内会释放锁的场景。
    • 重量级锁:在竞争激烈的情况下,使用操作系统的互斥量来实现锁机制,适用于长时间持有锁的场景。

类锁和对象锁

  • 对象锁:使用实例方法或 synchronized (this) 来锁住当前实例对象,不同对象的实例可以并行访问。
  • 类锁:使用静态方法或 synchronized (ClassName.class) 来锁住整个类,所有对象共享同一把锁。

Monitor 的实现机制

Monitor 是通过对象头中的 Mark Word 来实现的。当一个线程进入同步块或方法时,会尝试获取 Monitor 的所有权,如果获取成功,则可以进入临界区执行代码。如果获取失败,则进入等待队列,直到锁被释放。

ReentrantLock 是 Java 中的一种显式锁(Explicit Lock),它提供了比 synchronized 关键字更灵活和强大的锁机制。它属于 java.util.concurrent.locks 包,可以显式地加锁和解锁,并提供了可重入、可中断、公平与非公平锁等特性。

基本使用

ReentrantLock 的基本使用方式如下:

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {
    private final ReentrantLock lock = new ReentrantLock();

    public void performTask() {
        lock.lock(); // 获取锁
        try {
            // 临界区代码
            System.out.println(Thread.currentThread().getName() + " is performing a task.");
        } finally {
            lock.unlock(); // 释放锁
        }
    }

    public static void main(String[] args) {
        ReentrantLockExample example = new ReentrantLockExample();
        Runnable task = example::performTask;

        Thread thread1 = new Thread(task, "Thread 1");
        Thread thread2 = new Thread(task, "Thread 2");

        thread1.start();
        thread2.start();
    }
}

主要特性

  1. 可重入性ReentrantLock 是可重入的,意味着同一个线程可以多次获取同一个锁而不会发生死锁。每次获取锁后,计数器会加1,释放锁时计数器减1,当计数器为0时锁才真正释放。

  2. 公平锁和非公平锁

    • 公平锁:线程按照请求锁的顺序获取锁,避免线程饥饿。可以通过构造函数 new ReentrantLock(true) 创建公平锁。
    • 非公平锁:线程获取锁的顺序不保证,可能会导致线程饥饿,但性能较高。默认是非公平锁,可以通过构造函数 new ReentrantLock(false) 创建非公平锁。
  3. 可中断ReentrantLock 支持响应中断,线程在等待锁时可以被中断。使用 lockInterruptibly() 方法来获取锁。

    lock.lockInterruptibly();
    
  4. 条件变量ReentrantLock 提供了条件变量(Condition),可以实现更加复杂的等待/通知机制。通过 newCondition() 方法创建条件变量。

    Condition condition = lock.newCondition();
    

公平锁和非公平锁示例

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockFairExample {
    private final ReentrantLock fairLock = new ReentrantLock(true); // 公平锁

    public void performTask() {
        fairLock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " got the lock.");
        } finally {
            fairLock.unlock();
        }
    }

    public static void main(String[] args) {
        ReentrantLockFairExample example = new ReentrantLockFairExample();
        Runnable task = example::performTask;

        for (int i = 0; i < 5; i++) {
            new Thread(task, "Thread " + i).start();
        }
    }
}

可中断锁示例

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockInterruptibleExample {
    private final ReentrantLock lock = new ReentrantLock();

    public void performTask() {
        try {
            lock.lockInterruptibly();
            try {
                System.out.println(Thread.currentThread().getName() + " is performing a task.");
            } finally {
                lock.unlock();
            }
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName() + " was interrupted.");
        }
    }

    public static void main(String[] args) {
        ReentrantLockInterruptibleExample example = new ReentrantLockInterruptibleExample();
        Runnable task = example::performTask;

        Thread thread1 = new Thread(task, "Thread 1");
        Thread thread2 = new Thread(() -> {
            example.performTask();
            thread1.interrupt();
        }, "Thread 2");

        thread1.start();
        thread2.start();
    }
}

条件变量示例

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockConditionExample {
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();

    public void awaitCondition() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " is waiting.");
            condition.await();
            System.out.println(Thread.currentThread().getName() + " is woken up.");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            lock.unlock();
        }
    }

    public void signalCondition() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " is signaling.");
            condition.signal();
        } finally {
            lock.unlock();
        }
    }

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

        Thread waiter = new Thread(example::awaitCondition, "Waiter");
        Thread signaler = new Thread(example::signalCondition, "Signaler");

        waiter.start();
        try {
            Thread.sleep(1000); // 确保 waiter 线程先执行
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        signaler.start();
    }
}

总结

ReentrantLock 提供了比 synchronized 更灵活和强大的锁机制,包括可重入性、公平锁、非公平锁、可中断锁和条件变量等特性。它适用于需要更复杂同步控制的场景。希望这个解释对你有所帮助!如果你有更多问题或需要进一步的详细信息,请随时告诉我。

在Java中,悲观锁和乐观锁是处理并发访问时常用的两种机制。它们的主要区别在于对资源竞争的态度和处理方式。下面是对这两种锁机制的详细解释:

悲观锁(Pessimistic Lock)

特点

  • 锁定策略:假设会频繁发生冲突,每次操作前都会加锁,以防止其他线程修改数据。
  • 实现方式:通过 synchronized 关键字或 ReentrantLock 来实现。
  • 适用场景:适用于冲突较多的场景,确保数据修改时的绝对安全。

示例

import java.util.concurrent.locks.ReentrantLock;

public class PessimisticLockExample {
    private final ReentrantLock lock = new ReentrantLock();

    public void performTask() {
        lock.lock(); // 获取锁
        try {
            // 临界区代码
            System.out.println(Thread.currentThread().getName() + " is performing a task.");
        } finally {
            lock.unlock(); // 释放锁
        }
    }

    public static void main(String[] args) {
        PessimisticLockExample example = new PessimisticLockExample();
        Runnable task = example::performTask;

        Thread thread1 = new Thread(task, "Thread 1");
        Thread thread2 = new Thread(task, "Thread 2");

        thread1.start();
        thread2.start();
    }
}

乐观锁(Optimistic Lock)

特点

  • 锁定策略:假设不会频繁发生冲突,每次操作前不加锁,只在提交时检查冲突。
  • 实现方式:通常通过版本号或CAS(Compare-And-Swap)操作来实现。
  • 适用场景:适用于冲突较少的场景,提高并发性能。

示例: 在数据库操作中,乐观锁通常通过增加一个版本号字段来实现。在Java中,java.util.concurrent.atomic 包提供了一些原子类,可以用于实现乐观锁。

import java.util.concurrent.atomic.AtomicInteger;

public class OptimisticLockExample {
    private final AtomicInteger version = new AtomicInteger(0);

    public void performTask() {
        int currentVersion;
        int newVersion;
        do {
            currentVersion = version.get(); // 获取当前版本
            newVersion = currentVersion + 1; // 计算新版本
        } while (!version.compareAndSet(currentVersion, newVersion)); // CAS 操作

        // 临界区代码
        System.out.println(Thread.currentThread().getName() + " updated version to " + newVersion);
    }

    public static void main(String[] args) {
        OptimisticLockExample example = new OptimisticLockExample();
        Runnable task = example::performTask;

        Thread thread1 = new Thread(task, "Thread 1");
        Thread thread2 = new Thread(task, "Thread 2");

        thread1.start();
        thread2.start();
    }
}

区别

  1. 冲突处理

    • 悲观锁:每次操作都加锁,防止并发修改,适用于高冲突场景。
    • 乐观锁:只在提交时检查冲突,适用于低冲突场景,提高并发性能。
  2. 实现方式

    • 悲观锁:依赖于同步机制,如 synchronizedReentrantLock
    • 乐观锁:通过版本号和CAS操作实现。
  3. 适用场景

    • 悲观锁:数据竞争激烈,冲突频繁的场景。
    • 乐观锁:数据竞争较少,冲突不频繁的场景。

希望这些解释能帮助你理解Java中的悲观锁和乐观锁。如果你有更多问题或需要进一步的详细信息,请随时告诉我!

在并发编程中,ABA问题是指在两个比较操作之间,一个变量的值从A变成B再变回A,这样导致程序误以为值没有改变,从而出现潜在的数据一致性问题。为了解决这个问题,Java提供了一些工具和方法,其中 AtomicStampedReferenceAtomicMarkableReference 是比较常用的解决方案。

AtomicStampedReference

特点

  • 解决方案:通过使用“标记”来跟踪对象的版本。每次更新时,不仅更新对象本身,还更新一个关联的标记,从而检测到ABA问题。
  • 使用场景:适用于需要跟踪版本的情况,比如节点的CAS操作。

示例

import java.util.concurrent.atomic.AtomicStampedReference;

public class AtomicStampedReferenceExample {
    public static void main(String[] args) {
        String initialRef = "initial";
        int initialStamp = 0;
        AtomicStampedReference<String> atomicStampedRef = new AtomicStampedReference<>(initialRef, initialStamp);

        int[] stampHolder = new int[1];
        String ref = atomicStampedRef.get(stampHolder);
        System.out.println("Initial reference: " + ref + ", stamp: " + stampHolder[0]);

        boolean isUpdated = atomicStampedRef.compareAndSet(initialRef, "newRef", initialStamp, initialStamp + 1);
        System.out.println("Updated: " + isUpdated);

        ref = atomicStampedRef.get(stampHolder);
        System.out.println("Updated reference: " + ref + ", stamp: " + stampHolder[0]);
    }
}

AtomicMarkableReference

特点

  • 解决方案:通过使用一个布尔标记来标记对象的状态。每次更新时,不仅更新对象,还更新标记,从而检测到ABA问题。
  • 使用场景:适用于需要标记对象状态的情况,比如链表节点是否被删除。

示例

import java.util.concurrent.atomic.AtomicMarkableReference;

public class AtomicMarkableReferenceExample {
    public static void main(String[] args) {
        String initialRef = "initial";
        AtomicMarkableReference<String> atomicMarkableRef = new AtomicMarkableReference<>(initialRef, false);

        boolean[] markHolder = new boolean[1];
        String ref = atomicMarkableRef.get(markHolder);
        System.out.println("Initial reference: " + ref + ", marked: " + markHolder[0]);

        boolean isUpdated = atomicMarkableRef.compareAndSet(initialRef, "newRef", false, true);
        System.out.println("Updated: " + isUpdated);

        ref = atomicMarkableRef.get(markHolder);
        System.out.println("Updated reference: " + ref + ", marked: " + markHolder[0]);
    }
}

总结

  • AtomicStampedReference:通过标记来跟踪对象的版本,适用于需要版本控制的场景。
  • AtomicMarkableReference:通过布尔标记来标记对象状态,适用于需要标记状态的场景。

两者都可以有效解决ABA问题,但具体使用哪种方式取决于你的应用场景。如果需要跟踪版本和变更次数,可以使用 AtomicStampedReference;如果需要标记对象状态,可以使用 AtomicMarkableReference