读书笔记-Java并发编程的艺术--持续更新中

news2024/12/26 10:40:58

在这里插入图片描述

文章目录

  • 第1章 并发编程的挑战
    • 1.1 上下文切换
      • 1.1.1 多线程一定快吗
      • 1.1.2 如何减少上下文切换
    • 1.2 死锁
    • 1.3 资源限制的挑战
  • 第2章 Java并发机制的底层实现原理
  • 第3章 Java内存模型
  • 第4章 Java编发编程基础
  • 第5章 Java中的锁
  • 第6章 Java并发容器和框架
  • 第7章 Java中的13个原子操作类
  • 第8章 Java中的并发工具类
  • 第9章 Java中的线程池
  • 第10章 Executor框架
  • 第11章 Java并发编程实践

第1章 并发编程的挑战

1.1 上下文切换

即时是单核处理器也支持多线程执行代码,CPU通过给每个线程分配CPU时间片来实现这个机制。时间片是CPU分配给多个线程的时间,因为时间片非常短,所以CPU通过不停地切换线程执行让我们感觉多个线程是同时执行的,时间片一般是几十毫秒。

CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后切换到下一个任务。但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再加载这个任务的状态。所以任务从保存到再加载的过程就是一次上下文切换。

1.1.1 多线程一定快吗

下面的代码演示串行和并发执行并累加操作的时间,并发执行一定比串行执行快吗?

public class ConcurrencyTest {

    private static final int count = 200000000;
    
    public static void main(String[] args) throws InterruptedException {
        concurrency(count);
        serial(count);
    }
    private static void concurrency(int count) throws InterruptedException {
        long start = System.currentTimeMillis();
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                int a = 0;
                for (int i = 0; i < count; i++) {
                    a +=5;
                }
            }
        });
        thread.start();
        int b = 0;
        for (int i = 0; i < count; i++) {
            b--;
        }
        thread.join();
        long time = System.currentTimeMillis() - start;
        System.out.println("count:" + formatToTenThousand(count) +", concurrency: " + time + "ms,b=" + b);

    }

    private static void serial(int count) {
        long start = System.currentTimeMillis();
        int a = 0;
        for (int i = 0; i < count; i++) {
            a +=5;
        }
        int b = 0;
        for (int i = 0; i < count; i++) {
            b--;
        }

        long time = System.currentTimeMillis() - start;
        System.out.println("count:" + formatToTenThousand(count) +", serial:" + time+"ms, b="+b+", a="+a);
        System.out.println();
    }

	private static String formatToTenThousand(int number) {
        double tenThousand = (double) number / 10000;
        DecimalFormat df = new DecimalFormat("0万");
        return df.format(tenThousand);
    }

}

打印:

count:20000万, concurrency: 15ms,b=-200000000
count:20000万, serial:10ms, b=-200000000, a=1000000000

上述问题的答案是不一定,结果跟机器的核数相关,如果是1核处理器,那么肯定串行快,因为省略了创建线程和多线程上下文切换的时间,如果是多核,那么一般还是并发执行更快。而且这个代码示例中并发也只是两个线程,执行的结果意义不大。
我执行了多次也没有出现书中所描述的随着count数据量增大而出现并发执行更快的趋势,反而是串行一直都更快,即使count大小为两亿。而且这个代码示例基本没有io的处理,代表意义太有限。以我的经验来看,只是涉及在cpu中的计算,而没有与数据库等资源的交互,那么使用多线程意义不大。

1.1.2 如何减少上下文切换

减少上下文切换的方法有无锁并发编程、CAS算法、使用最少线程和使用协程。

  • 无锁并发编程。多线程竞争锁时,会引起上下文切换,所以多线程处理数据时,可以用一些办法来避免使用锁、如将数据的ID取余分段,不同的线程处理不同段的数据。
  • CAS算法。Java的java.util.concurrent.atomic包使用CAS算法来更新数据,而不需要加锁。
  • 使用最少线程。避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,这样会造成大量线程都处于等待状态。
  • 协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。

