mghio

Java 搬运工 & 终身学习者


  • 首页

  • 标签

  • 分类

  • 归档

  • 关于

  • 搜索

Java 线程池(一)

发表于 2019-11-23 | 分类于 Java , 线程池 |
字数统计: 3.9k 字 | 阅读时长 ≈ 13 分钟

线程池简介

使用线程池可以很好的提高性能,线程池在运行之初就会创建一定数量的空闲线程,我们将一个任务提交给线程池,线程池就会使用一个空闲的线程来执行这个任务,该任务执行完后,该线程不会死亡,而是再次变成空闲状态返回线程池,等待下一个任务的到来。在使用线程池时,我们把要执行的任务提交给整个线程池,而不是提交给某个线程,线程池拿到提交的任务后,会在内部寻找是否还有空闲的线程,如果有,就将这个任务提交给某个空闲的线程,虽然一个线程同一时刻只能执行一个任务,但是我们可以向线程池提交多个任务。合理使用线程池有以下几个优点:
① 降低资源消耗 多线程运行期间,系统不断的启动和关闭新线程,成本高,会过度消耗系统资源,通过重用存在的线程,减少对象创建、消亡的开销
② 提高响应速度 当有任务到达时,任务可以不需要等待线程的创建,可以直接从线程池中取出空闲的线程来执行任务
③ 方便线程管理 线程对计算机来说是很稀缺的资源,如果让他无限制创建,它不仅消耗系统的资源,还会降低系统的稳定性,我们使用线程池后可以统一进行分配和监控
谈到线程池就会想到池化技术,核心思想就是把宝贵的资源放到一个池子中,每次要使用都从池子里面取,用完之后又放回池子让别人用。那么线程池在 Java 中是如何实现的呢?

Java 四种线程池

在 Java 中 Executors 工具类给我们提供了四种不同使用场景的线程池的创建方法,分别为:

  1. newSingleThreadExecutor 只有一个线程来执行任务,适用于有顺序的任务的应用场景。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它,它可以保证任务按照指定顺序(FIFO,LIFO)执行,它还有可以指定线程工厂(ThreadFactory)的重载方法,可以自定义线程的创建行为
  2. newFixedThreadPool 固定线程数的线程池,只有核心线程,核心线程的即为最大的线程数量,没有非核心线程。每次提交一个任务就创建一个线程,直到达到线程池的最大大小。线程池一旦达到最大值就会保持不变,如果当中的某个线程因为异常而结束,那么线程池会新建一个线程加入到线程池中。它还可以控制线程的最大并发数,超出的线程会在阻塞队列(LinkedBlockingQueue)中等待,同样它也有可以指定线程工厂(ThreadFactory)的重载方法,可以自定义线程的创建行为。
  3. newCachedThreadPool 创建一个可缓存线程池,最大的线程个数为 2^31 - 1(Integer.MAX_VALUE),可以认为是无限大,若无可回收,则新建线程,如果线程池的大小超出了处理任务所需要的线程,那么就会回收部分空闲(60s 不执行任务)的线程。
  4. newScheduledThreadPool 周期性执行任务的线程池,按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程,非核心线程的大小也为无限大(Integer.MAX_VALUE:2^31 - 1),适用于执行周期性的任务。
阅读全文 »

深挖 HashMap

发表于 2019-11-14 | 分类于 Java , 原理 |
字数统计: 2.6k 字 | 阅读时长 ≈ 9 分钟

1.1 前言

