Java并发编程的艺术-并发编程基础

article/2025/9/16 22:26:17

       Java从诞生开始就明智地选择了内置对多线程的支持,这使得Java语言相比同一时期的其他语言具有明显的优势。线程作为操作系统调度的最小单元,多个线程能够同时执行,这将显著提升程序性能,在多核环境中表现得更加明显。但是,过多地创建线程和对线程的不当管理也容易造成问题。本章将着重介绍Java并发编程的基础知识,从启动一个线程到线程间不同的通信方式,最后通过简单的线程池示例以及应用(简单的Web服务器)来串联本章所介绍的内容。

 

线程简介

什么是线程

现代操作系统在运行一个程序时,会为其创建一个进程。例如,启动一个Java程序,操作系统就会创建一个Java进程。现代操作系统调度的最小单元是线程,也叫轻量级进程(Light Weight Process),在一个进程里可以创建多个线程,这些线程都拥有各自的计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量。处理器在这些线程上高速切换,让使用者感觉到这些线程在同时执行。

 

一个Java程序从main()方法开始执行,然后按照既定的代码逻辑执行,看似没有其他线程参与,但实际上Java程序天生就是多线程程序,因为执行main()方法的是一个名称为main的线程。下面使用JMX来查看一个普通的Java程序包含哪些线程,如代码清单所示。

public class MultiThread {public static void main(String[] args) {
// 获取Java线程管理MXBean
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
// 不需要获取同步的monitor和synchronizer信息,仅获取线程和线程堆栈信息
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
// 遍历线程信息,仅打印线程ID和线程名称信息
        for (ThreadInfo threadInfo : threadInfos) {
System.out.println("[" + threadInfo.getThreadId() + "] " + threadInfo.getThreadName());
}}

}

输出如下所示(输出内容可能不同)。

[4] Signal Dispatcher    // 分发处理发送给JVM信号的线程

[3] Finalizer            // 调用对象finalize方法的线程

[2] Reference Handler    // 清除Reference的线程

[1] main                 // main线程,用户程序入口

可以看到,一个Java程序的运行不仅仅是main()方法的运行,而是main线程和多个其他线程的同时运行。

 

为什么要使用多线程

执行一个简单的“Hello, World!”,却启动了那么多的“无关”线程,是不是把简单的问题复杂化了?当然不是,因为正确使用多线程,总是能够给开发人员带来显著的好处,而使用多线程的原因主要有以下几点。

(1)更多的处理器核心

随着处理器上的核心数量越来越多,以及超线程技术的广泛运用,现在大多数计算机都比以往更加擅长并行计算,而处理器性能的提升方式,也从更高的主频向更多的核心发展。如何利用好处理器上的多个核心也成了现在的主要问题。

线程是大多数操作系统调度的基本单元,一个程序作为一个进程来运行,程序运行过程中能够创建多个线程,而一个线程在一个时刻只能运行在一个处理器核心上。试想一下,一个单线程程序在运行时只能使用一个处理器核心,那么再多的处理器核心加入也无法显著提升该程序的执行效率。相反,如果该程序使用多线程技术,将计算逻辑分配到多个处理器核心上,就会显著减少程序的处理时间,并且随着更多处理器核心的加入而变得更有效率。

(2)更快的响应时间

有时我们会编写一些较为复杂的代码(这里的复杂不是说复杂的算法,而是复杂的业务逻辑),例如,一笔订单的创建,它包括插入订单数据、生成订单快照、发送邮件通知卖家和记录货品销售数量等。用户从单击“订购”按钮开始,就要等待这些操作全部完成才能看到订购成功的结果。但是这么多业务操作,如何能够让其更快地完成呢?

在上面的场景中,可以使用多线程技术,即将数据一致性不强的操作派发给其他线程处理(也可以使用消息队列),如生成订单快照、发送邮件等。这样做的好处是响应用户请求的线程能够尽可能快地处理完成,缩短了响应时间,提升了用户体验。

(3)更好的编程模型

