关于线程的总结--安全,协调,开销分析

来源:互联网 发布:java.util jar包 编辑:程序博客网 时间:2024/06/05 09:56

线程安全几个焦点:

1.由于CPU的抢占式调度导致数据的不一致性
2.线程死锁问题
3.线程异常退出的处理不当问题
对于操作系统和线程运行有一定了解的应该很熟知这三个问题,我不想解释这三个问题的概念,而是想总结下对这三个问题学到java解决方案。

同步机制

同步机制用于解决第一个问题,有几种方法可以解决:
其实,所有的手段都使保证一个线程的操作的变量和对象不会因为线程切换而被搞“脏”。加锁并阻塞其他竞争线程,其实就是告诉别的线程,现在我在搞这个对象,你不要插手捣乱,等我弄完给你。

但要注意的是,处在同一竞争条件下的线程才会被阻塞。(争抢同一个对象的控制权的所有线程)
1.synchronized关键字修饰的域和方法。

synchronized(obj){    //临界区操作    /**      synchronized使历史悠久的同步方法,处于这个域中的代码要么全部执行并保存,要么全部不执行    **/}

方法

public synchronized void getXxx(){    //临界区代码    //方法调用后将不会产生线程级上下文切换(context switch)}

2.可重入锁(RetrantLock)

对已经持有的锁再加n层锁,这些锁按照顺序全部打开,竞争阻塞的线程才可能获得锁的控制权。其实,重新加锁对实现人员没什么好处,就是一个线程调用加锁后,调用自己的对象方法,而为了保证线程安全,这个对象方法也上了锁,于是锁加了一层,等方法执行结束后,显示解锁,这一层锁就消失了。

可重入锁的尝试加锁
这是一个很人性化的方案。假如你是一个线程,别的线程正在持有锁(你想要对象的控制权),你可以尝试加锁,它不给的话,你可以暂时执行一些其他事情。

if(obj.tryLock()){//尝试加锁}else{   //高点其他事情}

我认为这对多核CPU来说才是有用的。因为如果单CPU,持有锁的线程处于临界区执行,那么本线程想要做其他事情不会被阻塞,也要获得CPU的调度啊!获得调度,不意味着效率的提升,这样做可以防止本线程饿死。
然而在多核上,却可以实际上提高效率,因为线程非临界区操作可以在另一个核上运行。
线程安全是一个不能够被结构化的问题,因为具体业务逻辑应该有不同的加锁位置,如果粗略画一个大范围,加锁与解锁距离比临界区大很多,开销可是不小啊,同步地代价!
3.终止线程引起地线程破坏
声明狼藉地stop和suspend方法
终止线程会立即退出线程地执行,而退出线程的遗留操作弄脏了对象。这就是弃用的原因。而之后版本采用interrupt方法,中断线程只是简单的将线程的中断状态位设置为真。中断是想要终止的请求,并无实际的意义,线程通过检测到中断位,做一些收尾工作(需要程序员速手动处理),然后可以正常退出了。
interrupt涉及的异常interruptedException有典型的两种情况会被抛出:
1.等待期间被中断
2.中断后,调用await(),sleep(),要求线程等待的可中断方法。

主存措施

对主存下手的就是volatile关键字,一致性问题存在的关键性原因就是现在多核计算机和Cache的写回策略导致线程更改的信息没有及时反馈到主存,导致其他线程读到过期信息。如果你只是希望对一个变量进行更新,那么volatile的开销还是比同步锁小的,但依然比非同步代码开销要大。

volatile会将线程的操作的共享域每次更改保存到主存,主存的访问要比Cache和寄存器慢多了。好苍白的解释,举个栗子。

两个线程Tfirst,Tsecond分别运行在CPU1CPU2,同时对变量common进行修改。
初始状态下:主存上存在一个最新值common=initial。由于线程运行,common被装载到CPU1的cache上,接下来CPU2也开始执行。

common更改时间线如下:

CPU1 CPU2 主存 操作描述 initial uninitialed initial comon载入两个CPU 1 uninitialed initial CPU1更改 1 initial initial CPU2comon

此时出现了不一致性,因为执行写回策略的cache,会将很多被修改的字节标志位已修改,然后批量把修改后的值写入内存。
如果common是用volatile修饰的,那么CPU1的修改结果会被强制写入内存,来保存数据一致性。主存写入是比较慢的,这是用效率来换取安全。(安全性是首要的,所以这是值的)。关于并行计算机,希望可以有些推荐的书籍。

更新日志:

感觉应该留下一些实际的代码或者更多一点比如,条件(condition)什么的,但又感觉没什么必要。关于线程池,Future对象这些看起来挺高大上的,不过真的没有什么意思,封装罢了。
也许应该有更精妙的东西,持续攻城中,待更新。2017/11/17

题外话

理解java线程底层的构建会对线程安全有很大的帮助,当然底层非操作系统层次,而是java对于对象的操作,同步机制。虽然java多线程开发,多利用线程池等开发人员开发的安全且良好的实现,但讨论底层线程仍然很有意义。把别人提供的框架当成“黑盒子”,能力永远无法提高。Alas,越来理解面对如此多的框架和快速开发工具,了解底层实现,才是真正的王道。因为作者水平有限,还未完成《java concurrency in practise》的精读,无法写出更加透彻的想法和见解,会在后面,重新整理。

原创粉丝点击