零蚀
原子化问题
-
volatile
-
volatile是最轻量级的同步机制,它是针对变量来进行的操作,volatile有以下两个特性:
-
可见性: volatile对于线程来说是可见的,什么事可见的,先说说什么事不可见的,一般我们给一个线程的一个变量进行复制,此时,其他线程不知道这个变量发生了变化,只有当这个值存储进了内存,其他的线程进行调用时候才发现这个变量发生了变化,这就是不可见。而被volatile关键字进行了修饰之后,变量的改变变为可见的,其他线程都能即时发现内容的变化。volatile它的内存消耗和普通变量基本完全一致,只是写操作时会有“屏障”,会比平常变量费事些。所有经常会有以下写法:
-
原子性(待定): 网上说volatile是没有原子性的,然后用value++来论证、说事,我就觉得无语 value++对应的表达式是value =value +1,表达式会有原子性??WTF,就证明了volatile没有原子性,emmmm,我只能说厉害!关于volatile的原子性,这里是指的double和long这种变量没有原子性,不断的赋值中会改变,强调的是这个!!!之前的演示代码我觉得是有问题的,因为并没有核实。并且double和long在JDK11上测试并没有出现非原子性问题的错误,后续遇到在说吧(可能是JDK版本的问题),所以这个关键字现在的可用性值得商榷。
-
-
-
线程安全
-
其实所有的同步问题都是原子化问题,这里有如果需要详细了解 可见Android线程篇。
-
安全程度,由强到弱,有下类五个:
- 不可变:一般我们将变量设置为final则是不可变类型,像String,对他进行concat(),substring(),replace()都不会影响他的原来的值的变化,其实可以知道(常量池的优化也不会让他改变的吧),在或者Integer……除此之外,还有BigInteger但是原子类型的AtomicInteger和AtomicLong却是可变的。
- 绝对线程安全:热河情况下不需要额外的同步操作,绝对安全是以牺牲任何性能而只保持原子性的概念,一般即使我们全部synchornized包裹,也不能保障绝对安全。
- 相对线程安全:一般我们所说的线程安全就是指相对的线程安全。像hashTable,Collections,Vector,StringBuffer……
- 线程兼容:线程兼容并不是指类的对象是线程安全的,但是可以通过某种手段使它线程安全,Java中大部分的类都是线程安全的。
- 线程对立:指无论使用任何同步手段,在代码里都是无法实现并发。而且这种方式很容易导致一些死锁等具有危害性的结果。
-
在java里同步一般会使用synchronized的块结构,经过javac编译后,会生成monitorenter和monitorexit两个指令。这两个指令都需要一个reference类型参数来作为加锁和解锁的对象。
-
加锁是一个重量级的操作,因为,这必然导致内核从用户态到核心态的转换,因此,synchornized是java中一个重量级的操作,一般在阻塞线程时会有一个自旋的操作,防止频繁的切换核心态。
-
-
锁优化
- 自旋锁:先说一下自旋,自旋字面意思,就是自己跳进了一个毫无意义的循环中等待,其实他的操作也确实是如此,它不是阻塞线程放进一个排队队列里,而是让他执行一个毫无意义的循环,然后等待事件,然后争抢事件,这是一种不公平的机制。而用这种方式编写的锁就是自旋锁。
- 锁消除:在某些即时编译的情况下,我们需要同步某些代码,但是这些代码在执行时不会产生竞争关系,所以此时就会使用锁消除。
- 轻量级锁:轻量级锁主要是为了在没有线程竞争的情况下,减少重量级所得的资源消耗。但是如果有竞争的情况下使用轻量级锁,还需要加入CAS操作,反而使轻量级锁比重量级锁更慢。轻量级锁的使用场景一般在类头中。
- 偏向锁:指当获取到这个锁的对象在执行后,没有其他对象获取这个锁,则这个对象不需要在进行同步了。
其实我觉得这本书这里有个遗憾,就是没有关于轻量级锁如何转变为重量级锁
,自旋场景 .etc
java的线程&协程
-
线程调度
-
线程调度时主要是安排线程对处理器的使用权的问题,调度的方式有两种,一个是协同式的抢占式。
- 协同式:在线程完成了自己的任务之后,就会主动的通知系统,然后主动切换到另一个线程上面去,这种方式最大的好处是自己完成了任务,切换操作线程自己是可知的,并切实现起来简单。但是有点也很明显,线程的执行时间不定,如果一致占有时间片的话,容易导致崩溃。
- 抢占式:线程由系统来分配时间,不得自己决定切换,在线程中一般会这么用,用
thread.yield()
来让出时间片,但是如果是自己主动获取时间是没有办法的。
while(条件){ thread.yeild(); }
-
-
协程
- 协程这个概念的兴起,主要是由于java中的经典1:1线程模型,在切换、调度上面存在着耗损资源,会造成浪费,在java中切换线程是一个重量级的操作,如果在一个数以百万的大型线程池中需要频繁的执行线程切换这是一个非常消耗性能的事情。因为A线程被中断后,他的上下文(内存信息,变量信息,寄存器,内存分页 .etc)都要拷贝一份给B线程,而B线程要处于挂起状态,等到所有信息就绪,才会执行。
- 协程主要的优势是轻量级,其次协程也分为,有栈协程和无栈协程,有栈协程主要是通过栈来进行数据的保护恢复,java后续应该会在JDK中添加有栈协程(“纤程“);(可以看看Kotlin中对协程的定义,见 Android知识点专题篇 [🔗 Android kotlin 专题])
内容参考自《深入理解java虚拟机》,无商业目的
🔗 前言
🔗 Android 知识栈
🔗 JVM 快速排序篇
🔗 NO.1 OpenJDK 前言
🔗 NO.2 内存区域&回收算法
🔗 NO.3 垃圾收集器&ClassLoader