Java为多线程编程提供了良好、考究并且一致的编程模型,使开发人员能够更加专注于问题的解决,即为所遇到的问题建立合适的模型,而不是绞尽脑汁地考虑如何将其多线程化。一旦开发人员建立好了模型,稍做修改总是能够方便地映射到Java提供的多线程编程模型上。

 

线程优先级

现代操作系统基本采用时分的形式调度运行的线程,操作系统会分出一个个时间片,线程会分配到若干时间片,当线程的时间片用完了就会发生线程调度,并等待着下次分配。线程分配到的时间片多少也就决定了线程使用处理器资源的多少,而线程优先级就是决定线程需要多或者少分配一些处理器资源的线程属性。

 

在Java线程中,通过一个整型成员变量priority来控制优先级,优先级的范围从1~10,在线程构建的时候可以通过setPriority(int)方法来修改优先级,默认优先级是5,优先级高的线程分配时间片的数量要多于优先级低的线程。设置线程优先级时,针对频繁阻塞(休眠或者I/O操作)的线程需要设置较高优先级,而偏重计算(需要较多CPU时间或者偏运算)的线程则设置较低的优先级,确保处理器不会被独占。在不同的JVM以及操作系统上,线程规划会存在差异,有些操作系统甚至会忽略对线程优先级的设定,示例代码清单。

 Priority. java

