文章目录

  • 脑图
  • 四个线程安全策略
  • 不可变对象定义
  • 不可变对象需要满足的条件
  • 如何创建不可变对象
  • 使用final关键字定义不可变对象
    • 修饰变量示例
      • final修饰基本数据类型及String: 初始化之后不能修改 (线程安全)
      • final修饰引用类型变量:初始化之后不能再修改其引用,但可以修改值 (线程不安全)
  • 使用JDK / Guava中提供的工具类创建不可变对象
    • Collections.unmodifiableXXX 示例 (线程安全)
    • Guava ImmutableXXX 示例 (线程安全)
  • 代码

脑图


四个线程安全策略

线程限制

一个被线程限制的对象,由线程独占,并且只能被占有它的线程修改

共享只读

一个共享只读的对象,在没有额外同步的情况下,可以被多个线程并发访问,但是任何线程都不能修改它

线程安全对象

一个线程安全的对象或者容器,在内部通过同步机制来保证线程安全,所以其他线程无需额外的同步就可以通过公共接口随意访问它

被守护对象

被守护对象只能通过获取特定的锁来访问


不可变对象定义

在Java中,有一种对象发布了就是安全的,被称之为不可变对象。

不可变对象可以在多线程中可以保证线程安全


不可变对象需要满足的条件

  • 对象创建以后其状态就不能修改
  • 对象所有域都是final类型
  • 对象是正确创建的(在对象创建期间,this引用没有逸出)


如何创建不可变对象

  • 类声明成final类型,使其不可以被继承

  • 所有的成员设置成私有的,使其他的类和对象不能直接访问这些成员

  • 对变量不提供set方法

  • 所有可变的成员声明为final,这样只能对他们赋值一次

  • 通过构造器初始化所有成员,进行深度拷贝

  • get方法中,不直接返回对象本身,而是克隆对象,返回对象的拷贝

提到不可变的对象就不得不说一下final关键字,该关键字可以修饰类、方法、变量:


使用final关键字定义不可变对象

final关键字可以修饰类、方法、变量

  • 修饰类:不能被继承(final类中的所有方法都会被隐式的声明为final方法)
  • 修饰方法:

1、锁定方法不被继承类修改;
2、可提升效率(private方法被隐式修饰为final方法)

  • 修饰变量:

基本数据类型变量: 初始化之后不能修改
引用类型变量: 初始化之后不能再修改其引用

  • 修饰方法参数:同修饰变量

修饰变量示例

final修饰基本数据类型及String: 初始化之后不能修改 (线程安全)

可知:编译报错,被final修饰后,基本类型和String的变量无法被修改


final修饰引用类型变量:初始化之后不能再修改其引用,但可以修改值 (线程不安全)

package com.artisan.example.immutable;import java.util.ArrayList;
import java.util.List;
import java.util.Map;import com.google.common.collect.Maps;import lombok.extern.slf4j.Slf4j;@Slf4j
public class FinalDemo {// 基本数据类型 int 使用final修饰 验证被final修饰的基本数据类型无法改变private final static int num = 1;// String类型 使用final修饰 验证被final修饰的基本数据类型无法改变private final static String name = "小工匠";// 引用类型 初始化之后不能再修改其引用,但是可修改其中值private final static Map<String, Object> map = Maps.newHashMap();static {map.put("name", "artisan");map.put("age", 20);map.put("sex", "男");}public static void main(String[] args) {// 被final修饰的基本数据类型和String无法改变// 编译报错: The final field FinalDemo.num cannot be assigned// num = 2;// 编译报错: The final field FinalDemo.name cannot be assigned// name = "artisan";// 引用对象,此引用无法指向别的对象,但可修改该对象的值map.put("name", "小工匠");log.info("name:{}", map.get("name"));// 验证 方法参数被final修饰的情况List<String> list = new ArrayList<>();list.add("我是小工匠");test2(list);}// final修饰传递进来的变量基本类型,不可别改变private void test(final int a) {// 不能修改// 编译报错: The final local variable a cannot be assigned. It must be blank and not using a compound assignment// a = 2;log.info("a:{}", a);}// final修饰方法传递进来的变量 引用对象,无法指向别的对象,但可修改该对象的值private static void test2(final List<String> list) {// 添加数据list.add("我是artisan");list.forEach(str ->{log.info("数据:{}",str);});}}

输出:

需要我们注意的是,final修饰引用类型时,虽然不能将引用再指向别的对象,但可修改该对象的值。 线程不安全


使用JDK / Guava中提供的工具类创建不可变对象

除了final可以定义不可变对象,java提供的Collections类,也可定义不可变对象。

