多线程基础必要知识点!看了学习多线程事半功倍(一)

简介: 笔记

回顾前面:

多线程三分钟就可以入个门了!

Thread源码剖析

本文章的知识主要参考《Java并发编程实战》这本书的前4章,这本书的前4章都是讲解并发的基础的。要是能好好理解这些基础,那么我们往后的学习就会事半功倍。

当然了,《Java并发编程实战》可以说是非常经典的一本书。我是未能完全理解的,在这也仅仅是抛砖引玉。想要更加全面地理解我下面所说的知识点,可以去阅读一下这本书,总的来说还是不错的。

首先来预览一下《Java并发编程实战》前4章的目录究竟在讲什么吧:

第1章 简介

  • 1.1 并发简史
  • 1.2 线程的优势
  • 1.2.1 发挥多处理器的强大能力
  • 1.2.2 建模的简单性
  • 1.2.3 异步事件的简化处理
  • 1.2.4 响应更灵敏的用户界面
  • 1.3 线程带来的风险
  • 1.3.1 安全性问题
  • 1.3.2 活跃性问题
  • 1.3.3 性能问题
  • 1.4 线程无处不在

ps:这一部分我就不讲了,主要是引出我们接下来的知识点,有兴趣的同学可翻看原书~

第2章 线程安全性

  • 2.1 什么是线程安全性
  • 2.2 原子性
  • 2.2.1 竞态条件
  • 2.2.2 示例:延迟初始化中的竞态条件
  • 2.2.3 复合操作
  • 2.3 加锁机制
  • 2.3.1 内置锁
  • 2.3.2 重入
  • 2.4 用锁来保护状态
  • 2.5 活跃性与性能

第3章 对象的共享

  • 3.1 可见性
  • 3.1.1 失效数据
  • 3.1.2 非原子的64位操作
  • 3.1.3 加锁与可见性
  • 3.1.4 Volatile变量
  • 3.2 发布与逸出
  • 3.3 线程封闭
  • 3.3.1 Ad-hoc线程封闭
  • 3.3.2 栈封闭
  • 3.3.3 ThreadLocal类
  • 3.4 不变性
  • 3.4.1 Final域
  • 3.4.2 示例:使用Volatile类型来发布不可变对象
  • 3.5 安全发布
  • 3.5.1 不正确的发布:正确的对象被破坏
  • 3.5.2  不可变对象与初始化安全性
  • 3.5.3 安全发布的常用模式
  • 3.5.4 事实不可变对象
  • 3.5.5 可变对象
  • 3.5.6 安全地共享对象

第4章 对象的组合

  • 4.1 设计线程安全的类
  • 4.1.1 收集同步需求
  • 4.1.2 依赖状态的操作
  • 4.1.3 状态的所有权
  • 4.2 实例封闭
  • 4.2.1 Java监视器模式
  • 4.2.2 示例:车辆追踪
  • 4.3 线程安全性的委托
  • 4.3.1 示例:基于委托的车辆追踪器
  • 4.3.2 独立的状态变量
  • 4.3.3 当委托失效时
  • 4.3.4 发布底层的状态变量
  • 4.3.5 示例:发布状态的车辆追踪器
  • 4.4 在现有的线程安全类中添加功能
  • 4.4.1 客户端加锁机制
  • 4.4.2 组合
  • 4.5 将同步策略文档化

那么接下来我们就开始吧~


一、使用多线程遇到的问题


1.1线程安全问题


在前面的文章中已经讲解了线程【多线程三分钟就可以入个门了!】,多线程主要是为了提高我们应用程序的使用率。但同时,这会给我们带来很多安全问题

如果我们在单线程中以“顺序”(串行-->独占)的方式执行代码是没有任何问题的。但是到了多线程的环境下(并行),如果没有设计和控制得好,就会给我们带来很多意想不到的状况,也就是线程安全性问题

因为在多线程的环境下,线程是交替执行的,一般他们会使用多个线程执行相同的代码。如果在此相同的代码里边有着共享的变量,或者一些组合操作,我们想要的正确结果就很容易出现了问题

简单举个例子:

  • 下面的程序在单线程中跑起来,是没有问题的
public class UnsafeCountingServlet extends GenericServlet implements Servlet {
    private long count = 0;
    public long getCount() {
        return count;
    }
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        ++count;
        // To something else...
    }
}

但是在多线程环境下跑起来,它的count值计算就不对了!

