深入理解Java并发编程:从synchronized到Lock的演进

目录

引言

一、synchronized关键字基础

二、Lock接口及其实现

三、ReentrantLock实战

1. 原子类(Atomic Classes)

2. 并发集合(Concurrent Collections)

3. 线程池(ThreadPool)

4. 并发工具类(Concurrent Utilities)

六、最佳实践与注意事项


引言

在Java的广袤生态中,并发编程是一个既充满挑战又极具魅力的领域。随着多核CPU的普及和云计算、大数据等技术的兴起,如何高效地利用多核处理器进行并发计算,成为了每一个Java开发者必须面对的问题。本文将带你深入理解Java并发编程的核心概念,特别是从synchronized关键字到Lock接口的演进过程,帮助你在复杂的多线程环境中游刃有余。

一、synchronized关键字基础

synchronized是Java提供的一种内置锁机制,它既可以修饰方法,也可以修饰代码块。当某个线程进入synchronized修饰的方法或代码块时,它会自动获得该对象或类(对于静态同步方法)的锁,其他线程必须等待该线程释放锁后才能继续执行。

  • 方法锁:修饰实例方法时,锁是当前实例对象;修饰静态方法时,锁是当前类的Class对象。
  • 代码块锁:可以更细粒度地控制锁的范围,通过指定锁对象来同步代码块。

尽管synchronized简单易用,但它也存在一些局限性,如无法尝试非阻塞地获取锁、无法中断一个正在等待锁的线程等。

二、Lock接口及其实现

为了弥补synchronized的不足,Java 5引入了java.util.concurrent.locks包,其中包含了Lock接口及其多种实现(如ReentrantLock)。Lock提供了一种更为灵活和强大的锁机制。

  • 主要方法lock()unlock()tryLock()tryLock(long time, TimeUnit unit)等。
  • 特点
    • 尝试非阻塞地获取锁:通过tryLock()方法,可以在获取不到锁时立即返回,而不是阻塞等待。
    • 可中断的锁获取:支持在尝试获取锁的过程中被中断,提高了程序的响应性和灵活性。
    • 锁超时tryLock(long time, TimeUnit unit)允许在指定时间内尝试获取锁,如果超时仍未获取到锁,则返回。
    • 支持多个条件对象:通过Condition接口,Lock可以支持多个条件变量,这是synchronized方法或代码块所不具备的。
三、ReentrantLock实战

ReentrantLockLock接口的一个实现,它表示一个可重入的互斥锁。下面是一个使用ReentrantLock的简单示例,展示了如何在多线程环境下安全地访问共享资源。

import java.util.concurrent.locks.Lock;  
import java.util.concurrent.locks.ReentrantLock;  public class Counter {  private int count = 0;  private final Lock lock = new ReentrantLock();  public void increment() {  lock.lock();  try {  count++;  } finally {  lock.unlock();  }  }  public int getCount() {  lock.lock();  try {  return count;  } finally {  lock.unlock();  }  }  
}

在这个例子中,我们使用ReentrantLock来保护count变量的访问,确保在多线程环境下increment()getCount()方法的线程安全性。

在深入理解了synchronizedLock之后,我们还需要探索一些高级并发编程概念,以便在更复杂的场景中编写高效、可维护的代码。

1. 原子类(Atomic Classes)

Java的java.util.concurrent.atomic包提供了一系列原子类,这些类通过使用底层的CAS(Compare-And-Swap)操作来确保操作的原子性,无需使用锁。原子类常用于实现计数器、累加器等需要高并发更新但又不希望引入锁的场景。

常见的原子类包括AtomicIntegerAtomicLongAtomicReference等,它们提供了如incrementAndGet()compareAndSet()等原子操作方法。

