在学习Java的时候,多线程是一个很重要很重要的问题。
从Java的内存模型而言,多线程需要读取内存,CPU(一个CPU代表一个线程)和内存的读取的速度不是一个等级的,那么就需要一个高效的缓存线程,通过将内存上需要操作的数据复制到缓存线程上,再由缓存线程与线程进行通讯,然后缓存线程将结果返回到内存中。
但是在多线程并发操作的时候,往往带来的问题就是线程的不安全,资源的不安全,所以线程的安全非常重要。
线程安全:
(非官方,仅个人理解):当多个线程同时访问同一块内存,或者同一个对象的时候,无需考虑这些线程在当前的运行环境下的调度和同时执行,也无需进行额外的同步而可以得到正确的结果,那么就说这个对象或者内存是线程安全的。
Java中的线程安全等级分布:
按照我的理解而言,Java中的线程安全可以分为3种,分别是绝对安全、相对安全、不安全三个等级。
绝对安全:
如果被final修饰的共享数据,是不会被修改的,因为final态为终态。如果final修饰的共享数据是对象的话,那么就需要保证fianl对自己的状态不会产生任何的影响,例如String的subString()方法返回的是一个新的子串对象,而自己却不会受到影响。
另外的情况是如果类中的每个方法都加同步synchronoized关键字修饰的话,那么这个类可以说是完全同步的,比如Vector。
相对安全:
相对安全是对于绝对安全而言的,例如ArrayList与Vector,StringBuilder与StringBuffer之间的关系,前者是无锁的,后者是有锁的,但是一般情况下,使用前者的效率更高而且出现不安全的几率是非常小的,如果真正会出现线程安全的问题,那么只需要在局部加锁就可以了。
不安全:
指的是多条线程访问同一块共享数据的时候会发生资源矛盾,出错的现象。典型的例如多线程卖票问题。
线程安全的实现方法:
1、同步 synchronized
非常常用,实现过程是一个当加上synchronized后,经过编译,会在同步块的前后生成一个monitorenter和moniterexit,当一个线程需要执行同步块的时候,会尝试获取对象的锁,如果对象没有加锁或者是线程已经获取得到对象的锁的时候,线程的持有锁会加1,然后如果执行moniterexit的时候,锁会减1,当锁为0的时候,该对象的锁被释放,如果此时另外线程得到锁,那么其他线程就继续阻塞式等待。
2、使用lock
lock也就是锁,lock与synchronized的区别就是多了几项高级技能,另外在多线程并发执行的时候效率更高。
第一项高级技能:等待可中断
也就是说当其他线程正在执行代码逻辑的时候,其他线程必须等待占有锁的线程完成操作,但是有的时候等待久了,或者说线程完全在里面沉睡的时候,等待时间过长,就可以放弃等待,去做自己的事情。
第二项高级技能:公平锁
在synchronized中,靠的是竞争,也就是说哪一个CPU抢到执行权,那么对应的线程可以去执行同步快的逻辑,但是lock可以开公平锁,也就是根据申请锁的时间进行获得锁。
另外,在synchronized中,代码效率不高,而lock效率会更高一点,特别是在JDK1.6之前。
多线程的缺陷:
在Java中,线程的执行是依靠我们操作系统的内核态调用原生线程进行操作的,那么这个过程是非常耗费时间的,特别是需要从内核唤醒操作系统原生线程的时候,所以多线程的的阻塞机制的是很消耗时间的。
怎么办?
锁的优化机制!!
1、自旋锁
之前说线程的等待是阻塞式的,在这种情况下,线程就像沉睡了一样,等到其他线程唤醒的时候,需要从操作系统的内核态中唤醒,那么就消耗很多的时间,所以如果不让线程沉睡,而让线程自旋(类似于loading…..),然后只要控制自旋不要太多次,那么就可以减少时间的损耗,但是会带来资源的浪费,因为线程一直在执行。
通过这一个方式可以减少线程挂起和线程恢复的时间。
2、自适应自旋锁
前面说的自旋锁是指人为控制自旋的次数,自适应是根据前面的情况去确定自旋的次数,例如前面自旋30次就可以成功获得锁,那么自适应锁可以尝试着30次,如果不行就在挂起线程的操作。类似于担保机制,冒险的方法,但是也显得虚拟机更加智能。
3、锁消除
锁消除是指虚拟机在编译的时候,会检测是否真的需要这个锁,如果不需要的话会直接消除这个锁。
4、锁粗化
如果有一段代码,层层嵌套,每一层都进行同步,那么虚拟机将会去除这些锁,在最外层加锁,这样虚拟机的加锁就变得更加智能。
okay,暂时先更新到这里。