java线程同步工具:`synchronized`、`ReentrantLock`与其他并发工具的对比与应用

在Java多线程编程中,线程同步和并发控制是确保程序正确性和性能的关键。Java提供了多种同步工具,包括内置的synchronized关键字、ReentrantLock以及java.util.concurrent包中的高级并发工具。这些工具各有特点,适用于不同的并发场景。本文将详细对比synchronizedReentrantLock,并介绍其他常用的线程工具,帮助开发者根据需求选择合适的工具。

一、synchronizedReentrantLock的对比
1. 基本概念
  • synchronized:
    • Java的内置关键字,基于JVM的监视器锁(monitor lock)实现。
    • 用于方法或代码块的同步,自动获取和释放锁。
    • 是隐式锁,依赖JVM管理,代码简洁但灵活性较低。
  • ReentrantLock:
    • java.util.concurrent.locks包中的显式锁,基于AQS(AbstractQueuedSynchronizer)实现。
    • 需要手动调用lock()unlock()来获取和释放锁。
    • 提供更高的灵活性和功能,但需要开发者显式管理锁的释放。
2. 主要区别
特性synchronizedReentrantLock
锁的类型内置锁,JVM级别实现显式锁,基于AQS实现
可重入性支持(同一线程可多次获取锁)支持(同一线程可多次获取锁)
灵活性较低,自动获取/释放锁较高,支持更多高级功能
锁的获取方式自动获取,阻塞直到获取可选择阻塞(lock())或非阻塞(tryLock()
公平性非公平锁(不保证线程获取锁的顺序)可配置公平锁(new ReentrantLock(true))或非公平锁
条件变量单一条件(wait()/notify()支持多个Condition对象(更灵活的等待/通知)
中断响应不支持锁获取中断支持(lockInterruptibly()
超时机制不支持超时获取锁支持(tryLock(long, TimeUnit)
性能现代JVM优化后性能较好在高并发场景下通常更高效
释放锁的方式自动释放(离开同步块或方法)需手动调用unlock(),否则可能导致死锁
异常处理异常后自动释放锁需在finally块中显式释放锁
3. 功能对比
  • synchronized:
    • 简单易用,适合简单的同步场景。
    • 使用wait()notify()/notifyAll()实现线程间的条件等待,功能有限。
    • 无法中断等待锁的线程或设置锁超时。
    • 示例:
      class Counter {
          private int count = 0;
          public synchronized void increment() {
              count++;
          }
          public synchronized int getCount() {
              return count;
          }
      }
      
  • ReentrantLock:
    • 提供非阻塞锁获取(tryLock())、中断锁获取(lockInterruptibly())和超时机制。
    • 支持多个Condition对象,适合复杂条件等待场景。
    • 可配置公平锁,减少线程饥饿。
    • 示例:
      import java.util.concurrent.locks.ReentrantLock;
      class Counter {
          private int count = 0;
          private final ReentrantLock lock = new ReentrantLock();
          public void increment() {
              lock.lock();
              try {
                  count++;
              } finally {
                  lock.unlock();
              }
          }
          public int getCount() {
              lock.lock();
              try {
                  return count;
              } finally {
                  lock.unlock();
              }
          }
      }
      
4. 使用场景
  • synchronized: 适合简单同步场景,代码简洁,适用于共享变量保护或方法同步。
  • ReentrantLock: 适合需要高级功能的场景,如超时控制、中断响应、公平锁或多条件等待(如生产者-消费者模型)。
    • 示例:
      ReentrantLock lock = new ReentrantLock();
      Condition condition = lock.newCondition();
      void produce() {
          lock.lock();
          try {
              while (buffer.isFull()) {
                  condition.await();
              }
              buffer.add(item);
              condition.signal();
          } finally {
              lock.unlock();
          }
      }
      

追问:锁升级流程
锁升级是单向的,从低开销到高开销:无锁 → 偏向锁 → 轻量级锁 → 重量级锁。具体流程如下:

  1. 无锁状态:对象刚创建时,无任何线程访问。
  2. 偏向锁
    • 第一个线程访问时,通过CAS将线程ID写入Mark Word,进入偏向锁状态。
    • 同一线程再次访问时,直接检查线程ID,效率极高。
  3. 轻量级锁
    • 如果有其他线程竞争,偏向锁撤销,升级为轻量级锁。
    • 线程通过CAS尝试获取锁,失败后自旋。
  4. 重量级锁
    • 自旋失败或竞争激烈时,锁膨胀为重量级锁,依赖操作系统互斥锁,需要从用户态切换到内核态
    • 未获取锁的线程被阻塞,进入Monitor的等待队列。
5. 性能
  • 早期Java中,synchronized是重量级锁,性能较低。现代JVM(Java 6及以上)通过锁升级(偏向锁→轻量级锁→重量级锁)优化,性能接近ReentrantLock
  • ReentrantLock在高并发场景下通常更高效,尤其在需要公平锁或复杂条件等待时。
6. 选择建议
  • 优先使用synchronized,因为它简单且现代JVM优化良好。
  • 当需要中断、超时、公平锁或多条件等待时,选择ReentrantLock
二、其他常用线程工具

Java的java.util.concurrent包提供了丰富的并发工具,适用于更复杂的多线程场景。以下是常用的工具及其应用:

1. 线程池相关工具
  • ExecutorService / Executors:
    • 管理线程池,支持任务提交、批量执行和线程复用。
    • 常用实现:
      • Executors.newFixedThreadPool(int n): 固定大小线程池。
      • Executors.newCachedThreadPool(): 动态调整线程数。
      • Executors.newSingleThreadExecutor(): 单线程顺序执行。
      • Executors.newScheduledThreadPool(int n): 支持定时任务。
    • 使用场景:批量任务处理、后台任务、定时任务。
    • 示例:
      ExecutorService executor = Executors.newFixedThreadPool(4);
      executor.submit(() -> System.out.println("Task executed"));
      executor.shutdown();
      
  • ForkJoinPool:
    • 专为分治算法设计,基于工作窃取算法。
    • 使用场景:并行排序、矩阵计算。
    • 示例:
      ForkJoinPool pool = ForkJoinPool.commonPool();
      RecursiveTask<Integer> task = new MyTask();
      Integer result = pool.invoke(task);
      
2. 并发集合
  • ConcurrentHashMap:
    • 线程安全的哈希表,锁粒度细,适合高并发读写。
    • 使用场景:共享键值存储。
    • 示例:
      ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
      map.put("key", 1);
      map.computeIfPresent("key", (k, v) -> v + 1);
      
  • CopyOnWriteArrayList / CopyOnWriteArraySet:
    • 写时复制,适合读多写少场景。
    • 使用场景:事件监听器列表、只读配置。
    • 示例:
      CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
      list.add("item");
      
  • BlockingQueue:
    • 线程安全队列,支持阻塞操作。
    • 实现类:ArrayBlockingQueueLinkedBlockingQueuePriorityBlockingQueueSynchronousQueue
    • 使用场景:生产者-消费者模型。
    • 示例:
      BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);
      queue.put("task");
      String task = queue.take();
      
3. 锁和同步工具
  • ReadWriteLock / ReentrantReadWriteLock:
    • 读写分离锁,允许多线程读、单线程写。
    • 使用场景:缓存系统、数据库访问。
    • 示例:
      ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
      rwLock.readLock().lock();
      try {
          // 读操作
      } finally {
          rwLock.readLock().unlock();
      }
      
  • Semaphore:
    • 控制同时访问资源的线程数。
    • 使用场景:数据库连接池、限流。
    • 示例:
      Semaphore semaphore = new Semaphore(3);
      semaphore.acquire();
      try {
          // 访问资源
      } finally {
          semaphore.release();
      }
      
  • CountDownLatch:
    • 让线程等待其他线程完成操作。
    • 使用场景:多线程任务协调。
    • 示例:
      CountDownLatch latch = new CountDownLatch(3);
      new Thread(() -> {
          // 任务
          latch.countDown();
      }).start();
      latch.await();
      
  • CyclicBarrier:
    • 让线程组在屏障点同步。
    • 使用场景:多阶段计算。
    • 示例:
      CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("All reached"));
      new Thread(() -> {
          barrier.await();
      }).start();
      
  • Phaser:
    • 动态管理多阶段同步。
    • 使用场景:复杂多阶段任务。
    • 示例:
      Phaser phaser = new Phaser(3);
      new Thread(() -> {
          phaser.arriveAndAwaitAdvance();
      }).start();
      
4. 原子操作类
  • AtomicInteger / AtomicLong / AtomicBoolean:
    • 基于CAS的无锁原子操作。
    • 使用场景:计数器、状态标志。
    • 示例:
      AtomicInteger counter = new AtomicInteger(0);
      counter.incrementAndGet();
      
  • AtomicReference:
    • 原子更新复杂对象。
    • 示例:
      AtomicReference<String> ref = new AtomicReference<>("old");
      ref.compareAndSet("old", "new");
      
5. 其他工具
  • LockSupport:
    • 提供低级线程控制(park()/unpark())。
    • 使用场景:自定义锁实现。
    • 示例:
      LockSupport.park();
      LockSupport.unpark(thread);
      
  • ThreadLocal:
    • 为每个线程提供独立变量副本。
    • 使用场景:线程本地存储。
    • 示例:
      ThreadLocal<Integer> local = ThreadLocal.withInitial(() -> 0);
      local.set(1);
      
三、总结与选择建议
  • synchronizedReentrantLock:
    • synchronized适合简单同步场景,代码简洁,依赖JVM优化。
    • ReentrantLock提供高级功能(如中断、超时、公平锁),适合复杂场景。
  • 其他并发工具:
    • 线程池(ExecutorServiceForkJoinPool)适合任务管理和并行计算。
    • 并发集合(ConcurrentHashMapBlockingQueue等)优化高并发数据结构操作。
    • 高级锁和同步器(ReadWriteLockSemaphoreCountDownLatch等)提供灵活的同步控制。
    • 原子类(AtomicInteger等)适合高效无锁操作。
    • ThreadLocalLockSupport解决特定场景需求。

开发者应根据场景需求选择工具:简单同步用synchronized,复杂场景用ReentrantLock或并发包工具,高并发数据操作用并发集合,任务管理用线程池。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

算法小生Đ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值