继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Java线程与并发编程实践----同步

远看寒山石径斜
关注TA
已关注
手记 268
粉丝 25
获赞 149

    上一节我们知道了java如何创建线程并启动,当线程之间没有交互,程序开发就十分简单了,但如果线程之间发生交互,通过共享变量的方式

进行交互,就会引发很多线程不安全问题,如,竞态条件数据竞争以及缓存变量

    竞态条件:当计算的正确性取决于相对时间或者调度器所控制的多线程交叉时,竞态条件就会发生。如下例子:

if(a == 10.0){    b = a / 2.0;}

    假如一条线程已经执行完了if(a == 10.0),突然被调度器所停止,另一条线程开始执行,并且修改a = 20,此时继续执行第一条线程,则b = 10。

这就产生了问题

    数据竞争:数据竞争指的是两条或两条以上的线程并发的访问同一块内存区域,同时其中至少有一条是为了写,而且这些线程没有协调对那块内存

域的访问。当满足这些条件的时候,访问顺序就是不确定的。依据这种顺序,每次运行都可能会产生不同的结果

    缓存变量:为了提升性能,编译器Java虚拟机以及操作系统会协调在寄存器中或处理器缓存中缓存变量,而不是依赖主存,每条线程都会有其自己的

变量拷贝。当线程写入这个变量的时候,其实是写入自己的拷贝;其他线程不太可能看到自己的变量拷贝发生更改。

    同步可以解决以上问题,方式有同步代码块和同步方法。

//同步代码块synchronized(lock){    //需要同步的代码}//同步方法public synchronized void method(){    //需要同步的代码}

    使用synchronized进行同步,它具有互斥性可见性。互斥性即为每条线程对临界区的访问都是互斥的,可见性指线程进入临界区后,都是从主存中读取

变量的值,离开时又将变量的值写入主存,因此,他总能看到共享变量最近的修改。而同步是通过监听器实现的,,每一个Java对象都和一个监听器相关联,这样线

程就可以通过释放和获取监听器的锁来上锁和解锁,而一个线程只能持有一个锁,以此来实现同步。但是锁的使用虽然可以实现同步,但也会面临死锁活锁饿死

的挑战。

    死锁:线程1等待线程2互斥持有的资源,而线程2也在等待线程1互斥持有的资源,两个线程都无法继续执行

    活锁:一个线程持续重试一个失败的操作,无法继续执行

    饿死:一个线程一直被调度器延时访问其赖以执行的资源。无法继续执行

Java语言和JVM并未提供避免以上问题的方式以及差错办法,因此主要靠我们在编程过程中自己注意,最简单的就是尽量减少同步方法、同步块之间的相互调用

    Java中还提供了一种更弱的仅仅包含可见性的同步形式,即volatile关键字,有的情况下,我们只需要关注代码的可见性问题,而不在乎他的互斥性,此时使用synchronized

就显得没有必要,我们应该考虑使用volatile,使用volatile标记的属性,线程在访问他时不会读取缓存变量的数据,而是从主存中读取。还有值的注意的是,使用volatile

修饰double和long类型时,应该避免在32位的操作系统上这样做,因为double、long是8个字节64个bit,在32位操作系统上它的读取分为两步,每一步取32位的数据,

volatile保证了可见性,但不能保证操作的原子性,因此应该注意编码。使用volatile时不能与final进行连用,因为final本来就可以确保线程访问的安全性,它修饰一个

属性,属性的引用不能被修改,而且引用也不能被缓存。


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP