Java多线程之先行发生原则(happens-before)

前面介绍了Java内存模型及内存屏障相关概念,这篇文章接着介绍多线程编程另外一个比较重要的概念:先行发生原则(happens-before)。

重要性

happens-before是判断数据是否存在竞争,线程是否安全的主要依据,通过这个原则,我们可以解决并发环境下两个操作之间是否可能存在冲突的所有问题。

happens-before是什么

它Java内存模型中针对两项操作定义的偏序关系。例如操作A先行于操作B发生,那么操作B可以观察到操作A所产生的所有影响,这些影响包括修改内存中共享变量的值、发送的消息,调用的方法等。

Java多线程之volatile关键字及内存屏障

前面一篇文章在介绍Java内存模型的三大特性(原子性、可见性、有序性)时,在可见性和有序性中都提到了volatile关键字,那这篇文章就来介绍volatile关键字的内存语义以及实现其特性的内存屏障。

volatile是JVM提供的一种最轻量级的同步机制,因为Java内存模型为volatile定义特殊的访问规则,使其可以实现Java内存模型中的两大特性:可见性和有序性。正因为volatile关键字具有这两大特性,所以我们可以使用volatile关键字解决多线程中的某些同步问题。

volatile的可见性

volatile的可见性是指当一个变量被volatile修饰后,这个变量就对所有线程均可见。白话点就是说当一个线程修改了一个volatile修饰的变量后,其他线程可以立刻得知这个变量的修改,拿到最这个变量最新的值。

Java多线程之Java内存模型

在介绍Java内存模型之前,我们先介绍一下计算机硬件的内存模型,因为JVM的并发和物理机器的并发很相似,甚至JVM并发操作中很多设计都是因为计算机系统的设计引发的。

硬件的内存模型

大家都知道计算机系统处理任务主要是靠处理器(CPU)来进行运算的,而运算中又会涉及到数据,数据在哪呢,数据自然是存储在计算机内存中,所以处理器在运算过程中不可避免的会涉及到与内存的读写交互,比如读取运算所需的数据,存储运算得到的数据结果等。而处理器的运算速度相比物理内存的读写速度要快得多,所以会出现处理器要等待内存数据读写结束后才能进行下一步的运算,因此为了提高计算机的运算速度,现在的计算机系统为处理器添加了一层读写速度尽量接近处理器的高速缓存来缓解内存与处理器之间的性能差异。这样在处理任务时将运算需要的数据复制到缓存中,运算结束后再将数据从缓存中同步写回到内存,这样处理器在运算时就不需要等待内存数据读写结束了。

多线程之:Synchronized与ReentrantLock

什么是线程安全

  1. 保证多线程环境下共享的、可修改的状态的正确性。(这里的状态在程序中可以看作为数据)
  2. 反着来说则是如果状态非共享、不可修改,也就不存在线程安全的问题

Hashtable、HashMap、TreeMap

Hashtable、HashMap、TreeMap都是比较常见的一些Map实现,它们都是key-value键值对的形式存储和操作数据的容器类,同时他们的元素中不能有重复的key,一个key也只能映射一个value值。

下面我从不同的维度来分别说说这三个集合,文章中涉及到的源码版本是JDK8

Hash冲突解决方法

何为Hash冲突

假设Hash表大小为5(即5个槽位),现在要把2,5,6,7,8这几个数存储到Hash表中,假设hash函数为hash(num)=num % size

简单计算下,第一个数2的hash值为2所以放到第三个槽中,第二个数5的hash值为0放到第一个槽中,第三个数6的hash值为1放到第二个槽中,如下图所示:

1号槽 2号槽 3号槽 4号槽 5号槽
5 6 2

final、finally、finalize

final

  1. final可以用来修饰类、方法、变量,修饰对象不同所代表的意义也不同
    • 修饰类则代表该类不可继承扩展
    • 修饰方法则代表该方法不可重写
    • 修饰变量则代表该变量某种程度不可更改。为什么说是某种程度呢,因为这需要根据变量的类型来区分
      • 如果修饰的变量是基本类型,则该变量赋值一次之后就无法修改,这是final就是不可变的标志
      • 如果修饰的变量是引用类型,那么该变量赋值一次之后,就无法修改该变量的引用,但是该引用对象的属性还是可以更改的,比较常见的就是变量引用了一个List,虽说用了final修饰,但是依然可以对该List的元素进行各种操作
  2. 将变量或参数使用final修饰可以清楚的避免意外赋值导致的编码错误
  3. 因为final修饰变量产生了某种程度的不可变的特性,所以它可以保护只读数据,因此在并发编程中使用final修饰变量有利于减少额外的同步开销,也可以省去一些防御性拷贝必要,从而提升性能

Exception与Error

这里写图片描述

相同点

ExceptionError都是继承自Throwable,在Java中只有Throwable的实例才可以被抛(throw)出或捕获(catch),它是java异常处理机制的基本组成类型。

ExceptionError体现了Java平台设计者对不同异常情况的分类

强引用、软引用、弱引用、虚引用

在Java语言中,除了基本数据类型外,其他的都是指向各类对象的对象引用;Java中根据其生命周期的长短,将引用分为4类。

强引用

特点:我们平常典型编码Object obj = new Object()中的obj就是强引用。

通过关键字new创建的对象所关联的引用就是强引用。 当JVM内存空间不足,JVM宁愿抛出OutOfMemoryError运行时错误(OOM),使程序异常终止,也不会靠随意回收具有强引用的“存活”对象来解决内存不足的问题。对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为 null,就是可以被垃圾收集的了,具体回收时机还是要看垃圾收集策略。

String、StringBuffer与StringBuilder

String

String的创建机制

因为String在Java中使用过于频繁,Java为了避免在系统中产生大量的String对象,引入了字符串常量池的概念。

其运行机制是:

  • 创建一个字符串时,首先检查池中是否有值相同的字符串对象(equals决定),如果有则不需要创建而是直接从常量池中找到的该字符串对象的引用;
  • 如果没有则新建一个字符串对象,返回该对象引用,并且将新创建的字符串对象放入池中
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×