唐宋丶 - Java
type: channel
link: http://www.tangsong.fun/index.php/category/JAVA/
description: Java：JDK源码、并发、多线程、数据结构
items: [JVM]Java Virtual Machine
items: [IO/NIO/AIO]IO、Socket
items: [资源][读书笔记]《Java8实战》
items: [高并发]场景下的JDBC连接异常
items: [高并发]修改Collection引发的java.util.ConcurrentModificationException异常
items: [设计模式]装饰模式
items: [数据结构][跳表]ConcurrentSkipListMap
items: [读书笔记]Java成神之路(基础篇)
items: [多线程]Java线程详解
items: [资源][开发规范]阿里巴巴《Java开发手册》

[JVM]Java Virtual Machine
type: item
date: 2023-02-22T23:42:00+08:00
description: 一、JVM定义Java Virtual Machine（Java虚拟机），它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。JVM屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行。我们一开始接触Java时第一课所下的JDK中，就包含了JVM和屏蔽操作系统差异的组件。其中JVM在各个操作系统中是一致的，而屏蔽操作系统差异的组件在不同系统上各不相同（类比与不同系统需要下载不同JDK）二、类的生命周期类的生命周期包括：类的加载→连接→初始化→使用→卸载1、类的加载：查找并加载类的二进制数据（.class文件），即把硬盘上的.class文件加载到JVM内存中2、连接：确定类与类之间的关系（如 student.setAddress(address)）（1）验证：校验.class文件正确性，防止损坏或被篡改（2）准备：给static静态变量分配内存，并赋予初始化默认值（如 static int num = 10在准备阶段num会赋予Integer的默认值0，在初始化阶段再将0修改成10）。在准备阶段，JVM只有类没有对象。Public class Student { static int age;// 准备阶段初始化为0 String name; }（3）解析：把类中的符号引用转为直接引用前期阶段，还不知道类的具体内存地址，只能用 cn.tangsong.Student来代替 Student类，这就是符号引用；而在解析阶段，JVM就可以将 cn.tangsong.Student映射成实际内存地址，这种使用内存地址来代替类方法就称为直接引用3、初始化：给static变量赋予正确的值4、使用：对象的初始化、对象的垃圾回收、对象的销毁5、卸载：即正常结束JVM结束生命周期时机：正常结束、异常结束/错误、System.exist()、操作系统异常三、JVM内存模型(Java Memory Model)JMM用于定义所有线程的共享变量（即方法外的全局变量，不能是局部变量）的访问规则。JMM将内存划分为主内存区和工作内存区：主内存区：真实存放变量工作内存区：主内存中变量的副本，供各个线程使用细节点：1、工作内存区是私有的，主内存区是公有的。 各个线程只能访问自己的私有工作内存区，不能访问其他线程的工作内存，也不能访问主内存2、不同线程之间，可以通过主内存间接的访问其他线程的工作内存Lock：将主内存中的变量，标识为一条线程的独占状态Read：将主内存中的变量，读取到工作内存中Load：将2中读取的变量拷贝到工作内存对应的变量副本中（i放到i，而不是i1、i2）Use：把工作内存中的变量副本，传递给线程去使用Assign：把线程正在使用的变量，传递给工作内存中的变量副本中Store：将工作内存中变量副本的值，传递到主内存中Write：将变量副本作为一个主内存中的变量进行存储Unlock：解决线程的独占状态3、JMM并不是真正存在，是一种规则规定了程序中各个变量的访问方式。 CPU中包括了CPU缓存、CPU寄存器、CPU主内存（RAM也叫主存）。线程映射到到操作系统上就是内核线程，工作内存和主内存映射到硬件上可以是CPU缓存、CPU寄存器、CPU主内存，是交叉关系没有固定内容。所以工作内存和主内存的数据流转在CPU层面来看是没有什么方向性的。从读取速度来说CPU寄存器>CPU缓存>主内存，所以，当一个CPU需要访问主内存时，它会将主内存的一部分读入其CPU缓存。它甚至可能将缓存的一部分读入其内部的CPU寄存器，然后对其进行操作。当CPU需要将结果写回主存时，它将把数值从其内部CPU寄存器冲到CPU缓存中，并在某个时间将数值冲回主存。4、Java线程与操作系统的关系：在Window和Linux中，Java线程是基于一对一模型实现的，使用线程的时候在Java虚拟机内部是调用当前操作系统的内核线程来完成当前任务的。每个线程映射一个内核线程，通过线程能够调用内核线程，再由操作系统内核将任务映射到各个处理器上。内核线程是由操作系统内核支持的线程，由操作系统内核完成线程的切换，内核操作调度器对线程执行调度，并将线程映射到各个处理器上，每个内核线程都可以一视为内核的一个分身，是操作系统可以处理多个任务的原因。并发操作的原因是线程映射到操作系统的内核线程，然后内核操作调度器对内核线程进行调度并映射到多处理器上。通常我们所说的并发指的是时间片轮转法的假象的并发。线程的处理瓶颈主要在于操作系统…

[IO/NIO/AIO]IO、Socket
type: item
date: 2021-03-16T11:10:00+08:00
description: IO这块其实相当于是工具类，一般来说在企业开发中基本已经集成了各种IOUtils了。可能更多的就是在文件上传/报表中用缓冲流进行读写操作。（4399校招中就有一道IO拷贝的手写代码题，但感觉更多的是考研对于异常和流关闭的处理）[...]

[资源][读书笔记]《Java8实战》
type: item
date: 2021-01-08T14:44:00+08:00
description: Java8.pdf《Java 8 in Action》[...]

[高并发]场景下的JDBC连接异常
type: item
date: 2021-09-06T15:50:00+08:00
description: 前言一、数据预热，如果是增删改的情况下，不推荐用多线程去预热，因为可能触发间隙锁甚至是表锁。二、多线程处理查的情况下，严禁将 Connection、Statement、ResultSet等数据库连接对象作为共享变量！尤其是后两者。[...]

[高并发]修改Collection引发的java.util.ConcurrentModificationException异常
type: item
date: 2021-04-09T16:51:00+08:00
description: 一般来说，java.util.ConcurrentModificationException并发修改异常是出现在高并发同时读写的线程不安全的集合中。例如：ArrayList、HashMap之类的。[...]

[设计模式]装饰模式
type: item
date: 2021-03-16T11:02:00+08:00
description: 前言：在学习IO的时候发现，InputStreamReader这个转换字节流和字符流的桥梁。这正是IO设计的核心思想：装饰模式。主要参考：《设计模式之禅》[...]

[数据结构][跳表]ConcurrentSkipListMap
type: item
date: 2021-01-24T21:28:00+08:00
description: 在Prometheus推送监控指标到Pushgateway上的时候，需要先在本地做一个缓存减少开销，这时候的缓存通常用的类型就是跳表，即 ConcurrentSkipListMap。[...]

[读书笔记]Java成神之路(基础篇)
type: item
date: 2020-11-24T16:54:00+08:00
description: Java成神之路 虽然目前只有基础篇，但是对于打地基来说可以算是典中典了，另外也出了实体书，有能力的话还是可以支持下的。一、面向对象1、面向对象和面向过程面向过程是自顶向下的编程模式。主要是把问题拆分成一个个步骤，每个步骤用函数实现。总之不要问为什么，上来先反手一个函数定义就完事了。例如实现各种排序等等。面向对象是将事务高度抽象化的编程模式。同样是要把问题拆分成一个个步骤，但是把每个步骤都抽象成对象，基于不同对象以及对象的能力进行业务逻辑的组合实现。例如造车，把车的各个部件定义成属性，然后在把车抽象成一个Car类。举一个栗子：要设计一个象棋。面向过程：重点在于怎么实现每一步（的函数）“开始—黑走—棋盘—白走—棋盘—判断—循环”面向对象：重点在于各司其职。黑白双方对象负责演算，棋盘对象负责画布，规则对象负责判断。这样优势在于不用重复造轮子，一次创建，多次使用。所以，面向过程占用资源相对低,速度相对快。而面向对象刚好相反。2、面向对象的三大特性和五大基本原则三大特性封装(Encapsulation)、继承(Inheritance)、多态(Polymorphism)就不在多赘述了。稍微提一句：我认为，多态应该是一种运行期特性，Java 中的重写是多态的体现。不过也有人提出重载是一种静态多态的想法，这个问题在 StackOverflow 等网站上有很多人讨论，但是并没有什么定论。我更加倾向于重载不是多态。（可以看下文的第3点）重点介绍以下五大基本原则：单一职责原则(Single-Responsibility)：一个类最好只做一件事，只有一个引起它的变话。 职责多变化就多，职责依赖也多，就会产生影响，从而损伤内聚性和耦合度。开放封闭原则(Open-Closed)：对扩展开放，对修改封闭。 其核心思想就是对抽象编程，而不对具体编程。因为抽象相对稳定，让类依赖于固定的抽象，所以修改就是封闭的；而通过面向对象的继承和多态机制，又可实现对抽象类的继承，通过覆写其方法来改变固有行为，实现新的拓展方法，所以就是开放的。Liskov 替换原则(Liskov-Substitution)：子类必须能够替换其基类，从而在运行期内能够识别子类。是保证继承复用的基础。 Liskov 替换原则能够保证系统具有良好的拓展性，同时实现基于多态的抽象机制，能够减少代码冗余，避免运行期的类型判别。依赖倒置原则(Dependecy-Inversion)：高层模块不依赖于底层模块，二者都同依赖于抽象；抽象不依赖于具体，具体依赖于抽象。依赖于抽象，就是对接口编程，不要对实现编程。 即MVC的分层思想：在依赖之间定义一个抽象的接口(Service)使得高层模块(Controller)调用接口，而底层模块(ServiceImpl)实现接口的定义，以此来有效控制耦合关系，达到依赖于抽象的设计目标。接口隔离原则(Interface-Segregation)：使用多个小而专门的接口，而不要使用一个大的总的接口。 即，接口应该内聚，避免“胖”接口。业务中可以用多重继承分离方法，通过接口多继承来实现客户的需求。3、重载(Overloading)和重写(Overriding)两者的概念和区别在之前的《Java就业面试题大全》的17中都有体现，其中还有一题华为的面试题：为什么函数不能根据返回类型来区分重载？这里从不同周期的角度来区分它们：重载是一个编译器概念，重写是一个运行期间的概念。重载遵循所谓的“编译期绑定”，即在编译时根据参数变量的类型判断应该调用哪个方法。重写遵循所谓的“运行期绑定”，即在运行的时候，根据引用变量所指向的实际对象的类型来调用方法。因为编译期已经确定调用哪个方法，所以重载并不是多态。而重写是多态。重载是一种语言特性，是一种语法规则，与多态无关，与面向对象也无关。4、Java如何实现的平台无关性的无关性其实就是独立性，在今年的4399笔试题里面也遇到过这题：如何理解java平台的独立性？，当时主要是从体系结构无关和可移植两方面来说明。今天就重新再认识认识。无关性，或者说独立性。就是一种语言在计算机上的运行不受平台约束，一次编译，到处执行（Wrete One, Run Anywhere）。往细的来说，就是用Java创建的可执行二进制程序，能够不加改变的运行于多个平台。因此可以运行在各种嵌入式设备上（打印机、扫描仪等），好处也就不言而喻。虽然 Java 语言是平台无关的，但JVM虚拟机 却是平台有关的，不同的操作系统上面要安装对应的 JVM。 Java 虚拟机充当了桥梁，将 Class 文件转成对应平台的二进制文件等，从而屏蔽了底层操作系统和硬件的差异。而所有Java 文件都要编译成统一的 Java Class 文件，它可以在任意平台创建并被平台的Java 虚拟机装载并执行，从…

[多线程]Java线程详解
type: item
date: 2020-01-19T19:57:00+08:00
description: 整理了网上大部分java线程相关的知识，并加入一些自己的理解以及代码示例。线程概述一个Thread类实例只是一个对象，像Java中的任何其他对象一样，具有变量和方法，生死于堆上。Java中，每个线程都有一个调用栈，即使不在程序中创建任何新的线程，线程也在后台运行着。一个Java应用总是从main()方法开始运行，mian()方法运行在一个线程内，它被称为主线程。一旦创建一个新的线程，就产生一个新的调用栈。线程总体分两类：用户线程和守护线程。当所有用户线程执行完毕的时候，JVM自动关闭。但是守候线程却不独立于JVM，守候线程一般是由操作系统或者用户自己创建的，随着JVM关闭也随之结束。用个比较通俗的比如，任何一个守护线程都是整个JVM中所有非守护线程的保姆：只要当前JVM实例中尚存在任何一个非守护线程没有结束，守护线程就全部工作；只有当最后一个非守护线程结束时，守护线程随着JVM一同结束工作。Daemon的作用是为其他线程的运行提供便利服务，守护线程最典型的应用就是 GC (垃圾回收器)，它就是一个很称职的守护者。（后文有守护线程详解）Java线程：创建与启动一、定义线程1、继承（extends）java.lang.Thread类。此类中有个run()方法，应该注意其用法： private Runnable target; @Override public void run() { if (target != null) { target.run(); } }Thread的子类应该重写该方法，否则，该方法不执行任何操作并返回。2、实现（implements）java.lang.Runnable接口。源码如下：@FunctionalInterface public interface Runnable { public abstract void run(); }使用实现接口Runnable的对象创建一个线程时，启动该线程将导致在独立执行的线程中调用对象实现的run方法。方法run的常规协定是，它可能执行任何所需的操作。实现Runnable接口所具有的优势：1、避免Java单继承的问题2、适合多线程处理同一资源3、代码可以被多线程共享，数据独立，很容易实现资源共享二、实例化线程1、如果是扩展java.lang.Thread类的线程，则直接new即可。2、如果是实现了java.lang.Runnable接口的类，则用Thread的构造方法：Thread(Runnable target) Thread(Runnable target, String name) Thread(ThreadGroup group, Runnable target) Thread(ThreadGroup group, Runnable target, String name) Thread(ThreadGroup group, Runnable target, String name, long stackSize)即在xxxDemo的start()中：Thread thread= new Thread(); Thread runnable= new Thread(new Runnable(){ @Override public void run() { ... } );一般用法：Thread thread = new Thread(this,threadName);若继承Thread类或实现Runnable接口，只重写了 run()方法，那么只需要如下这般创建：Thread thread = new ThreadDemo(); Thread runnable = new Thread(new RunnableDemo());三、启动线程通俗来讲，在线程中，把需要处理的代码放到run()方法中，用户自定义的 public void start()会写入Thread的thread.start()方法启动线程，该方法将自动调用run()方法，这个由java的内存机制规定的。并且run()方法必需是public访问权限，返回值类型为void。Thread.start()源码如下：public synchronized void start() { /** * This method is not invoked for the main method thread or "system" * group threads created/set up by the VM. Any new functionality added * to this method in the future may have to also be added to the VM. *…

[资源][开发规范]阿里巴巴《Java开发手册》
type: item
date: 2020-01-16T15:18:00+08:00
description: 《Java开发手册（嵩山版）》1.7.0版（2020.08.03）制定团队：阿里巴巴与Java社区开发者[...]