前段时间一直在学习多线程相关的知识,目前也算有了一个总体的认识,今天呢,主要从总体介绍一下,只谈造火箭,拧螺丝这种细节还须要本身深究。html
首先是操做系统级别对于多线程的支持,由 CPU 的多级缓存、缓存一致性、乱序执行优化等问题而设计出 Java 内存模型。关于这部分我前面已经总结过。算法
说完了操做系统级别的多线程的后备知识以及 Java 内存模型的设计,接着说说 多线程的实现以及Java 中的多线程是怎么实现的,具体能够看这篇。多线程
多线程实现原理工具
上面说的都是一些原理,深层次的东西,你如果不懂,也不影响你使用 Java 多线程来编码,无非就是继承 Thread 实现 Runnable 。可是呢,使用多线程,不可避免的就会出现线程安全问题。性能
虽然说咱们也知道,无论三七二十一我使用 synchronized 关键字就好使,可是呢,想要知其因此然的同窗仍是须要好好学习一下上面说的那些原理的。并且,处理线程安全问题会涉及到性能问题,这就是高手之间的差异了。学习
这么一说,咱们的重点就放在如何保证线程安全的问题上了,首先,线程安全的特性有 3 个,原子性,可见性,有序性。咱们主要看看 Java 中都是使用什么方式来保证这 3 个特性的。优化
主要的体如今 atomic 包、CAS 思想、synchronized 锁、Lock 锁、volatile 关键字、还有一个很是重要的 JUC 包,别急,后面一个一个的说。ui
哎,发现我已经说了 atomic 和 CAS 了,atomic包、synchronized | Java 中线程安全 关于 volatile 关键字,在 JVM 中的语义,即为防止指令重排并添加内存屏障,简单来讲就是限制指令的执行顺序,并默认添加一些指令(内存屏障)以达到有序和可见性的目的。比方说在 volatile 变量读操做以前必须先完成写。
简单提一下,使用 volatile 修饰的变量不必定是线程安全的,除非对变量的操做都是原子性的。
在说 JUC 以前,先说一种比较讨巧的手段来处理多线程问题,咱们知道多线程中存在问题主要就是在不一样线程之间对共享变量进行操做,使得数据变脏。那咱们在多线程中杜绝出现共享变量就好了呀,基于此,也就出现了 ThreadLocal 这个类。
ThreadLocal 也被称为线程局部变量,实际上它是封装了一个当前线程为 K 的一个 Map,将须要用到的变量存进 Map,用的时候取出来,而这个 Map 又是线程私有的,其它线程没法访问,天然是线程安全的。
使用 ThreadLocal 有个实际的例子,咱们在过滤器中添加变量到 ThreadLocal 中,在拦截器中删除 ThreadLocal 中的值,这个变量多是用户惟一性的标识或是其它,由于每个请求都会触发一个线程执行,通过过滤器以后设置好变量,在方法中使用,而在拦截器中对请求处理以后便可对 ThreadLocal 中的数据进行清除,否则会发生内存泄漏。
为何 ThreadLocal 能够保证线程安全,由于它存放的变量都是线程私有的,还有一种咱们常常定义的变量也是线程私有的,那就是咱们定义的局部变量,方法内的变量。由于这些变量是存放在线程栈上面,这部分区域是线程私有的,也就是咱们以前说的工做内存的概念,因此咱们常常写局部变量,而历来没有考虑过线程安全问题,实际缘由在这。
线程私有能够保证线程安全,可是全局变量又是少不了一环,偶尔定义了那么一个,一不留神,在多线程环境中可能就会出错。最最常说的无非就是那几个集合类和 String 相关的类,固然还有其它的不安全的类,可能你已经使用了但还不知道。
既然知道不安全了,那在多线程的环境中就避免使用它,而贴心的 JDK 也提供了一套与之对应的安全的类给咱们使用,咱们要作的就是知道哪些类是不安全的,对应的安全的类是什么以及如何保证的安全。
先说 String、StringBuffer、StringBuilder 之间细微的差异,String 是不可变类,不可变类自己就是定义的时候就是线程安全的,关于不可变类这又是一块知识,暂时不说了。
可是也正是由于 String 的不可变,因此每次对字符串的修改都会新建立一个字符串,而最终咱们想要获得的可能就是最后的一个值,中间那些中间值太占空间。嗯,优化以后就有了后面两兄弟。
简单说 StringBuffer 是线程安全的,而 StringBuilder 是不安全的,这两个较 String 都是可变对象。因此说,在单线程中使用 StringBuilder 代替 String ,多线程中使用 StringBuffer 代替 String 是个不错的选择。对了,StringBuffer 保证线程安全的手段也就是使用 synchronized 方法。
还有就是关于集合类相关的线程安全操做,有一个 Collections 工具类,提供了一套 synchronizedXXX 方法,好吧,都写在脸上了,这样一来,List、Set、Map 都摇身一变安全了。
还有一些古老的实现类,像什么 Vector,Hashtable,也是能够实现线程安全的,可是一来是基本不使用,二是有些状况下可能并不能保证线程安全,像 Vector 中的 add 和 remove 方法就是没有进行同步操做,在某些状况下就会出现不安全。
不知道大家有没有发现,这些古老的实现类中实现安全的方式无非就是添加 synchronized 关键字。有没有高级一点的操做啊,确定有,可是今天扯不动了,下次再说吧!
简单总结一下,今天说了什么,回顾以前的文章并理顺了总体的思路,从底层原理到 JMM,从多线程的实现到如何保证线程安全,又举例说明了 JDK 中类的设计思路,后面的 JUC 设计的更秀。
说的挺多,可是万变不离其宗,作好分类,学习记忆更便捷,Java 保证线程安全底层看 3 大特性,语言实现层面主要看锁,锁也就两大类 synchronized 和 Lock,只不过度支极多而已,还有一层是算法层面,像什么 CAS 。可是,回到 CPU 时代还他么都是 0、1 代码,因此,不要怂就是干!