一、线程安全性

1.1 什么是线程安全性?

当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或者协同,这个类都能表现出正确行为。

1.2 原子性

i++ -> "读取-修改-写入"

1.2.1 竞态条件 和 复合操作

当某个计算的正确性取决于多个线程的交替执行时序时,那么就会发生竞态条件。或者说,之所以发生竞态条件是因为操作是非原子性而是一个复合操作。

public class A {private static A instance = null;public static A getInstance(){if(instance == null){instance = new A();}return instance;}
}复制代码

这里会存在竞态条件(先检查后执行),假设线程A1和A2同时执行getInstance方法,A1看到instance实例为空,它会去new A();A2也要判断instance是否为空,此时的instance是否为空取决于不可预测的时序(包括线程调度方式),以及A1要花多长时间new A 实例;如果A1在new操作时,轮到A2线程被调度,那么此时A2判断instance也为空,最终会出现两个A实例。 同理i++也存在这样的竞态条件(读取-修改-写入) 解决:避免某个线程修改变量时,通过某种方式防止其他线程使用这个变量,即保证操作是原子方式执行。

先检查后执行 -- 加锁实现同步
i++ -- concurrent.atomic 实现原子操作

1.3 加锁机制

1.3.1 内置锁

Synchronized

锁重入

“重入”意味着获取锁的操作粒度是“线程”,而不是“调用”,避免死锁。

二、共享对象

2.1 可见性

当读操作和写操作在不同的线程中执行时,无法确保执行读操作的线程能看到其它线程写入的值。

问题:

  1. 数据失效

public class ClassA {private int value;public int getValue() {return value;}public void setValue(int value) {this.value = value;}
}
复制代码

在没有同步的情况下get和set访问value,数据失效很容易出现:如果某个线程调用了set,那么另一个在调用get的线程可能会看到更新后的value值,也可能看不到。

  1. 非原子的64位操作
    非volatile类型的64位数值变量long和double,JVM允许将64位的读操作或者写操作分解为两个32位的操作,当读取一个非volatile类型的long变量时,如果对该变量的读操作和写操作在不同的线程中执行,那么很可能读到某个值得高32位和另一个值得低32位,造成数据失效。

解决:

  1. 加锁与可见性
    加锁不仅仅具有互斥性和包括可见性,为了确保所有线程都能看到共享变量的最新值,所有执行读操作或者写操作的线程都必须在同一个锁上同步。
  2. volatile 变量
    可见性与重排序 使用场景:确保自身状态可见,确保引用对象状态可见,标记重要程序生命周期事件发生(初始化和关闭) 某操作完成、中断或者状态标志

volatile boolean asleep;
...
while(!asleep) {countSomeSheep();
}
复制代码

2.2 发布与逸出

1. 发布: 发布一个对象的意思是,使对象能够在当前作用域之外的代码中使用。

  1. 将一个引用存储到其他代码可以访问的地方;
  2. 在一个非私有的方法中返回该引用;
  3. 将该对象传递到其他类的方法中等。

public static Set secrets;
public void init(){secrets = new HashSet();
}
复制代码

当发布某个对象时,可能间接地发布其他对象。例如如果将一个Secret对象添加到集合secrets中,那么在发布secrets的同时,也会发布Secret对象,因为任何代码都可以遍历这个集合,并获得对Secret对象的引用。

2. 逸出: 当某个不应该发布的对象被发布时,这种情况就是逸出。
对象逸出会导致对象的内部状态被暴露,可能危及到封装性,使程序难以维持稳定;若发布尚未构造完成的对象,可能危及线程安全问题。
最常见的逸出是this引用在构造时逸出,导致this引用逸出的常见错误有:

  1. 在构造函数中启动线程:
    当对象在构造函数中显式还是隐式创建线程时,this引用几乎总是被新线程共享,于是新的线程在所属对象完成构造之前就能看见它。 避免构造函数中启动线程引起的this引用逸出的方法是不要在构造函数中启动新线程,取而代之的是在其他初始化或启动方法中启动对象拥有的线程。
  2. 在构造方法中调用可覆盖的实例方法:
    在构造方法中调用那些既不是private也不是final的可被子类覆盖的实例方法时,同样导致this引用逸出。 避免此类错误的方法时千万不要在父类构造方法中调用被子类覆盖的方法。
  3. 在构造方法中创建内部类:
    在构造方法中创建内部类实例时,内部类的实例包含了对封装实例的隐含引用(深入理解 内部类),可能导致隐式this逸出。例子如下:

不要在构造函数中使用this引用逸出,也不要在构造方法中调用可改写“实例”的方法;


public class SafeListener {  private final EventListener listener;  private SafeListener(){  listener = new EventListener(){  public void onEvent(Event e){  doSomething(e);  }  );  }  public static SafeListener newInstance(EventSource source) {  SafeListener safe = new SafeListener();  source.registerListener(safe.listener);  return safe;  }
}
复制代码

2.3 线程封闭

当访问共享的可变数据时,通常需要同步。一种避免使用同步的方式就是不同享数据,这叫做线程封闭。java提供了一些机制来维持线程封闭性,例如局部变量和ThreadLocal类。 线程封闭技术的一个常见应用是JDBC的Connection对象。JDBC规范不要求Connection对象时线程安全的,而要求连接池是线程安全的。线程通过线程池中获得一个Connection对象,并且用该对象来处理请求,使用完之后再返回给连接池。由于大多数请求(例如Servlet请求和EJB)都是单个线程采用同步的方式来处理,并且在Connection对象返回前,连接池不会再把它分配给其它线程,因此这种连接在处理请求时,把Connection对象封闭在线程中。

2.3.1 栈封闭

将变量封闭在方法中

