码迷,mamicode.com
首页 > 编程语言 > 详细

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

时间:2018-01-13 19:00:32      阅读:155      评论:0      收藏:0      [点我收藏+]

标签:调度   相关   包含   标记   执行   相互   实现   字节   zed   

    上一节我们知道了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本来就可以确保线程访问的安全性,它修饰一个

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


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

标签:调度   相关   包含   标记   执行   相互   实现   字节   zed   

原文地址:http://blog.51cto.com/12222886/2060621

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!