import java.util.concurrent.atomic.AtomicInteger;  public class AtomicCounter {  private final AtomicInteger count = new AtomicInteger(0);  public void increment() {  count.incrementAndGet(); // 原子性递增  }  public int getCount() {  return count.get();  }  public static void main(String[] args) throws InterruptedException {  AtomicCounter counter = new AtomicCounter();  // 假设有多个线程同时调用increment  Thread[] threads = new Thread[10];  for (int i = 0; i < threads.length; i++) {  threads[i] = new Thread(() -> {  for (int j = 0; j < 1000; j++) {  counter.increment();  }  });  threads[i].start();  }  for (Thread t : threads) {  t.join(); // 等待所有线程完成  }  System.out.println("Final count: " + counter.getCount()); // 预期输出接近10000  }  
}
2. 并发集合(Concurrent Collections)

Java的java.util.concurrent包还包含了一系列并发集合,如ConcurrentHashMapCopyOnWriteArrayList等。这些集合类专为并发环境设计,提供了比同步包装器(Collections.synchronizedMap等)更高的并发级别。

  • ConcurrentHashMap:通过分段锁(在Java 8及以后版本中改为基于CAS的Node数组和链表/红黑树结构)来减少锁的竞争,提高并发性能。
  • CopyOnWriteArrayList:通过写时复制策略来避免读写冲突,适用于读多写少的场景。
    import java.util.concurrent.ConcurrentHashMap;  public class ConcurrentMapExample {  private final ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();  public void putIfAbsent(String key, int value) {  map.putIfAbsent(key, value); // 线程安全地添加元素,如果键已存在则不替换  }  public static void main(String[] args) {  ConcurrentMapExample example = new ConcurrentMapExample();  // 假设有多个线程同时调用putIfAbsent  // ...(此处省略具体的线程创建和启动代码,与上面的示例类似)  // 演示用法  example.putIfAbsent("one", 1);  example.putIfAbsent("one", 2); // 这行不会替换已存在的键  System.out.println(example.map.get("one")); // 输出 1  }  
    }

3. 线程池(ThreadPool)

线程池是一种基于池化技术来管理线程的资源池。通过复用线程,可以减少线程的创建和销毁开销,提高系统的响应速度和吞吐量。Java的java.util.concurrent包提供了Executors工厂类来创建不同类型的线程池,如固定大小的线程池(Executors.newFixedThreadPool)、可缓存的线程池(Executors.newCachedThreadPool)等。

import java.util.concurrent.ExecutorService;  
import java.util.concurrent.Executors;  public class ThreadPoolExample {  public static void main(String[] args) {  // 创建一个固定大小的线程池  ExecutorService executor = Executors.newFixedThreadPool(4);  // 提交多个任务到线程池  for (int i = 0; i < 10; i++) {  int taskId = i;  executor.submit(() -> {  System.out.println("Task " + taskId + " is running in thread " + Thread.currentThread().getName());  // 模拟任务执行时间  try {  Thread.sleep(1000);  } catch (InterruptedException e) {  Thread.currentThread().interrupt();  }  });  }  // 关闭线程池(不再接受新任务,但已提交的任务会继续执行)  executor.shutdown();  // (可选)等待所有任务完成  // while (!executor.isTerminated()) {  //     // 可以做其他事情或等待  // }  }  
}
4. 并发工具类(Concurrent Utilities)

Java的并发包还包含了一系列并发工具类,如CountDownLatchCyclicBarrierSemaphore等,这些工具类提供了强大的同步和协调机制,帮助开发者解决复杂的并发问题。

  • CountDownLatch:允许一个或多个线程等待其他线程完成一组操作。
  • CyclicBarrier:允许一组线程相互等待,直到所有线程都达到某个公共屏障点。
  • Semaphore:用于控制对共享资源的访问数量,类似于操作系统中的信号量。
    import java.util.concurrent.CountDownLatch;  public class CountDownLatchExample {  public static void main(String[] args) throws InterruptedException {  int numberOfThreads = 5;  CountDownLatch latch = new CountDownLatch(numberOfThreads);  for (int i = 0; i < numberOfThreads; i++) {  new Thread(() -> {  try {  // 模拟任务执行  Thread.sleep(1000);  } catch (InterruptedException e) {  Thread.currentThread().interrupt();  }  latch.countDown(); // 完成任务后减少计数  }).start();  }  latch.await();

六、最佳实践与注意事项
  • 避免过度同步:同步会带来性能开销,因此应尽量避免不必要的同步。只同步必须同步的代码块,减少锁的粒度。
  • 注意死锁:在多个线程相互等待对方释放锁时,可能会发生死锁。应仔细设计锁的顺序,避免循环等待。
  • 利用Java并发工具:Java的并发包提供了丰富的并发工具类,合理利用这些工具可以简化并发编程的复杂度,提高代码的可读性和可维护性。
  • 进行充分的测试:并发程序的错误往往难以复现和定位,因此应编写充分的测试用例,确保并发程序在各种情况下的正确性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/3250613.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

四川赤橙宏海商务信息咨询有限公司真实可靠吗?

在当今数字化浪潮中&#xff0c;电商行业正以前所未有的速度蓬勃发展&#xff0c;而抖音作为短视频领域的佼佼者&#xff0c;其电商服务更是异军突起&#xff0c;成为众多商家争相入驻的新蓝海。四川赤橙宏海商务信息咨询有限公司&#xff0c;正是这一领域的佼佼者&#xff0c;…

【Git标签管理】理解标签 | 创建标签 | 查看标签 | 删除标签 | 推送标签

目录 1.理解标签 2.创建标签 3.查看标签 4.删除本地仓库的标签 5.推送标签 6.删除远程仓库的标签 1.理解标签 Git提供一个打标签的功能tag&#xff0c;对某一次事务/提交的表示&#xff08;作用/意义&#xff09;。标签 tag &#xff0c;可以简单的理解为是对某次 comm…

Python调用搜索引擎Meilisearch

文章目录 简介安装初试参考文献 简介 Meilisearch 是一个 Rust 语言编写的开源搜索引擎&#xff0c;用于快速构建全文搜索。2018 年发布&#xff0c;支持中文。 特点&#xff1a; 速度至上&#xff1a;50 毫秒返回结果。相关性优先&#xff1a;最相关的结果排在前面开发者友好…

request.getParameter()与request.getAttribute()的区别

request.getParameter&#xff08;&#xff09;与request.getAttribute&#xff08;&#xff09;的区别 1、数据来源2、使用范围3、数据类型4、使用场景 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 1、数据来源 getParameter()&#xf…

C#数字医学影像系统(RIS/PACS)源码,Oracle数据库,C/S架构,运行稳定

数字医学影像系统&#xff08;RIS/PACS&#xff09;源码&#xff0c;三甲以下的医院都能满足。PACS 系统全套成品源码。 开发技术&#xff1a;C/S架构&#xff0c;C#开发语言&#xff0c;数据库服务器采用Oracle数据库。 医学影像存储与传输系统&#xff0c;融合了医学信息化…

独立站外链如何影响搜索引擎排名?

独立站的外链对搜索引擎排名有着非常重要的影响。简单来说&#xff0c;外链就像是别的网站对你的网站投的信任票。每一条外链都告诉搜索引擎&#xff1a;“这个网站的内容是有价值的&#xff0c;值得推荐。”因此&#xff0c;外链的数量和质量直接影响你的网站在搜索引擎中的排…

力扣3202:找出有效子序列的最大长度||

class Solution { public:int maximumLength(vector<int>& nums, int k) {int res0;for(int m0;m<k;m){//假设子序列两数%k之后的结果为m 相当于枚举vector<int> v(k,0);for(auto num:nums){v[num%k]v[(m-num%kk)%k]1; //知道m之后可以知道需要的子序列当前…

换了那么多台电脑,这四款高质量软件,从不离身,装机必备

Windows 10退休&#xff0c;Windows 11接棒上阵。 不过&#xff0c;不管Windows系统怎么更新&#xff0c;换多少次电脑或重装系统&#xff0c;这些软件小编总是会第一时间下载回来。 sunlight studio 这款软件堪称DIY爱好者的福音&#xff0c;它将市面上众多出色的硬件工具集…

【echarts】存在左右Y轴,多个图例切换时,图宽度会缩短(没有右轴,图宽度正常。 高亮右轴,图宽度会变窄。)- 已解决

问题描述&#xff1a; 在绘制图表时&#xff0c;左侧 Y 轴有一条曲线&#xff0c;右侧 Y 轴有三条曲线。初始化时发现&#xff0c;图表的宽度变窄了&#xff0c;这在 PC 端不太明显&#xff0c;但在移动端特别明显。 没有右轴&#xff0c;图宽度正常。 高亮右轴&#xff0c;图…

安全防御2

实验要求&#xff1a; 实验过程&#xff1a; 7&#xff0c;办公区设备可以通过电信链路和移动链路上网(多对多的NAT&#xff0c;并且需要保留一个公网IP不能用来转换)&#xff1a; 新建电信区&#xff1a; 新建移动区&#xff1a; 将对应接口划归到各自区域&#xff1a; 新建…

硬件开发——UART/I2C/SPI协议

硬件开发——UART/I2C/SPI协议 小狼http://blog.csdn.net/xiaolangyangyang 1、UART 电压&#xff1a; TTL电平&#xff1a;1&#xff1a;&#xff08;3.3V~5V&#xff09;&#xff0c;0&#xff1a;&#xff08;0V&#xff09;RS232电平&#xff1a;1&#xff1a;&#xff0…

【LeetCode:3112. 访问消失节点的最少时间 + Dijkstra】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

springboot校园网络通信系统-计算机毕业设计源码01829

摘要 在当今信息时代&#xff0c;高效的校园网络通信系统对于促进学术交流、管理学生信息和提高教学质量至关重要。该系统基于SpringBoot框架旨在构建一个高效的信息管理平台&#xff0c;为学生、管理员和教师提供全面的学术和管理功能。 系统为学生提供首页、公告消息、校园资…

微信小程序 button样式设置为图片的方法

微信小程序 button样式设置为图片的方法 background-image background-size与background-repeat与border:none;是button必须的 <view style" position: relative;"><button class"customer-service-btn" style"background-image: url(./st…

sip六大头域深度解析:From头域和To头域

From头域用于标识SIP请求的逻辑发起者&#xff0c;即发送请求的用户或设备。它通常包含用户的SIP URI&#xff08;统一资源标识符&#xff09;和可选的显示名称。 To头域用于标识请求的逻辑接收者&#xff0c;To头域的基本格式通常包括一个SIP URI&#xff0c;和显示名&#x…

matplotlib可视化梯度下降

引言 本文主要基于numpy来进行梯度下降的可视化观察&#xff0c;梯度下降本质上是一种迭代技术&#xff0c;它试图从随机猜测开始&#xff0c;为给定模型和数据点找到最佳可能的参数集。 为什么要基于numpy而不直接使用pytorch&#xff1f; 主要是因为pytorch是一个高度封装的…

去中心化技术的变革力量:探索Web3的潜力

随着区块链技术的发展和应用&#xff0c;去中心化技术正成为数字世界中的一股强大变革力量。Web3作为去中心化应用的新兴范式&#xff0c;正在重新定义人们对于数据、互联网和价值交换的认知。本文将探索去中心化技术的基本概念、Web3的核心特征及其潜力应用&#xff0c;展示其…

C语言 底层逻辑详细阐述指针(一)万字讲解 #指针是什么? #指针和指针类型 #指针的解引用 #野指针 #指针的运算 #指针和数组 #二级指针 #指针数组

文章目录 前言 序1&#xff1a;什么是内存&#xff1f; 序2&#xff1a;地址是怎么产生的&#xff1f; 一、指针是什么 1、指针变量的创建及其意义&#xff1a; 2、指针变量的大小 二、指针的解引用 三、指针类型存在的意义 四、野指针 1、什么是野指针 2、野指针的成因 a、指…

自定义注解 + Redis 实现业务的幂等性

1.实现幂等性思路 实现幂等性有两种方式&#xff1a; ⭐ 1. 在数据库层面进行幂等性处理&#xff08;数据库添加唯一约束&#xff09;. 例如&#xff1a;新增用户幂等性处理&#xff0c;username 字段可以添加唯一约束. ⭐ 2. 在应用程序层面进行幂等性处理. 而在应用程序…

JVM(day2)经典垃圾收集器

经典垃圾收集器 Serial收集 使用一个处理器或一条收集线程去完成垃圾收集工作&#xff0c;更重要的是强调在它进行垃圾收集时&#xff0c;必须暂停其他所有工作线程&#xff0c;直到它收集结束。 ParNew收集器 ParNew 收集器除了支持多线程并行收集之外&#xff0c;其他与 …