做过 java 开发的朋友们相信都很熟悉 HashMap 这个类,它是一个基于 hashing 原理用于存储 Key-Value 键值对的集合,其中的每一个键也叫做 Entry,这些键分别存储在一个数组当中,系统会根据 hash 方法来计算出 Key-Value 的存储位置,可以通过 key 快速存取 value。
HashMap 基于 hashing 原理,当我们将一个键值对(Key-Value) 传入 put 方法时,它将调用这个 key 的 hashcode 方法计算出 key 的 hashcode 值,然后根据这个 hashcode 值来定位其存放数组的位置来存储对象(HashMap 使用链表来解决碰撞问题,当其发生碰撞了,对象将会存储在链表的下一个节点中,在链表的每个节点中存储 Entry 对象,在 JDK 1.8+ 中,当链表的节点个数超过一定值时会转为红黑树来进行存储),当通过 get 方法传入一个 key 来获取其对应的值时,也是先通过 key 的 hashcode 方法来定位其存储在数组的位置,然后通过键对象的 eqauls 方法找到对应的 value 值。接下来让我们看看其内部的一些实现细节。(PS:以下代码分析都是基于 JDK 1.8)

1.2 为什么容量始终是 2 的整数次幂

因为获取 key 在数组中对应的下标是通过 key 的哈希值与数组的长度减一进行与运算来确定的(tab[(n - 1) & hash])。当数组的长度 n 为 2 的整数次幂,这样进行 n - 1 运算后,之前为 1 的位后面全是 1 ,这样就能保证 (n - 1) & hash 后相应位的值既可能是 1 又可能是 0 ,这完全取决于 key 的哈希值,这样就能保证散列的均匀,同时与运算(位运算)效率高。如果数组的长度 n 不是 2 的整数次幂,会造成更多的 hash 冲突。HashMap 提供了如下四个重载的构造方法来满足不同的使用场景:

  1. 无参构造:HashMap(),使用该方法表示全部使用 HashMap 的默认配置参数
  2. 指定容量初始值构造:HashMap(int initialCapacity),在初始化 HashMap 时指定其容量大小
  3. 指定容量初始值和扩容因子构造:HashMap(int initialCapacity, float loadFactor),使用自定义初始化容量和扩容因子
  4. 通过 Map 来构造 HashMap:HashMap(Map<? extends K, ? extends V> m),使用默认的扩容因子,其容量大小有传入的 Map 大小来决定
    阅读全文 »

同步工具类

发表于 2019-11-03 | 分类于 Java , 并发 |
字数统计: 3.4k 字 | 阅读时长 ≈ 12 分钟

1.1 前言

同步工具类可以是任何一个对象,只要它根据其自身的状态来协调线程的控制流。在容器中,有些也可以作为同步工具类,其它类型的同步工具类还包括闭锁(Latch)、信号量(Semaphore)以及栅栏(Barrier)。阻塞队列(eg: BlockQueue)是一种独特的类:它们不仅能作为保存对象的容器,还能协调生产者和消费者之间的控制流,因为它提供的 take 和 put 等方法将会阻塞,直到队列达到期望的状态。所有的同步工具类都包含一些特定的属性:它们封装了一些状态,这些状态将决定同步工具类的线程是继续执行还是等待,此外还提供了一些方法对其状态进行操作,以及另一些方法用于高效地等待同步工具类进入到预期状态。

1.2 闭锁

闭锁是一种同步工具类,可以延迟线程的进度直到其到达终止状态。闭锁的作用相当于一扇门:在闭锁到达结束状态之前,这扇门一直是关闭的,并且没有任何线程能通过,当到达结束状态时,这扇门会打开并允许所有线程通过。当闭锁到达结束状态后,将不会再次改变状态,因此这扇门将永远保持打开状态。闭锁可以用来确保某些活动直到其它活动都完成后才继续执行。比如:

  • 确保某个计算机在其需要的所有资源初始化后才能继续执行。
  • 确保某个服务在其依赖的所有服务都已经启动后才启动。
  • 等待直到某个操作的所有参与者都就绪后再继续执行。
    阅读全文 »

Java 运行时数据区域

发表于 2019-10-26 | 分类于 Java , JVM |
字数统计: 4.9k 字 | 阅读时长 ≈ 17 分钟

1.1 为什么要进行内存区域划分

