保障线程安全相关问题

1.Java 运行时存储空间

Java运行时(Java runtime)空间可以分为栈区,堆区与方法区(非堆空 间).

栈空间(Stack Space)为线程的执行准备一段固定大小的存储空间, 每个线程都有独立的线程栈空间,创建线程时就为线程分配栈空间.在 线程栈中每调用一个方法就给方法分配一个栈帧,栈帧用于存储方法 的局部变量,返回值等私有数据, 即局部变量存储在栈空间中, 基本 类型变量也是存储在栈空间中, 引用类型变量值也是存储在栈空间 中,引用 的对象存储在堆中. 由于线程栈是相互独立的,一个线程不 能访问另外一个线程的栈空间,因此线程对局部变量以及只能通过当 前线程的局部变量才能访问的对象进行的操作具有固定的线程安全 性.
堆空间(Heap Space)用于存储对象,是在 JVM 启动时分配的一段可以动态扩容的内存空间. 创建对象时,在堆空间中给对象分配存储空 间,实例变量就是存储在堆空间中的, 堆空间是多个线程之间可以共 享的空间,因此实例变量可以被多个线程共享. 多个线程同时操作实 例变量可能存在线程安全问题
非堆空间(Non-Heap Space)用于存储常量,类的元数据等, 非堆空 间也是在 JVM 启动时分配的一段可以动态扩容的存储空间.类的元数 据包括静态变量,类有哪些方法及这些方法的元数据(方法名,参数,返 回值等). 非堆空间也是多个 线程可以共享的, 因此访问非堆空间中 的静态变量也可能存在线程安全问题
堆空间和非堆空间是线程可以共享的空间,即实例变量与静态变 量是线程可以共享的,可能存在线程安全问题. 栈空间是线程私有的 存储空间,局部变量存储在栈空间中,局部变量具有固定的线程安全性

2.无状态对象

对象就是数据及对数据操作的封装, 对象所包含的数据称为对象 的状态(State), 实例变量与静态变量称为状态变量.
如果一个类的同一个实例被多个线程共享并不会使这些线程存储 共享的状态,那么该类的实例就称为无状态对象(Stateless Object). 反 之如果一个类的实例被多个线程共享会使这些线程存在共享状态,那该类的实例称为有状态对象. 实际上无状态对象就是不包含任何 实例变量也不包含任何静态变量的对象.
线程安全问题的前提是多个线程存在共享的数据,实现线程安全 的一种办法就是避免在多个线程之间共享数据,使用无状态对象就是 这种方法

3.不可变对象

不可变对象是指一经创建它的状态就保持不变的对象,不可变对 象具有固有的线程安全性. 当不可变对象现实实体的状态发生变化 时,系统会创建一个新的不可变对象,就如 String 字符串对象. 一个不 可变对象需要满足以下条件:

  1. 类本身使用 final 修饰,防止通过创建子类来改变它的定义
  2. 所有的字段都是 final 修饰的,final 字段在创建对象时必须显示 初始化,不能被修改
  3. 如果字段引用了其他状态可变的对象(集合,数组),则这些字段 必须是 private 私有的

不可变对象主要的应用场景:

  1. 被建模对象的状态变化不频繁
  2. 同时对一组相关数据进行写操作,可以应用不可变对象,既可以 保障原子性也可以避免锁的使用
  3. 使用不可变对象作为安全可靠的Map键, HashMap键值对的存 储位置与键的 hashCode()有关,如果键的内部状态发生了变化会导致 键的哈希码不同,可能会影响键值对的存储位置. 如果 HashMap 的键 是一个不可变对象,则 hashCode()方法的返回值恒定,存储位置是固定 的.

4.线程特有对象

我们可以选择不共享非线程安全的对象,对于非线程安全的对象, 每个线程都创建一个该对象的实例,各个线程线程访问各自创建的实 例,一个线程不能访问另外一个线程创建的实例. 这种各个线程创建 各自的实例,一个实例只能被一个线程访问的对象就称为线程特有对 象. 线程特有对象既保障了对非线程安全对象的访问的线程安全,又 避免了锁的开销.线程特有对象也具有固有的线程安全性.
ThreadLocal类相当于线程访问其特有对象的代理,即各个线程 通过 ThreadLocal 对象可以创建并访问各自的线程特有对象,泛型 T 指 定了线程特有对象的类型. 一个线程可以使用不同的 ThreadLocal 实 例来创建并访问不同的线程特有对象

ThreadLocal 实例为每个访问它的线程都关联了一个该线程特有的 对象, ThreadLocal 实例都有当前线程与特有实例之间的一个关联.

5.装饰器模式

装饰器模式可以用来实现线程安全,基本思想是为非线程安全的 对象创建一个相应的线程安全的外包装对象,客户端代码不直接访问 非线程安全的对象而是访问它的外包装对象. 外包装对象与非线程 安全的对象具有相同的接口,即外包装对象的使用方式与非线程安全 对象的使用方式相同,而外包装对象内部通常会借助锁,以线程安全的 方式调用相应的非线程安全对象的方法.
在 java.util.Collections 工具类中提供了一组 synchronizedXXX(xxx) 可以把不是线程安全的 xxx 集合转换为线程安全的集合,它就是采用 了这种装饰器模式. 这个方法返回值就是指定集合的外包装对象.这 类集合又称为同步集合.
使用装饰器模式的一个好处就是实现关注点分离,在这种设计中, 实现同一组功能的对象的两个版本:非线程安全的对象与线程安全的 对象. 对于非线程安全的在设计时只关注要实现的功能,对于线程安 全的版本只关注线程安全性

posted @ 2021-08-30 15:32  x77  阅读(32)  评论(0编辑  收藏  举报