java并发编程实践笔记

简介: 1, 保证线程安全的三种方法:    a, 不要跨线程访问共享变量    b, 使共享变量是final类型的    c, 将共享变量的操作加上同步2, 一开始就将类设计成线程安全的, 比在后期重新修复它,更容易.

1, 保证线程安全的三种方法:
    a, 不要跨线程访问共享变量
    b, 使共享变量是final类型的
    c, 将共享变量的操作加上同步

2, 一开始就将类设计成线程安全的, 比在后期重新修复它,更容易.

3, 编写多线程程序, 首先保证它是正确的, 其次再考虑性能.

4, 无状态或只读对象永远是线程安全的.

5, 不要将一个共享变量裸露在多线程环境下(无同步或不可变性保护)

6, 多线程环境下的延迟加载需要同步的保护, 因为延迟加载会造成对象重复实例化

7, 对于volatile声明的数值类型变量进行运算, 往往是不安全的(volatile只能保证可见性,不能保证原子性).
详见volatile原理与技巧中, 脏数据问题讨论.

8, 当一个线程请求获得它自己占有的锁时(同一把锁的嵌套使用), 我们称该锁为可重入锁.
在jdk1.5并发包中, 提供了可重入锁的java实现-ReentrantLock.

9, 每个共享变量,都应该由一个唯一确定的锁保护.
创建与变量相同数目的ReentrantLock, 使他们负责每个变量的线程安全.

10,虽然缩小同步块的范围, 可以提升系统性能.
但在保证原子性的情况下, 不可将原子操作分解成多个synchronized块.

11, 在没有同步的情况下, 编译器与处理器运行时的指令执行顺序可能完全出乎意料.
原因是, 编译器或处理器为了优化自身执行效率, 而对指令进行了的重排序(reordering).

12, 当一个线程在没有同步的情况下读取变量, 它可能会得到一个过期值, 但是至少它可以看到那个
线程在当时设定的一个真实数值. 而不是凭空而来的值. 这种安全保证, 称之为最低限的安全性(out-of-thin-air safety)

在开发并发应用程序时, 有时为了大幅度提高系统的吞吐量与性能, 会采用这种无保障的做法.
但是针对, 数值的运算, 仍旧是被否决的.

13, volatile变量,只能保证可见性, 无法保证原子性.
详见 volatile原理与技巧

14, 某些耗时较长的网络操作或IO, 确保执行时, 不要占有锁.

15, 发布(publish)对象, 指的是使它能够被当前范围之外的代码所使用.(引用传递)
对象逸出(escape), 指的是一个对象在尚未准备好时将它发布.

原则: 为防止逸出, 对象必须要被完全构造完后, 才可以被发布(最好的解决方式是采用同步)

this关键字引用对象逸出
例子: 在构造函数中, 开启线程, 并将自身对象this传入线程, 造成引用传递.
而此时, 构造函数尚未执行完, 就会发生对象逸出了.

16, 必要时, 使用ThreadLocal变量确保线程封闭性(封闭线程往往是比较安全的, 但一定程度上会造成性能损耗)
封闭对象的例子在实际使用过程中, 比较常见, 例如 hibernate openSessionInView机制, jdbc的connection机制.

17, 单一不可变对象往往是线程安全的(复杂不可变对象需要保证其内部成员变量也是不可变的)
良好的多线程编程习惯是: 将所有的域都声明为final, 除非它们是可变的

18, 保证共享变量的发布是安全的
    a, 通过静态初始化器初始化对象(jls 12.4.2叙述, jvm会保证静态初始化变量是同步的)
    b, 将对象申明为volatile或使用AtomicReference
    c, 保证对象是不可变的
    d, 将引用或可变操作都由锁来保护

19, 设计线程安全的类, 应该包括的基本要素:
    a, 确定哪些是可变共享变量
    b, 确定哪些是不可变的变量
    c, 指定一个管理并发访问对象状态的策略

20, 将数据封装在对象内部, 并保证对数据的访问是原子的.
建议采用volatile javabean模型或者构造同步的getter,setter.

21, 线程限制性使构造线程安全的类变得更容易, 因为类的状态被限制后, 分析它的线程安全性时, 就不必检查完整的程序.

22, 编写并发程序, 需要更全的注释, 更完整的文档说明.

23, 在需要细分锁的分配时, 使用java监视器模式好于使用自身对象的监视器锁.
前者的灵活性更好.

Object target = new Object();
// 这里使用外部对象来作为监视器, 而非this
synchronized(target) {
    // TODO
}

针对java monitor pattern, 实际上ReentrantLock的实现更易于并发编程.
功能上, 也更强大.

24, 设计并发程序时, 在保证伸缩性与性能折中的前提下, 优先考虑将共享变量委托给线程安全的类.
由它来控制全局的并发访问.

25, 使用普通同步容器(Vector, Hashtable)的迭代器, 需要外部锁来保证其原子性.
原因是, 普通同步容器产生的迭代器是非线程安全的.

26, 在并发编程中, 需要容器支持的时候, 优先考虑使用jdk并发容器
(ConcurrentHashMap, ConcurrentLinkedQueue, CopyOnWriteArrayList...).

27, ConcurrentHashMap, CopyOnWriteArrayList
并发容器的迭代器,以及全范围的size(), isEmpty() 都表现出弱一致性.
他们只能标示容器当时的一个数据状态. 无法完整响应容器之后的变化和修改.