JVM规范 规定,JVM 在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途。以及创建和销毁的时间,有的区域随着虚拟机进程的启动就存在了,而有些区域则依赖用户线程的启动和结束而建立和销毁。JVM 规范对 JVM 定义了运行时统一的内存划分规范,统一了标准,类似于 JDBC 规范一样。JVM 也有许多厂商的不同产品。比如下面的这些:

厂商 JVM
Oracle-SUN Hotspot
Oracle JRocket
IBM J9 JVM
阿里 Taobao JVM

其内存区域划分规范对于 JVM 的含义类似于我们 Java 中的接口,都是起到了规范的作用,JVM 是一台可以运行 Java 应用程序的抽象的计算机。在 JVM 中存在三个重要的概念:

  • JVM 规范:它定义了虚拟机运行的规范,但是由 Oracle(SUN)或者其它厂商实现
  • Java 运行时环境(JRE:Java Runtime Environment):它是 JVM 规范的具体实现
  • JVM 实例:编写好 Java 代码之后,运行 Java 程序,此时就会创建 JMV 实例

对于 Java 程序员来说,在虚拟机自动内存管理机制的帮助下,不再需要为每一个对象去编写内存释放的代码,不要像 C 或者 C++ 要时刻注意着内存泄漏和内存溢出的问题,这种由虚拟机去管理一切看起来都很美好。不过,也正是因为 Java 设计者把内存控制全部交给了 JVM,一旦出现了内存泄漏和溢出方面的问题,如果不了解虚拟机是怎么分配运行时内存的,那么排查错误将是一项非常艰难的工作。

阅读全文 »

Java 并发之 ThreadLocal

发表于 2019-10-19 | 分类于 Java , 并发 |
字数统计: 3.1k 字 | 阅读时长 ≈ 12 分钟

1.1 什么是 ThreadLocal

ThreadLocal 简单理解 Thread 即线程,Local 即本地,结合起来理解就是 每个线程都是本地独有的。在早期的计算机中不包含操作系统,从头到尾只执行一个程序,并且这个程序能访问计算中的所有资源,这对于计算机资源来说是一种浪费。要想充分发挥多处理器的强大计算能力,最简单的方式就是使用多线程。与串行程序相比,在并发程序中存在更多容易出错的地方。当访问共享数据时,通常需要使用同步来控制并发程序的访问。一种避免使用同步的方式就是让这部分共享数据变成不共享的,试想一下,如果只是在单个线程内对数据进行访问,那么就可以不用同步了,这种技术称为线程封闭(Thread Confinement),它是实现线程安全最简单的方式之一。
当某个对象封闭在一个单个线程中时,这种用法会自动实现了线程安全,因为只有一个线程访问数据,从根本上避免了共享数据的线程安全问题,即使被封闭的对象本身不是线程安全的。要保证线程安全,并不是一定就需要同步,两者没有因果关系,同步只是保证共享数据征用时正确性的手段,如果一个方法本来就不涉及共享数据,那它就不需要任何同步措施去保证正确性。而维持线程封闭的一种规范用法就是使用 ThreadLoal,这个类能使当前线程中的某个值与保存的值关联起来。ThreadLocal 提供了 get() 与 set(T value) 等方法,set 方法为每个使用了该变量的线程都存有一份独立的副本,因此当我们调用 get 方法时总是返回由当前线程在调用 set 方法的时候设置的最新值。

1.2 ThreadLocal 的用法

接下来通过一个示例代码说明 ThreadLocal 的使用方式,该示例使用了三个不同的线程 Main Thread、Thread-1 和 Thread-2 分别对同一个 ThreadLocal 对象中存储副本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* @author mghio
* @date: 2019-10-20
* @version: 1.0
* @description: Java 并发之 ThreadLocal
* @since JDK 1.8
*/
public class ThreadLocalDemoTests {
private ThreadLocal<String> boolThreadLocal = ThreadLocal.withInitial(() -> "");

@Test
public void testUseCase() {
boolThreadLocal.set("main-thread-set");
System.out.printf("Main Thread: %s\n", boolThreadLocal.get());

new Thread("Thread-1") {
@Override
public void run() {
boolThreadLocal.set("thread-1-set");
System.out.printf("Thread-1: %s\n", boolThreadLocal.get());
}
}.start();

new Thread("Thread-2") {
@Override
public void run() {
System.out.printf("Thread-2: %s\n", boolThreadLocal.get());
}
}.start();
}
}
阅读全文 »