2.3.2 threadLocal 为每个使用变量的线程都存有一个副本

使用场景:

  1. 一个单线程应用程序移植到多线程环境,将共享的全局变量转换为threadLocdal对象
  2. 避免调用每个方法都要传递上下文信息

2.4 不变性

如果一个对象在创建后其状态就不能被修改,那么这个对象就称为不可变对象。 不可变对象需要满足下面条件:

  1. 对象本身是final的(避免被子类化),声明属性为private 和 final
  2. 不可变对象的状态在创建后就不能再改变,不要提供任何可以修改对象状态的方法 - 不仅仅是set方法, 还有任何其它可以改变状态的方法,每次对他们的改变都是产生了新的不可变对象的对象。
  3. 不可变对象能被正确地创建(在创建过程中没有发生this引用逸出)。
  4. 如果类有任何可变对象属性, 那么当它们在类和类的调用者间传递的时候必须被保护性拷贝

不可变的对象一定是线程安全的

2.5 安全发布

转载于:https://juejin.im/post/5c348d39f265da616f7024b2

java并发编程一:基础知识相关推荐

  1. 学习笔记:Java 并发编程①_基础知识入门

    若文章内容或图片失效,请留言反馈. 部分素材来自网络,若不小心影响到您的利益,请联系博主删除. 视频链接:https://www.bilibili.com/video/av81461839 视频下载: ...

  2. Java并发编程笔记—基础知识—实用案例

    如何正确停止一个线程 1)共享变量的使用 中断线程最好的,最受推荐的方式是,使用共享变量(shared variable)发出信号,告诉线程必须停止正在运行的任务.线程必须周期性的核查这一变量(尤其在 ...

  3. zbb20180929 thread java并发编程之Condition

    java并发编程之Condition 引言 在java中,对于任意一个java对象,它都拥有一组定义在java.lang.Object上监视器方法,包括wait(),wait(long timeout ...

  4. Java 并发编程之美:并发编程高级篇之一-chat

    借用 Java 并发编程实践中的话:编写正确的程序并不容易,而编写正常的并发程序就更难了.相比于顺序执行的情况,多线程的线程安全问题是微妙而且出乎意料的,因为在没有进行适当同步的情况下多线程中各个操作 ...

  5. Java 并发编程之美:并发编程高级篇之一

    借用 Java 并发编程实践中的话:编写正确的程序并不容易,而编写正常的并发程序就更难了.相比于顺序执行的情况,多线程的线程安全问题是微妙而且出乎意料的,因为在没有进行适当同步的情况下多线程中各个操作 ...

  6. Java并发编程实战基础概要

    文章目录 Java并发编程实战基础概要 开篇 多线程问题有啥难点呢? 为啥要学习并发编程? 并发问题的根源是什么? CPU切换线程执导致的原子性问题是如何发生的? 缓存导致的可见性问题是如何发生的? ...

  7. [Java面试三]JavaWeb基础知识总结.

    [Java面试三]JavaWeb基础知识总结. 1.web服务器与HTTP协议 Web服务器 l WEB,在英语中web即表示网页的意思,它用于表示Internet主机上供外界访问的资源. l Int ...

  8. Java中的线程基础知识

    Java中的线程基础知识 1.线程概念 线程是程序运行的基本执行单元.当操作系统(不包括单线程的操作系统,如微软早期的DOS)在执行一个程序时,会在系统中建立一个进程,而在这个进程中,必须至少建立一个 ...

  9. java并发编程之4——Java锁分解锁分段技术

    转载自 java并发编程之4--Java锁分解锁分段技术 并发编程的所有问题,最后都转换成了,"有状态bean"的状态的同步与互斥修改问题.而最后提出的解决"有状态bea ...

最新文章

  1. ASP.NET JScript公共类(非常有用)
  2. 非对称加密,我终于理解了!
  3. 一句SQL随机查询ACCESS中的几条记录
  4. HDP 2.6 requires libtirpc-devel
  5. django项目如何连接前端_工作笔记前端小白如何搭建前端项目
  6. linux make命令实现,Linux make命令主要参数详解
  7. AngularJS之页面跳转Route
  8. U盘量产及在虚拟机中测试
  9. html5动态切换class,uni-app v-for循环遍历 动态切换class、动态切换style
  10. 【渝粤教育】电大中专金融与税收 (2)_1作业 题库
  11. ascll编码表图片_ASCII码一览表,ASCII码对照表
  12. 国家统计局统计用区划和城乡划分代码
  13. lede虚拟服务器,在虚拟机中安装 LEDE 软路由
  14. Alibaba之EasyExcel使用
  15. Arduino 传感器: 使用FSR402压力传感器检测压力
  16. C++证明哥德巴赫猜想
  17. 关于Img标签绑定:src不显示图片
  18. 怎么解决word中英文混合换行后字体间隔变大问题
  19. 【华为2019届校园招聘】算法工程师面试总结
  20. 想要学习wxPython的看这里,wxPython模块超详细汇总(供自己学习用)

热门文章

  1. 计算机自适应测试的应用 托福,计算机自适应测试系统的研究和应用.pdf
  2. java default parameter_Java Parameter.DefaultBounds方法代码示例
  3. el表达式遍历list中的list_EL表达式获取map和list集合中的值 | 学步园
  4. Docker容器技术
  5. 分布式架构的前世今生...
  6. 【Java单例模式】Java单例模式之懒汉模式线程安全
  7. 使用mysqlbinlog恢复指定表
  8. Centos7 install Openstack - (第三节)添加镜像服务(Glance)
  9. C++ Ouput Exactly 2 Digits After Decimal Point 小数点后保留三位数字
  10. Creator-配置MSVC调试器