28, 使用有界队列, 在队列充满或为空时, 阻塞所有的读与写操作. (实现生产-消费的良好方案)
BlockQueue下的实现有LinkedBlockingQueue与ArrayBlockingQueue, 前者为链表, 可变操作频繁优先考虑,后者为数组, 读取操作频繁优先考虑.
PriorityBlockingQueue是一个按优先级顺序排列的阻塞队列, 它可以对所有置入的元素进行排序(实现Comparator接口)

29, 当一个方法, 能抛出InterruptedException, 则意味着, 这个方法是一个可阻塞的方法, 如果它被中断, 将提前结束阻塞状态.
当你调用一个阻塞方法, 也就意味着, 本身也称为了一个阻塞方法, 因为你必须等待阻塞方法返回.

如果阻塞方法抛出了中断异常, 我们需要做的是, 将其往上层抛, 除非当前已经是需要捕获异常的层次.
如果当前方法, 不能抛出InterruptedException, 可以使用Thread.currentThread.interrupt()方法, 手动进行中断.

目录
相关文章
|
19天前
|
前端开发 JavaScript Java
Java构建工具-maven的复习笔记【适用于复习】
这篇文档由「潜意识Java」创作,主要介绍Maven的相关知识。内容涵盖Maven的基本概念、作用、项目导入步骤、依赖管理(包括依赖配置、代码示例、总结)、依赖传递、依赖范围以及依赖的生命周期等七个方面。作者擅长前端开发,秉持“得之坦然,失之淡然”的座右铭。期待您的点赞、关注和收藏,这将是作者持续创作的动力! [个人主页](https://blog.csdn.net/weixin_73355603?spm=1000.2115.3001.5343)
32 3
|
2月前
|
安全 Java 程序员
深入理解Java内存模型与并发编程####
本文旨在探讨Java内存模型(JMM)的复杂性及其对并发编程的影响,不同于传统的摘要形式,本文将以一个实际案例为引子,逐步揭示JMM的核心概念,包括原子性、可见性、有序性,以及这些特性在多线程环境下的具体表现。通过对比分析不同并发工具类的应用,如synchronized、volatile关键字、Lock接口及其实现等,本文将展示如何在实践中有效利用JMM来设计高效且安全的并发程序。最后,还将简要介绍Java 8及更高版本中引入的新特性,如StampedLock,以及它们如何进一步优化多线程编程模型。 ####
48 0
|
2月前
|
Java 程序员
Java编程中的异常处理:从基础到高级
在Java的世界中,异常处理是代码健壮性的守护神。本文将带你从异常的基本概念出发,逐步深入到高级用法,探索如何优雅地处理程序中的错误和异常情况。通过实际案例,我们将一起学习如何编写更可靠、更易于维护的Java代码。准备好了吗?让我们一起踏上这段旅程,解锁Java异常处理的秘密!
|
19天前
|
存储 Java 开发者
【潜意识Java】深入详细理解分析Java中的toString()方法重写完整笔记总结,超级详细。
本文详细介绍了 Java 中 `toString()` 方法的重写技巧及其重要
43 10
【潜意识Java】深入详细理解分析Java中的toString()方法重写完整笔记总结,超级详细。
|
2月前
|
存储 缓存 Java
Java 并发编程——volatile 关键字解析
本文介绍了Java线程中的`volatile`关键字及其与`synchronized`锁的区别。`volatile`保证了变量的可见性和一定的有序性,但不能保证原子性。它通过内存屏障实现,避免指令重排序,确保线程间数据一致。相比`synchronized`,`volatile`性能更优,适用于简单状态标记和某些特定场景,如单例模式中的双重检查锁定。文中还解释了Java内存模型的基本概念,包括主内存、工作内存及并发编程中的原子性、可见性和有序性。
Java 并发编程——volatile 关键字解析
|
28天前
|
Kubernetes Java 持续交付
小团队 CI/CD 实践:无需运维,Java Web应用的自动化部署
本文介绍如何使用GitHub Actions和阿里云Kubernetes(ACK)实现Java Web应用的自动化部署。通过CI/CD流程,开发人员无需手动处理复杂的运维任务,从而提高效率并减少错误。文中详细讲解了Docker与Kubernetes的概念,并演示了从创建Kubernetes集群、配置容器镜像服务到设置GitHub仓库Secrets及编写GitHub Actions工作流的具体步骤。最终实现了代码提交后自动构建、推送镜像并部署到Kubernetes集群的功能。整个过程不仅简化了部署流程,还确保了应用在不同环境中的稳定运行。
60 9
|
2月前
|
算法 Java 调度
java并发编程中Monitor里的waitSet和EntryList都是做什么的
在Java并发编程中,Monitor内部包含两个重要队列:等待集(Wait Set)和入口列表(Entry List)。Wait Set用于线程的条件等待和协作,线程调用`wait()`后进入此集合,通过`notify()`或`notifyAll()`唤醒。Entry List则管理锁的竞争,未能获取锁的线程在此排队,等待锁释放后重新竞争。理解两者区别有助于设计高效的多线程程序。 - **Wait Set**:线程调用`wait()`后进入,等待条件满足被唤醒,需重新竞争锁。 - **Entry List**:多个线程竞争锁时,未获锁的线程在此排队,等待锁释放后获取锁继续执行。
76 12
|
2月前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
201 2
|
2月前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####