Java 字符串 split 踩坑记

发表于 2019-10-13 | 分类于 Java |
字数统计: 2.4k 字 | 阅读时长 ≈ 10 分钟

1.1 split 的坑

前几天在公司对通过 FTP 方式上传的数据文件按照事先规定的格式进行解析后入库,代码的大概实现思路是这样的:先使用流进行文件读取,对文件的每一行数据解析封装成一个个对象,然后进行入库操作。本以为很简单的一个操作,然后写完代码后自己测试发现对文件的每一行进行字符串分割的时候存在问题,在这里做个简单的记录总结。在 Java 中使用 split 方法对字符串进行分割是经常使用的方法,经常在一些文本处理、字符串分割的逻辑中,需要按照一定的分隔符进行分割拆解。这样的功能,大多数情况下我们都会使用 String 中的 split 方法。关于这个方法,稍不注意很容易踩坑。

(1)split 的参数是正则表达式
首先一个常见的问题,就是忘记了 String 的 split 方法的参数不是普通的字符串,而是正则表达式,例如下面的这两种使用方式都达不到我们的预期:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* @author mghio
* @date: 2019-10-13
* @version: 1.0
* @description: Java 字符串 split 踩坑记
* @since JDK 1.8
*/
public class JavaStringSplitTests {

@Test
public void testStringSplitRegexArg() {
System.out.println(Arrays.toString("m.g.h.i.o".split(".")));
System.out.println(Arrays.toString("m|g|h|i|o".split("|")));
}

}
阅读全文 »

Java 字节码

发表于 2019-10-02 | 分类于 Java |
字数统计: 3.1k 字 | 阅读时长 ≈ 11 分钟

1.1 什么是字节码?

Java 在刚刚诞生之时曾经提出过一个非常著名的口号: “一次编写,到处运行(write once,run anywhere)”,这句话充分表达了软件开发人员对冲破平台界限的渴求。“与平台无关”的理想最终实现在操作系统的运用层上: 虚拟机提供商开发了许多可以运行在不同平台上的虚拟机,这些虚拟机都可以载入和执行同一种平台无关的字节码,从而实现了程序的“一次编写到处运行”。

各种不同平台的虚拟机与所有平台都统一使用的程序存储格式—字节码(ByteCode),因此,可以看出字节码对 Java 生态的重要性。之所以被称为字节码,是因为字节码是由十六进制组成的,而 JVM(Java Virtual Machine)以两个十六进制为一组,即以字节为单位进行读取。在 Java 中使用 javac 命令把源代码编译成字节码文件,一个 .java 源文件从编译成 .class 字节码文件的示例如图 1 所示:
图1

图 1

阅读全文 »

hello world

发表于 2019-10-01 | 分类于 Java |
字数统计: 18 字 | 阅读时长 ≈ 1 分钟
1
2
3
4
5
6
7
public class HelloWorld {

public static void main(String[] args) {
System.out.println("Hello World ~~~");
}

}
阅读全文 »
1…56
mghio

mghio

58 日志
63 分类
57 标签
RSS
GitHub 思否 简书 掘金 E-Mail 博客园
友情链接
  • 气象万千
  • 淡白记忆博客
  • Junhua's blog
  • IT运维狗
  • Z
  • 13blog
  • 黄泽彬个人站
  • 荷戟独彷徨
  • Java学习之道
  • 宇宙湾
  • Morcat Blog
  • Jason
© 2019 — 2025 mghio
湘 ICP 备 2021001218 号-1 · 湘公网安备 43312302000074
本网站由提供 CDN 加速服务
访客数 人 总访问量 次