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

Java的ConcurrentHashMap是使用的分段锁?

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

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

轻量级锁

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

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

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

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

示例代码:03a28资讯网——每日最新资讯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的实现和运行时环境)03a28资讯网——每日最新资讯28at.com

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

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

重量级锁

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

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

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

我们来看看使用synchronized可能会涉及到重量级锁的代码:03a28资讯网——每日最新资讯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块内部的锁升级为重量级锁。03a28资讯网——每日最新资讯28at.com

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

我们再来看看这个ReentrantLock来实现:03a28资讯网——每日最新资讯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内部可能会使用重量级锁。03a28资讯网——每日最新资讯28at.com

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

偏向锁

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

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

示例代码:03a28资讯网——每日最新资讯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对象作为锁。03a28资讯网——每日最新资讯28at.com

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

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

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

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

分段锁

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

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

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

我们看一个分段锁实现安全计数器的代码:03a28资讯网——每日最新资讯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数组。每个计数器都有一个与之对应的锁,这使得线程可以独立地更新不同的计数器,而不会相互干扰。当然,这个简单的例子并没有考虑一些高级的并发问题,比如锁的粒度选择、锁争用和公平性等问题。在实际应用中,你可能需要根据具体的需求和性能目标来调整设计。03a28资讯网——每日最新资讯28at.com

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

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

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

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

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

标签:
  • 热门焦点
  • 小米降噪蓝牙耳机Necklace分享:听一首歌 读懂一个故事

    小米降噪蓝牙耳机Necklace分享:听一首歌 读懂一个故事

    在今天下午的小米Civi 2新品发布会上,小米还带来了一款新的降噪蓝牙耳机Necklace,我们也在发布结束的第一时间给大家带来这款耳机的简单分享。现在大家能见到最多的蓝牙耳机
  • 2023 年的 Node.js 生态系统

    2023 年的 Node.js 生态系统

    随着技术的不断演进和创新,Node.js 在 2023 年达到了一个新的高度。Node.js 拥有一个庞大的生态系统,可以帮助开发人员更快地实现复杂的应用。本文就来看看 Node.js 最新的生
  • 服务存储设计模式:Cache-Aside模式

    服务存储设计模式:Cache-Aside模式

    Cache-Aside模式一种常用的缓存方式,通常是把数据从主存储加载到KV缓存中,加速后续的访问。在存在重复度的场景,Cache-Aside可以提升服务性能,降低底层存储的压力,缺点是缓存和底
  • 十个简单但很有用的Python装饰器

    十个简单但很有用的Python装饰器

    装饰器(Decorators)是Python中一种强大而灵活的功能,用于修改或增强函数或类的行为。装饰器本质上是一个函数,它接受另一个函数或类作为参数,并返回一个新的函数或类。它们通常用
  • 微信语音大揭秘:为什么禁止转发?

    微信语音大揭秘:为什么禁止转发?

    大家好,我是你们的小米。今天,我要和大家聊一个有趣的话题:为什么微信语音不可以转发?这是一个我们经常在日常使用中遇到的问题,也是一个让很多人好奇的问题。让我们一起来揭开这
  • WebRTC.Net库开发进阶,教你实现屏幕共享和多路复用!

    WebRTC.Net库开发进阶,教你实现屏幕共享和多路复用!

    WebRTC.Net库:让你的应用更亲民友好,实现视频通话无痛接入! 除了基本用法外,还有一些进阶用法可以更好地利用该库。自定义 STUN/TURN 服务器配置WebRTC.Net 默认使用 Google 的
  • OPPO Reno10 Pro英雄联盟定制礼盒公布:萨勒芬妮同款配色梦幻十足

    OPPO Reno10 Pro英雄联盟定制礼盒公布:萨勒芬妮同款配色梦幻十足

    5月24日,OPPO推出了全新的OPPO Reno 10系列,包含OPPO Reno10、OPPO Reno10 Pro和OPPO Reno10 Pro+三款新机,全系标配了超光影长焦镜头,是迄今为止拍照
  • 苹果140W USB-C充电器:采用氮化镓技术

    苹果140W USB-C充电器:采用氮化镓技术

    据10 月 30 日 9to5 Mac 消息报道,当苹果推出新的 MacBook Pro 2021 时,该公司还推出了新的 140W USB-C 充电器,附赠在 MacBook Pro 16 英寸机型的盒子里,也支
  • DRAM存储器10月价格下跌,NAND闪存本月价格与上月持平

    DRAM存储器10月价格下跌,NAND闪存本月价格与上月持平

    10月30日,据韩国媒体消息,自今年年初以来一直在上涨的 DRAM 存储器的交易价格仅在本月就下跌了近 10%,此次是全年首次降价,而NAND 闪存本月价格与上月持平。市
Top