当前位置:首页 > 科技  > 软件

Java的ConcurrentHashMap是使用的分段锁?

来源: 责编: 时间:2024-02-01 12:51:54 274观看
导读了不起在前两天的时候给大家讲述了关于这个 Java 的公平锁,非公平锁,共享锁,独占锁,乐观锁,悲观锁,递归锁,读写锁,今天我们就再来了解一下其他的锁,比如,轻量级锁,重量级锁,偏向锁,以及分段锁。轻量级锁Java的轻量级锁(Lightweight

了不起在前两天的时候给大家讲述了关于这个 Java 的公平锁,非公平锁,共享锁,独占锁,乐观锁,悲观锁,递归锁,读写锁,今天我们就再来了解一下其他的锁,比如,轻量级锁,重量级锁,偏向锁,以及分段锁。vIN28资讯网——每日最新资讯28at.com

轻量级锁

Java的轻量级锁(Lightweight Locking)是Java虚拟机(JVM)中的一种优化机制,用于减少多线程竞争时的性能开销。在多线程环境中,当多个线程尝试同时访问共享资源时,通常需要某种形式的同步以防止数据不一致。Java提供了多种同步机制,如synchronized关键字和ReentrantLock,但在高并发场景下,这些机制可能导致性能瓶颈。vIN28资讯网——每日最新资讯28at.com

轻量级锁是JVM中的一种锁策略,它在没有多线程竞争的情况下提供了较低的开销,同时在竞争变得激烈时能够自动升级到更重量级的锁。这种策略的目标是在不需要时避免昂贵的线程阻塞操作。vIN28资讯网——每日最新资讯28at.com

不过这种锁并不是通过Java语言直接暴露给开发者的API,而是JVM在运行时根据需要自动应用的。因此,我们不能直接通过Java代码来实现一个轻量级锁。vIN28资讯网——每日最新资讯28at.com

但是我们可以使用Java提供的synchronized关键字或java.util.concurrent.locks.Lock接口(及其实现类,如ReentrantLock)来创建同步代码块或方法,这些同步机制在底层可能会被JVM优化为使用轻量级锁。vIN28资讯网——每日最新资讯28at.com

示例代码:vIN28资讯网——每日最新资讯28at.com

public class LightweightLockExample {        private Object lock = new Object();      private int sharedData;        public void incrementSharedData() {          synchronized (lock) {              sharedData++;          }      }        public int getSharedData() {          synchronized (lock) {              return sharedData;          }      }        public static void main(String[] args) {          LightweightLockExample example = new LightweightLockExample();            // 使用多个线程来访问共享数据          for (int i = 0; i < 10; i++) {              new Thread(() -> {                  for (int j = 0; j < 1000; j++) {                      example.incrementSharedData();                  }              }).start();          }            // 等待所有线程执行完毕          try {              Thread.sleep(2000);          } catch (InterruptedException e) {              e.printStackTrace();          }            // 输出共享数据的最终值          System.out.println("Final shared data value: " + example.getSharedData());      }  }

这个示例中的同步块在JVM内部可能会使用轻量级锁(具体是否使用取决于JVM的实现和运行时环境)vIN28资讯网——每日最新资讯28at.com

在这个例子中,我们有一个sharedData变量,多个线程可能会同时访问它。我们使用synchronized块来确保每次只有一个线程能够修改sharedData。在JVM内部,这些synchronized块可能会使用轻量级锁来优化同步性能。vIN28资讯网——每日最新资讯28at.com

请注意,这个例子只是为了演示如何使用synchronized关键字,并不能保证JVM一定会使用轻量级锁。实际上,JVM可能会根据运行时的情况选择使用偏向锁、轻量级锁或重量级锁。vIN28资讯网——每日最新资讯28at.com

重量级锁

在Java中,重量级锁(Heavyweight Locking)是相对于轻量级锁而言的,它涉及到线程阻塞和操作系统级别的线程调度。当轻量级锁或偏向锁不足以解决线程间的竞争时,JVM会升级锁为重量级锁。vIN28资讯网——每日最新资讯28at.com

重量级锁通常是通过操作系统提供的互斥原语(如互斥量、信号量等)来实现的。当一个线程尝试获取已经被其他线程持有的重量级锁时,它会被阻塞(即挂起),直到持有锁的线程释放该锁。在阻塞期间,线程不会消耗CPU资源,但会导致上下文切换的开销,因为操作系统需要保存和恢复线程的上下文信息。vIN28资讯网——每日最新资讯28at.com

在Java中,synchronized关键字和java.util.concurrent.locks.ReentrantLock都可以导致重量级锁的使用,尤其是在高并发和激烈竞争的场景下。vIN28资讯网——每日最新资讯28at.com

我们来看看使用synchronized可能会涉及到重量级锁的代码:vIN28资讯网——每日最新资讯28at.com

public class HeavyweightLockExample {        private final Object lock = new Object();      private int counter;        public void increment() {          synchronized (lock) {              counter++;          }      }        public int getCounter() {          synchronized (lock) {              return counter;          }      }        public static void main(String[] args) {          HeavyweightLockExample example = new HeavyweightLockExample();            // 创建多个线程同时访问共享资源          for (int i = 0; i < 10; i++) {              new Thread(() -> {                  for (int j = 0; j < 10000; j++) {                      example.increment();                  }              }).start();          }            // 等待所有线程完成          try {              Thread.sleep(2000);          } catch (InterruptedException e) {              e.printStackTrace();          }            // 输出计数器的值          System.out.println("Final counter value: " + example.getCounter());      }  }

在这个示例中,多个线程同时访问counter变量,并使用synchronized块来确保每次只有一个线程能够修改它。如果线程间的竞争非常激烈,JVM可能会将synchronized块内部的锁升级为重量级锁。vIN28资讯网——每日最新资讯28at.com

我们说的是可能哈,毕竟内部操作还是由 JVM 具体来操控的。vIN28资讯网——每日最新资讯28at.com

我们再来看看这个ReentrantLock来实现:vIN28资讯网——每日最新资讯28at.com

import java.util.concurrent.locks.ReentrantLock;    public class ReentrantLockExample {        private final ReentrantLock lock = new ReentrantLock();      private int counter;        public void increment() {          lock.lock(); // 获取锁          try {              counter++;          } finally {              lock.unlock(); // 释放锁          }      }        public int getCounter() {          return counter;      }        public static void main(String[] args) {          // 类似上面的示例,创建线程并访问共享资源      }  }

在这个示例中,ReentrantLock被用来同步对counter变量的访问。如果锁竞争激烈,ReentrantLock内部可能会使用重量级锁。vIN28资讯网——每日最新资讯28at.com

需要注意的是,重量级锁的使用会带来较大的性能开销,因此在设计并发系统时应尽量通过减少锁竞争、使用更细粒度的锁、使用无锁数据结构等方式来避免重量级锁的使用。vIN28资讯网——每日最新资讯28at.com

偏向锁

在Java中,偏向锁(Biased Locking)是Java虚拟机(JVM)为了提高无竞争情况下的性能而引入的一种锁优化机制。它的基本思想是,如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word的结构也变为偏向锁结构,当这个线程再次请求锁时,无需再做任何同步操作,即获取锁的过程只需要检查Mark Word的锁标记位为偏向锁以及当前线程ID等于Mark Word的Thread ID即可,这样就省去了大量有关锁申请的操作。vIN28资讯网——每日最新资讯28at.com

他和轻量级锁和重量级锁一样,并不是直接通过Java代码来控制的,而是由JVM在运行时自动进行的。因此,你不能直接编写Java代码来显式地使用偏向锁。不过,你可以编写一个使用synchronized关键字的简单示例,JVM可能会自动将其优化为使用偏向锁(取决于JVM的实现和运行时的配置)。vIN28资讯网——每日最新资讯28at.com

示例代码:vIN28资讯网——每日最新资讯28at.com

public class BiasedLockingExample {        // 这个对象用作同步的锁      private final Object lock = new Object();            // 共享资源      private int sharedData;        // 使用synchronized关键字进行同步的方法      public synchronized void synchronizedMethod() {          sharedData++;      }        // 使用对象锁进行同步的方法      public void lockedMethod() {          synchronized (lock) {              sharedData += 2;          }      }        public static void main(String[] args) throws InterruptedException {          // 创建示例对象          BiasedLockingExample example = new BiasedLockingExample();            // 使用Lambda表达式和Stream API创建并启动多个线程          IntStream.range(0, 10).forEach(i -> {              new Thread(() -> {                  // 每个线程多次调用同步方法                  for (int j = 0; j < 10000; j++) {                      example.synchronizedMethod();                      example.lockedMethod();                  }              }).start();          });            // 让主线程睡眠一段时间,等待其他线程执行完毕          Thread.sleep(2000);            // 输出共享数据的最终值          System.out.println("Final sharedData value: " + example.sharedData);      }  }

在这个示例中,我们有一个BiasedLockingExample类,它有两个同步方法:synchronizedMethod和lockedMethod。synchronizedMethod是一个实例同步方法,它隐式地使用this作为锁对象。lockedMethod是一个使用显式对象锁的方法,它使用lock对象作为锁。vIN28资讯网——每日最新资讯28at.com

当多个线程调用这些方法时,JVM可能会观察到只有一个线程在反复获取同一个锁,并且没有其他线程竞争该锁。在这种情况下,JVM可能会将锁偏向到这个线程,以减少获取和释放锁的开销。vIN28资讯网——每日最新资讯28at.com

然而,请注意以下几点:vIN28资讯网——每日最新资讯28at.com

  • 偏向锁的使用是由JVM动态决定的,你不能强制JVM使用偏向锁。
  • 在高并发环境下,如果锁竞争激烈,偏向锁可能会被撤销并升级到更重的锁状态,如轻量级锁或重量级锁。
  • 偏向锁适用于锁被同一个线程多次获取的场景。如果锁被多个线程频繁地争用,偏向锁可能不是最优的选择。

由于偏向锁是透明的优化,因此你不需要在代码中做任何特殊的事情来利用它。只需编写正常的同步代码,让JVM来决定是否应用偏向锁优化。vIN28资讯网——每日最新资讯28at.com

分段锁

在Java中,"分段锁"并不是一个官方的术语,但它通常被用来描述一种并发控制策略,其中数据结构或资源被分成多个段,并且每个段都有自己的锁。这种策略的目的是提高并发性能,允许多个线程同时访问不同的段,而不会相互阻塞。vIN28资讯网——每日最新资讯28at.com

而在 Java 里面的经典例子则是ConcurrentHashMap,在早期的ConcurrentHashMap实现中,内部采用了一个称为Segment的类来表示哈希表的各个段,每个Segment对象都持有一个锁。这种设计允许多个线程同时读写哈希表的不同部分,而不会产生锁竞争,从而提高了并发性能。vIN28资讯网——每日最新资讯28at.com

然而,需要注意的是,从Java 8开始,ConcurrentHashMap的内部实现发生了重大变化。它不再使用Segment,而是采用了一种基于CAS(Compare-and-Swap)操作和Node数组的新设计,以及红黑树来处理哈希冲突。这种新设计提供了更高的并发性和更好的性能。尽管如此,"分段锁"这个概念仍然可以用来描述这种将数据结构分成多个可独立锁定的部分的通用策略。vIN28资讯网——每日最新资讯28at.com

我们看一个分段锁实现安全计数器的代码:vIN28资讯网——每日最新资讯28at.com

import java.util.concurrent.locks.Lock;  import java.util.concurrent.locks.ReentrantLock;    public class SegmentedCounter {      private final int size;      private final Lock[] locks;      private final int[] counters;        public SegmentedCounter(int size) {          this.size = size;          this.locks = new Lock[size];          this.counters = new int[size];          for (int i = 0; i < size; i++) {              locks[i] = new ReentrantLock();          }      }        public void increment(int index) {          locks[index].lock();          try {              counters[index]++;          } finally {              locks[index].unlock();          }      }        public int getValue(int index) {          locks[index].lock();          try {              return counters[index];          } finally {              locks[index].unlock();          }      }  }

在这个例子中,SegmentedCounter类有一个counters数组和一个locks数组。每个计数器都有一个与之对应的锁,这使得线程可以独立地更新不同的计数器,而不会相互干扰。当然,这个简单的例子并没有考虑一些高级的并发问题,比如锁的粒度选择、锁争用和公平性等问题。在实际应用中,你可能需要根据具体的需求和性能目标来调整设计。vIN28资讯网——每日最新资讯28at.com

所以,你学会了么?vIN28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-70469-0.htmlJava的ConcurrentHashMap是使用的分段锁?

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 字节码增强技术,不止有 Java Proxy、 Cglib 和 Javassist 还有 Byte Buddy

下一篇: 十个Python编程小技巧

标签:
  • 热门焦点
  • 中兴AX5400Pro+上手体验:再升级 双2.5G网口+USB 3.0这次全都有

    2021年11月的时候,中兴先后发布了两款路由器产品,中兴AX5400和中兴AX5400 Pro,从产品命名上就不难看出这是隶属于同一系列的,但在外观设计上这两款产品可以说是完全没一点关系
  • 2023年Q2用户偏好榜:12+256G版本成新主流

    3月份的性能榜、性价比榜和好评榜之后,就要轮到2023年的第二季度偏好榜了,上半年的新机潮已经过去,最明显的肯定就是大内存和存储的机型了,另外部分中端机也取消了屏幕塑料支架
  • 线程通讯的三种方法!通俗易懂

    线程通信是指多个线程之间通过某种机制进行协调和交互,例如,线程等待和通知机制就是线程通讯的主要手段之一。 在 Java 中,线程等待和通知的实现手段有以下几种方式:Object 类下
  • 让我们一起聊聊文件的操作

    文件【1】文件是什么?文件是保存数据的地方,是数据源的一种,比如大家经常使用的word文档、txt文件、excel文件、jpg文件...都是文件。文件最主要的作用就是保存数据,它既可以保
  • 在线图片编辑器,支持PSD解析、AI抠图等

    自从我上次分享一个人开发仿造稿定设计的图片编辑器到现在,不知不觉已过去一年时间了,期间我经历了裁员失业、面试找工作碰壁,寒冬下一直没有很好地履行计划.....这些就放在日
  • JVM优化:实战OutOfMemoryError异常

    一、Java堆溢出堆内存中主要存放对象、数组等,只要不断地创建这些对象,并且保证 GC Roots 到对象之间有可达路径来避免垃 圾收集回收机制清除这些对象,当这些对象所占空间超过
  • 三星显示已开始为AR设备研发硅基LED微显示屏

    7月18日消息,据外媒报道,随着苹果首款头显产品Vision Pro在6月份正式推出,AR/VR/MR等头显产品也就将成为各大公司下一个重要的竞争领域,对显示屏这一关
  • 2299元起!iQOO Pad明晚首销:性能最强天玑平板

    5月23日,iQOO如期举行了新品发布会,除了首发安卓最强旗舰处理器的iQOO Neo8系列新机外,还在发布会上推出了旗下首款平板电脑——iQOO Pad,其最大的卖点
  • OPPO K11搭载高性能石墨散热系统:旗舰同款 性能凉爽释放

    日前OPPO官方宣布,将于7月25日14:30举办新品发布会,届时全新的OPPO K11将正式与大家见面,将主打旗舰影像,和同档位竞品相比,其最大的卖点就是将配备索尼
Top