public class Priority {
private static volatile boolean notStart = true;
private static volatile boolean notEnd = true;
public static void main(String[] args) throws Exception {
List<Job> jobs = new ArrayList<Job>();
for (int i = 0; i < 10; i++) {
int priority = i < 5 ? Thread.MIN_PRIORITY : Thread.MAX_PRIORITY;
Job job = new Job(priority);
jobs.add(job);
Thread thread = new Thread(job, "Thread:" + i);
thread.setPriority(priority);
thread.start();
}
notStart = false;
TimeUnit.SECONDS.sleep(10);
notEnd = false;
for (Job job : jobs) {
System.out.println("Job Priority : " + job.priority + ",
                    Count : " + job.jobCount);
}
}
static class Job implements Runnable {
private int priority;
private long jobCount;
public Job(int priority) {
this.priority = priority;
}
public void run() {
while (notStart) {
Thread.yield();
}
while (notEnd) {
Thread.yield();
jobCount++;
}
}
}

}

运行该示例,在笔者机器上对应的输出如下。

Job Priority : 1, Count : 1259592

Job Priority : 1, Count : 1260717

Job Priority : 1, Count : 1264510

Job Priority : 1, Count : 1251897

Job Priority : 1, Count : 1264060

Job Priority : 10, Count : 1256938

Job Priority : 10, Count : 1267663

Job Priority : 10, Count : 1260637

Job Priority : 10, Count : 1261705

Job Priority : 10, Count : 1259967

从输出可以看到线程优先级没有生效,优先级1和优先级10的Job计数的结果非常相近,没有明显差距。这表示程序正确性不能依赖线程的优先级高低。注意

线程优先级不能作为程序正确性的依赖,因为操作系统可以完全不用理会Java线程对于优先级的设定。笔者的环境为:Mac OS X 10.10,Java版本为1.7.0_71,经过笔者验证该环境下所有Java线程优先级均为5(通过jstack查看),对线程优先级的设置会被忽略。另外,尝试在Ubuntu 14.04环境下运行该示例,输出结果也表示该环境忽略了线程优先级的设置。

 

线程的状态

Java线程在运行的生命周期中可能处于表4-1所示的6种不同的状态,在给定的一个时刻,线程只能处于其中的一个状态。

 

Java线程的状态

          

          

由图可以看到,线程创建之后,调用start()方法开始运行。当线程执行wait()方法之后,线程进入等待状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而超时等待状态相当于在等待状态的基础上增加了超时限制,也就是超时时间到达时将会返回到运行状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到阻塞状态。线程在执行Runnable的run()方法之后将会进入到终止状态。

注意

Java将操作系统中的运行和就绪两个状态合并称为运行状态。阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态,但是阻塞在java. concurrent包中Lock接口的线程状态却是等待状态,因为java.concurrent包中Lock接口对于阻塞的实现均使用了LockSupport类中的相关方法。

 

更快更新请关注我公众号:be_coder 

公众号:【转行程序员】专注新人转行入门指导,老兵技术提升,包含各种新手学习资料,架构师技能提升系列文章,欢迎关注。

 

 


http://chatgpt.dhexx.cn/article/DZlLMoI9.shtml

相关文章

java并发编程(下篇)

java里的阻塞队列 ArrayBlockingQueue 数组结构组成的有界阻塞队列 LinkedBlockingQeque 链表结构的无界阻塞队列 PriorityBlockingQueue 支持优先级排序的无界阻塞队列 DelayQueue 使用优先级队列实现的无界阻塞队列 LinkedBlockingDeque 链表结构组成的双向队列 并发工具…

Java并发编程之美——第一章 Java并发编程基础

文章目录 Time 2021-12-26——Hireek什么是线程线程的等待和通知等待线程终止的join方法让线程睡眠的sleep方法让出CPU执行权的yield方法线程中断demo 线程上下文切换线程死锁什么是死锁如何避免死锁 用户线程与守护线程ThreadLocalintroduction&#xff0c;下文只阐述重要的se…

Java并发编程入门这一篇就够了(文章很长,但很好哦)

Java并发编程入门这一篇就够了 一、进程与线程1. 进程2. 线程3. 二者对比 二、并行与并发三、Java线程1. 创建和运行线程2. 线程运行原理3. 常见方法4. 常用方法详解及异同区分5. 两阶段终止模式&#xff08;使得线程优雅的退出&#xff09;6.主线程与守护线程7. 线程五种状态8…

Java并发编程之Java线程

文章目录 前言01、线程简介02、线程池03、线程间通信总结 前言 记录一下Java并发编程的知识点。有部分内容是借鉴《Java并发编程的艺术》这本书的。本次先介绍一下线程。 01、线程简介 进程和线程的区别 进程&#xff1a;当一个程序被运行&#xff0c;即把程序的代码从磁盘加载…

Java并发编程的艺术

1、并发编程的挑战 1、上下文切换 CPU通过给每个线程分配CPU时间片来实现多线程机制。时间片是CPU分配给各个线程的时间&#xff0c;这个时间非常短&#xff0c;一般是几十毫秒。 CPU通过时间片分配算法来循环执行任务&#xff0c;当前任务执行一个时间片后会切换到下一个任务…

Java并发编程简介

并发编程简介 1. 什么是并发编程 所谓并发编程是指在一台处理器上“同时”处理多个任务。并发是在在同一实体上的多个事件。多个事件在同一时间间隔发生。 并发编程 ①从程序设计的角度来讲&#xff0c;是希望通过某些机制让计算机可以在一个时间段内&#xff0c;执行多个任务…

【java】Java并发编程系列-基础知识(非常详细哦)

文章目录 一、Java并发编程基础1.1 并发编程基本概念1.1.1原⼦性1.1.2 可⻅性1.1.3 有序性 二、内存模型三、重排序四、内存屏障五、总结 一、Java并发编程基础 主要讲解Java的并发编程的基础知识&#xff0c;包括原⼦性、可⻅性、有序性&#xff0c;以及内存模型JMM&#xff…

理解Java并发编程

计算机基础 要想理解Java多线程&#xff0c;一定离不开计算机组成原理和操作系统&#xff0c;因为&#xff0c;java的多线程是JVM虚拟机调用操作系统的线程来实现的 /*Thread.start() 方法中调用了原生的start0()方法 */ public synchronized void start() {if (threadStatus…

【并发编程】JAVA并发编程面试题合集

1.在Java中守护线程和本地线程的区别&#xff1f; Java中的线程分为两种&#xff1a;守护线程&#xff08;Daemon&#xff09;和用户线程&#xff08;User&#xff09;任何线程都可以设置为守护线程和用户线程&#xff0c;通过方法Thread.setDaemon(boolean)&#xff1b;true表…

Java并发编程概述

在学习并发编程之前&#xff0c;我们需要稍微回顾以下线程相关知识&#xff1a; 线程基本概念 程序&#xff1a;静态的代码&#xff0c;存储在硬盘中 进程&#xff1a;运行中的程序&#xff0c;被加载在内存中&#xff0c;是操作系统分配内存的基本单位 线程&#xff1a;是cpu执…

java并发编程(并发编程的三个问题)

什么是并发编程? 首先我们要知道什么是并发? 什么是并行? 并行: 多件事情在同一时刻同时发生 并发: 在同一时间内,多个事情交替执行 并发编程: 比如抢票,秒杀等在同一场景下,有大量的请求访问同一资源, 会出现一些安全性的问题,所以要通过编程来控制多个线程依次访问资源,称…

java并发编程(荣耀典藏版)

大家好 我是月夜枫&#xff0c;聊一聊java中的并发编程&#xff0c;面试工作中也许都会用到&#xff0c;参考了很大博主的博客&#xff0c;整理了很久的文章&#xff0c;虽然还没有全部整理完&#xff0c;后续慢慢更新吧。 并发编程 一、线程的基础概念 一、基础概念 1.1 进…

Java并发编程基础(一篇入门)

1 并发编程简介 1.1 什么是并发编程 所谓并发编程是指在一台处理器上 “同时” 处理多个任务。并发是在同一实体上的多个事件。多个事件在同一时间间隔发生。 并发编程&#xff0c;从程序设计的角度来说&#xff0c;是希望通过某些机制让计算机可以在一个时间段内&#xff0…

关于Java并发编程的总结和思考

编写优质的并发代码是一件难度极高的事情。Java语言从第一版本开始内置了对多线程的支持&#xff0c;这一点在当年是非常了不起的&#xff0c;但是当我们对并发编程有了更深刻的认识和更多的实践后&#xff0c;实现并发编程就有了更多的方案和更好的选择。本文是对并发编程的一…

Java 并发编程

目录 回顾线程 并发编程 并发编程 Java 内存模型(JMM) 编程核心问题--可见性,原子性,有序性 可见性 有序性 原子性 valatile 关键字 CAS&#xff08;Compare-And-Swap&#xff0c;比较并交换&#xff09; 原子类 java中的锁 乐观锁/悲观锁 可重用锁&#xff08;…

JAVA并发编程

并发编程 1.进程与线程 进程 程序由指令和数据组成&#xff0c;但这些指令要运行&#xff0c;数据要读写&#xff0c;就必须将指令加载至 CPU&#xff0c;数据加载至内存。在 指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理 IO 的。当一个…

阿里面试失败后,一气之下我图解了Java中18把锁

号外号外&#xff01;《死磕 Java 并发编程》系列连载中&#xff0c;大家可以关注一波&#xff1a; 「死磕 Java 并发编程04」说说Java Atomic 原子类的实现原理 「死磕 Java 并发编程03」阿里二面&#xff0c;面试官&#xff1a;说说 Java CAS 原理&#xff1f; 「死磕 Jav…

Java 是否应该使用通配符导入( wildcard imports)

这个问题应该是所有使用过 Java 第一课的人都会告诉你不要使用通配符导入。 主要问题 主要的问题是它使你的本地命名空间变得混乱。 用最简单的说法就是 Date 这个对象&#xff0c;你可能在 java.sql.Date 和 java.util.Date 都会有这个对象。 如果你使用通配符导入的话&…

Makefile中wildcard函数的应用理解

文章目录 前言 1 "*"通配符使用场景 2 "*"通配符实例 总结 前言 如果我们想定义一系列比较类似的文件&#xff0c;我们很自然地就想起使用通配符。make 支持三种通配符&#xff1a;"*"&#xff0c;"?" 和 "[...]"。这…

DNS Wildcard(DNS泛域名)

在DNS中&#xff0c;泛域名&#xff08;wildcard Resource Record&#xff09;可以被认为是一种合成RR的机制&#xff0c;借助于它&#xff0c;DNS服务器可以响应本来不存在的域名的请求&#xff0c;它的设计初衷是用来把所有邮件都转发到一个邮件系统&#xff08;当然&#xf…