java.util.concurrent.atomic包:
在这里插入图片描述

对于Java开发工程师而言,尽量使用JDK并发包提供的并发容器和工具类来解决并发问题,因为这些类都已经通过了充分的测试和优化

1.2 死锁

下面这段代码只是演示死锁的场景,在现实中你可能不会写出这样的代码。但是,在一些更为复杂的场景中,你可能会遇到这样的问题,比如t1拿到锁之后,因为一些异常情况没有释放锁(死循环)。又或者是t1拿到一个数据库锁,释放锁的时候抛出了异常,没释放掉。

public class DeadLockDemo {
    private static String A = "A";
    private static String B = "B";

    public static void main(String[] args) {
        new DeadLockDemo().deadLock();
    }

    private void deadLock() {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (A) {
                    try {
                        Thread.currentThread().sleep(2000L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (B) {
                        System.out.println("1");
                    }
                }
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (B) {
                    try {
                        Thread.currentThread().sleep(2000L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (A) {
                        System.out.println("2");
                    }
                }
            }
        });

        t1.start();
        t2.start();
    }
}

避免死锁的几个常见方法:

  • 避免一个线程同时获取多个锁。
  • 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
  • 尝试使用定时锁,使用lock.tryLock(timeout) 来替代使用内部锁机制。
  • 对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。

1.3 资源限制的挑战

(1)什么是资源限制
资源限制就是程序的执行速度受限于计算机硬件或软件资源。
例如,服务器的带宽只有2Mb/s,每个资源的下载速度是1Mb/s,系统启动10个线程下载资源,下载速度不会变成10Mb/s,所以在进行并发编程时,要考虑这些资源的限制。硬件资源限制有带宽的上传/下载速度、硬盘读写速度和CPU的处理速度。软件资源限制有数据库的连接数和socket连接数等。

(2)资源限制引发的问题
在并发编程中,将代码执行速度加快的原则是将代码中串行执行的部分变成并发执行,但是如果将某段串行的代码并发执行,因为受限于资源,仍然在串行执行,这时候程序不仅不会加快速度,反而会更慢,因为增加了上下文切换和资源调度的时间。例如,之前看到一段程序使用多线程在办公网并发地下载和处理数据时,导致CPU利用率达到100%,几个小时都不能运行完成任务,后来修改为单线程,一个小时就执行完成了。

(3)如何解决资源限制的问题
对于硬件资源限制,可以考虑使用集群并行执行程序。既然单机的资源有限制,那么就让程序在多机上运行。比如使用ODPS、Hadoop或者自己搭建服务器集群,不同的机器处理不同的数据。可以通过“数据ID%机器数”,计算得到一个机器编号,然后由对应编号的机器处理这笔数据。
对于软件资源限制,可以考虑使用资源池将资源复用。比如使用连接池将数据库和socket连接复用,或者在调用对方webservice接口获取数据时,只建立一个连接。

WebService 与 Socket 区别

(4)在资源限制情况下进行并发编程
如何在资源限制的情况下,让程序执行得更快呢?方法就是,根据不同的资源限制调整程序的并发度,比如下载文件程序依赖于两个资源—带宽和硬盘读写速度。有数据库操作时,涉及数据库连接数,如果SQL语句执行非常快,而线程的数量比数据库连接数大很多,则某些线程会被阻塞,等待数据库连接。

第2章 Java并发机制的底层实现原理

第3章 Java内存模型

第4章 Java编发编程基础

第5章 Java中的锁

第6章 Java并发容器和框架

第7章 Java中的13个原子操作类

第8章 Java中的并发工具类

第9章 Java中的线程池

第10章 Executor框架

第11章 Java并发编程实践

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

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

相关文章

java人口老龄化社区服务与管理平台源码(springboot+vue+mysql)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的人口老龄化社区服务与管理平台。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 人口老龄化…

缓存IO与直接IO

IO类型 缓存 I/O 缓存 I/O 又被称作标准 I/O&#xff0c;大多数文件系统的默认 I/O 操作都是缓存 I/O。在 Linux 的缓存 I/O 机制中&#xff0c;数据先从磁盘复制到内核空间的缓冲区&#xff0c;然后从内核空间缓冲区复制到应用程序的地址空间&#xff08;用户空间&#xff0…

阅读笔记——《未知协议状态机推断技术研究综述》

【参考文献】盛嘉杰, 牛胜杰, 陈阳, 等. 未知协议状态机推断技术研究综述[J]. 计算机与现代化, 2023 (05): 58.【注】本文仅为作者个人学习笔记&#xff0c;如有冒犯&#xff0c;请联系作者删除。 摘要 协议逆向工程&#xff08;PRE&#xff09;描述了协议的行为逻辑&#xff…

技术前沿 |【VL-BEIT:引领未来的极简单阶段多模态预训练方案】

VL-BEIT&#xff1a;引领未来的极简单阶段多模态预训练方案 引言一、VL-BEIT的基本介绍二、VL-BEIT的原理和工作方式三、VL-BEIT的特点四、VL-BEIT的应用场景五、总结与展望 引言 在人工智能蓬勃发展的今天&#xff0c;多模态预训练模型正逐渐成为研究和应用的热点。这些模型能…

Nature期刊的等级和分类

Nature期刊不用过多介绍&#xff0c;学术界人员都对其有所了解&#xff0c;可以和Science&#xff0c;Cell比肩&#xff0c;Nature旗下创办了很多子刊&#xff0c;系列期刊有一百多种&#xff0c;当然其含金量各有不同&#xff0c;nature旗下的期刊等级你是否都了解了。 Nature…

【机器学习300问】98、卷积神经网络中的卷积核到底有什么用?以边缘检测为例说明其意义。

卷积核是用于从输入数据中提取特征的关键工具。卷积核的设计直接关系到网络能够识别和学习的特征类型。本文让我以边缘检测为例&#xff0c;带大家深入理解卷积核的作用。 一、卷积核的作用 卷积核&#xff0c;又称为过滤器&#xff0c;本质上是一个小的矩阵&#xff0c;其元素…

Python图形界面(GUI)Tkinter笔记(八):用【Label()】方法制作九九乘数表

主要是使用"config()"方法来体现函数式、模块化的美好风景。把需随时要修改的控件参数定义在“config()”方法里且把它封装在一个函数中&#xff0c;这时只需对这函数内的“config()”方法作出相应的修改即可&#xff0c;无需对主代码或全部代码重新修一遍。这也是Py…

号外!号外,现在用闪侠惠递寄快递便宜啦!

你现在寄快递还是花费很多吗&#xff1f;那么究竟有没有什么办法才能便宜寄快递呢&#xff1f;现在小编告诉你&#xff0c;用闪侠惠递寄快递才是真的便宜呢&#xff01;那么我们究竟怎么才能省钱寄快递呢&#xff1f; 现在我们大家都知道闪侠惠递寄快递是非常的便宜了&#xff…

推荐一个快速开发接私活神器

文章目录 前言一、项目介绍二、项目地址三、功能介绍四、页面显示登录页面菜单管理图表展示定时任务管理用户管理代码生成 五、视频讲解总结 前言 大家好&#xff01;我是智航云科技&#xff0c;今天为大家分享一个快速开发接私活神器。 一、项目介绍 人人开源是一个提供多种…

使用cockpit管理服务器

安装cockpit yum install cockpit启用cockpit systemctl start cockpit浏览器中访问cockpit cockpit监听的端口是9090在浏览器中访问https://ip:9090/效果图 系统 日志 网络 账号

STM32 学习——2. PWM

这个项目将会不断改变pwm占空比&#xff0c;使用proteus示波器进行观察。 1. proteus8.15 原理图 2. cubemx 上图是配置外部晶振 上图配置在proteus中没啥作用&#xff0c;注意&#xff1a; 在实际开发板中&#xff0c;一定要配置它&#xff0c;不然下一次你写不进代码。 上图配…

Wpf 使用 Prism 实战开发Day24

自定义询问窗口 当需要关闭系统或进行删除数据或进行其他操作的时候&#xff0c;需要询问用户是否要执行对应的操作。那么就需要一个弹窗来给用户进行提示。 一.添加自定义询问窗口视图 (MsgView.xaml) 1.首先&#xff0c;添加一个自定义询问窗口视图 (MsgView.xaml) <Use…

如何在 Jupyter Notebook 中切换/使用 conda 虚拟环境?

参考文章&#xff1a; 【最全指南】如何在 Jupyter Notebook 中切换/使用 conda 虚拟环境&#xff1f;_多个conda环境 notebook用的哪个-CSDN博客 感谢这篇文章博主的解答&#xff0c;成功解决了我的难题。以下做一些具体的操作方法以及心得体会&#xff1a; 这里我使用的这篇…

meshshader中对三角形的组织优化

一、Mesh Shader与Index Buffer压缩 1. Mesh Shader简介 Mesh Shader是一种新型的图形着色器&#xff0c;结合了传统的顶点着色器和几何着色器的功能&#xff0c;提供了更高的灵活性和性能。它允许开发者以更自由的方式组织和处理顶点数据&#xff0c;从而优化渲染流程。 2.…

【C语言深度解剖】(15):动态内存管理和柔性数组

&#x1f921;博客主页&#xff1a;醉竺 &#x1f970;本文专栏&#xff1a;《C语言深度解剖》 &#x1f63b;欢迎关注&#xff1a;感谢大家的点赞评论关注&#xff0c;祝您学有所成&#xff01; ✨✨&#x1f49c;&#x1f49b;想要学习更多C语言深度解剖点击专栏链接查看&…

螺旋矩阵的思想

方阵类型 https://leetcode.cn/problems/spiral-matrix-ii/ lc59: 螺旋矩阵&#xff0c; 解题思路 关键点&#xff1a; 上方&#xff0c; 从左到右&#xff1b; 右侧&#xff0c;从上到下&#xff1b; 下方&#xff0c;从右到左&#xff1b; 左侧&#xff0c; 从下往上&…

c语言:将小写字母转换为大写字母

//将小写字母转换为大写字母 #include <stdio.h> #include <ctype.h> int main() { char arr[]"you are low"; int i0; while(arr[i]) { if(islower(arr[i])) { arr[i]arr[i]-32; } i; } printf("%s\n",arr); return 0; }

iCloud 照片到 Android 指南:帮助您快速将照片从 iCloud 传输到安卓手机

​ 概括 iOS 和 Android 之间的传输是一个复杂的老问题。将 iCloud 照片传输到 Android 似乎是不可能的。放心。现在的高科技已经解决了这个问题。尽管 Apple 和 Android 不提供传输工具&#xff0c;但您仍然有其他有用的选项。这篇文章与您分享了 5 个技巧。因此&#xff0c;…

Vue3实战笔记(42)—Vue + ECharts:流量数据可视化的强大组合

文章目录 前言vue3使用echarts标准demo&#xff1a;总结 前言 在前端开发中&#xff0c;数据可视化已经成为了一个不可或缺的部分。Vue.js作为一个轻量级且易于上手的渐进式JavaScript框架&#xff0c;与ECharts这个强大的数据可视化库的结合&#xff0c;使得在Vue应用中构建交…

【30天精通Prometheus:一站式监控实战指南】第8天:redis_exporter从入门到实战:安装、配置详解与生产环境搭建指南,超详细

亲爱的读者们&#x1f44b;   欢迎加入【30天精通Prometheus】专栏&#xff01;&#x1f4da; 在这里&#xff0c;我们将探索Prometheus的强大功能&#xff0c;并将其应用于实际监控中。这个专栏都将为你提供宝贵的实战经验。&#x1f680;   Prometheus是云原生和DevOps的…