  • JDK中的 Collections.unmodifiableXXX传入的对象一经初始化便无法修改,XXX可表示Collection、List、Set、Map等

  • 谷歌的Guava中的ImmutableXXX,XXX同样可表示Collection、List、Set、Map等


Collections.unmodifiableXXX 示例 (线程安全)

执行后结果如下:

由此可见,用Collections.UnmodifiableMap修饰的对象是不可修改的,如果尝试修改对象的值,在程序运行时会抛出异常。


跟下Collections.UnmodifiableMap的源码

继续看下UnmodifiableMap

主要是将一个新的集合的所有更新方法变为抛出异常


Guava ImmutableXXX 示例 (线程安全)

package com.artisan.example.immutable;import com.artisan.anno.ThreadSafe;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;@ThreadSafe
public class GuavaImmutableSetDemo {// 使用Guava中提供的类来定义不可变对象的集合// 不可变listprivate final static ImmutableList<Integer> list = ImmutableList.of(1, 2, 3);// 不可变的setprivate final static ImmutableSet<Integer> set = ImmutableSet.copyOf(list);// 不可变的map,需要以k/v的形式传入数据,即奇数位参数为key,偶数位参数为valueprivate final static ImmutableMap<String, String> map = ImmutableMap.of("k1", "v1", "k2","v2");// 通过builder调用链的方式构造不可变的mapprivate final static ImmutableMap<String, String> map2 = ImmutableMap.<String, String>builder().put("key1", "value1").put("key2", "value2").put("key3", "value3").build();public static void main(String[] args) {// 修改对象内的数据就会抛出UnsupportedOperationException异常// 不能添加新的元素 ,运行将抛出 java.lang.UnsupportedOperationExceptionlist.add(4);// 不能添加新的元素 ,运行将抛出 java.lang.UnsupportedOperationExceptionset.add(4);// 不能添加新的元素 ,运行将抛出 java.lang.UnsupportedOperationExceptionmap.put("k3", "v3");// 不能添加新的元素 ,运行将抛出 java.lang.UnsupportedOperationExceptionmap2.put("key4", "value4");}
}

上述代码是线程安全的,开发时如果我们的对象可以变为不可变对象,我们尽量将对象变为不可变对象,这样可以避免线程安全问题。


代码

https://github.com/yangshangwei/ConcurrencyMaster

并发编程-10线程安全策略之不可变对象相关推荐

  1. 并发编程-11线程安全策略之线程封闭

    文章目录 脑图 概述 线程封闭的三种方式 示例 堆栈封闭 ThreadLocal Step1. ThreadLocal操作类 Step2. 自定义过滤器 Step3. 注册拦截器,配置拦截规则 Ste ...

  2. 并发编程-13线程安全策略之两种类型的同步容器

    文章目录 脑图 概述 同步容器 集合接口下的同步容器实现类 Vector (线程安全性比ArrayList好一些,但并非绝对线程安全) 同步容器 线程不安全的场景 其他注意事项 Hashtable C ...

  3. 并发编程-12线程安全策略之常见的线程不安全类

    文章目录 脑图 概述 字符串拼接子之StringBuilder.StringBuffer StringBuilder (线程不安全) StringBuffer (线程安全) 小结 时间相关的类 Sim ...

  4. java并发编程实战wwj----------------------第二阶段-------不可变对象-------19-20

    不可变对象一定是线程安全的. 可变对象不一定是安全的,因为里面会加锁. ------------------------------------------------ servlet不是线程安全的, ...

  5. 并发编程-14线程安全策略之并发容器(J.U.C)中的集合类

    文章目录 J.U.C总览 脑图 概述 并发容器特性 示例 ArrayList对应的线程安全的并发容器类CopyOnWriteArrayList (线程安全) HashSet对应的线程安全的并发容器类C ...

  6. 【Java 并发编程】线程池机制 ( ThreadPoolExecutor 线程池构造参数分析 | 核心线程数 | 最大线程数 | 非核心线程存活时间 | 任务阻塞队列 )

    文章目录 前言 一.ThreadPoolExecutor 构造参数 二.newCachedThreadPool 参数分析 三.newFixedThreadPool 参数分析 四.newSingleTh ...

  7. 【Java 并发编程】线程池机制 ( 线程池示例 | newCachedThreadPool | newFixedThreadPool | newSingleThreadExecutor )

    文章目录 前言 一.线程池示例 二.newCachedThreadPool 线程池示例 三.newFixedThreadPool 线程池示例 三.newSingleThreadExecutor 线程池 ...

  8. 并发编程-05线程安全性之原子性【锁之synchronized】

    文章目录 线程安全性文章索引 脑图 概述 原子性synchronized 修饰的4种对象 修饰代码块 作用范围及作用对象 Demo 多线程下 同一对象的调用 多线程下不同对象的调用 修饰方法 作用范围 ...

  9. java并发编程与线程安全

    2019独角兽企业重金招聘Python工程师标准>>> 什么是线程安全 如果对象的状态变量(对象的实例域.静态域)具有可变性,那么当该对象被多个线程共享时就的考虑线程安全性的问题,否 ...

最新文章

  1. eclipse或者myeclipse的代码提示功能
  2. 帷幕的帷是什么意思_俗语:“宁娶寡妇,不娶生妻!”什么是“生妻”?老祖宗智慧...
  3. 自定义的 ListBoxItem 自适应ListBox的宽度
  4. 线性规划实战—投资的收益和风险
  5. PHP生成日历(实例详解)
  6. vs最好的版本_Win10 环境下,LightGBM GPU 版本的安装
  7. 同期两篇 Nature:运行温度高于 1K 的量子计算平台问世!
  8. VMware快照功能与(非永久)永久磁盘详解
  9. dede文章异步ajax加载,织梦DedeCMS列表“加载更多”“无限下拉”Ajax加载使用方法...
  10. 语义分割Swin Transformer
  11. kernel打印模块驱动加载时间
  12. 【等保小知识】等保二级是否需要做密评?什么是密评?
  13. python arduino 微信_MicroPython动手做(27)——物联网之微信小程序
  14. 解决opencv imwrite()影像全黑
  15. 【低碳发展案例研究】中国西部中小城市低碳发展研究——以泸州市为例
  16. win在哪打开java_win7系统打开java控制面板的操作方法
  17. python 携程航班信息查询
  18. 知名的兴趣社群平台小打卡是如何获得5000万用户的?【黑盒研究内参第11期】...
  19. 【堆】leetcode378.有序矩阵中第K小的元素
  20. 计算机自考考研学校有哪些专业,适合自考生考研的学校有哪些

热门文章

  1. app.vue只执行一次吗_面包可以只发酵一次吗?
  2. 基于keras 的神经网络股价预测模型
  3. leetcode-寻找两个正序数组的中位数
  4. python 栈道实现
  5. C 语言 链表的创建与打印
  6. Leetcode 160 相交链表 (每日一题 20210802)
  7. 【转载】Few-shot learning(少样本学习)和 Meta-learning(元学习)概述
  8. MoCo论文中的Algorithm 1伪代码解读
  9. TImm 笔记: 训练模型
  10. 机器学习笔记:线性回归