首先,它共享了count这个变量,其次来说++count;这是一个组合的操作(注意,它并非是原子性

  • ++count实际上的操作是这样子的:
  • 读取count值
  • 将值+1
  • 将计算结果写入count

于是多线程执行的时候很可能就会有这样的情况:

  • 当线程A读取到count的值是8的时候,同时线程B也进去这个方法上了,也是读取到count的值为8
  • 它俩都对值进行加1
  • 将计算结果写入到count上。但是,写入到count上的结果是9
  • 也就是说:两个线程进来了,但是正确的结果是应该返回10,而它返回了9,这是不正常的!

如果说:当多个线程访问某个类的时候,这个类始终能表现出正确的行为,那么这个类就是线程安全的!

有个原则:能使用JDK提供的线程安全机制,就使用JDK的

当然了,此部分其实是我们学习多线程最重要的环节,这里我就不详细说了。这里只是一个总览,这些知识点在后面的学习中都会遇到~~~


1.3性能问题


使用多线程我们的目的就是为了提高应用程序的使用率,但是如果多线程的代码没有好好设计的话,那未必会提高效率。反而降低了效率,甚至会造成死锁

就比如说我们的Servlet,一个Servlet对象可以处理多个请求的,Servlet显然是一个天然支持多线程的

又以下面的例子来说吧:

public class UnsafeCountingServlet extends GenericServlet implements Servlet {
    private long count = 0;
    public long getCount() {
        return count;
    }
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        ++count;
        // To something else...
    }
}

从上面我们已经说了,上面这个类是线程不安全的。最简单的方式:如果我们在service方法上加上JDK为我们提供的内置锁synchronized,那么我们就可以实现线程安全了。

public class UnsafeCountingServlet extends GenericServlet implements Servlet {
    private long count = 0;
    public long getCount() {
        return count;
    }
    public void synchronized service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        ++count;
        // To something else...
    }
}

虽然实现了线程安全了,但是这会带来很严重的性能问题

  • 每个请求都得等待上一个请求的service方法处理了以后才可以完成对应的操作

这就导致了:我们完成一个小小的功能,使用了多线程的目的是想要提高效率,但现在没有把握得当,却带来严重的性能问题

在使用多线程的时候:更严重的时候还有死锁(程序就卡住不动了)。

这些都是我们接下来要学习的地方:学习使用哪种同步机制来实现线程安全,并且性能是提高了而不是降低了~

目录
打赏
0
0
0
0
1281
分享
相关文章
|
25天前
|
python3多线程中使用线程睡眠
本文详细介绍了Python3多线程编程中使用线程睡眠的基本方法和应用场景。通过 `time.sleep()`函数,可以使线程暂停执行一段指定的时间,从而控制线程的执行节奏。通过实际示例演示了如何在多线程中使用线程睡眠来实现计数器和下载器功能。希望本文能帮助您更好地理解和应用Python多线程编程,提高程序的并发能力和执行效率。
51 20
Unity多线程使用(线程池)
在C#中使用线程池需引用`System.Threading`。创建单个线程时,务必在Unity程序停止前关闭线程(如使用`Thread.Abort()`),否则可能导致崩溃。示例代码展示了如何创建和管理线程,确保在线程中执行任务并在主线程中处理结果。完整代码包括线程池队列、主线程检查及线程安全的操作队列管理,确保多线程操作的稳定性和安全性。
Java线程池ExecutorService学习和使用
通过学习和使用Java中的 `ExecutorService`,可以显著提升并发编程的效率和代码的可维护性。合理配置线程池参数,结合实际应用场景,可以实现高效、可靠的并发处理。希望本文提供的示例和思路能够帮助开发者深入理解并应用 `ExecutorService`,实现更高效的并发程序。
41 10
|
4月前
|
JavaGuide知识点整理——线程池的最佳实践
总之,合理使用和配置线程池是提高 Java 程序性能和稳定性的重要手段。遵循最佳实践,可以更好地发挥线程池的作用,提升系统的运行效率。同时,要不断地进行监控和优化,以适应不同的业务需求和环境变化。
166 63
|
3月前
|
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
89 1
|
1月前
|
Linux编程: 在业务线程中注册和处理Linux信号
本文详细介绍了如何在Linux中通过在业务线程中注册和处理信号。我们讨论了信号的基本概念,并通过完整的代码示例展示了在业务线程中注册和处理信号的方法。通过正确地使用信号处理机制,可以提高程序的健壮性和响应能力。希望本文能帮助您更好地理解和应用Linux信号处理,提高开发效率和代码质量。
45 17
|
1月前
|
Linux编程: 在业务线程中注册和处理Linux信号
通过本文,您可以了解如何在业务线程中注册和处理Linux信号。正确处理信号可以提高程序的健壮性和稳定性。希望这些内容能帮助您更好地理解和应用Linux信号处理机制。
57 26
|
3月前
|
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
276 2
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等