Java线程与并发编程实践----同步
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java线程与并发编程实践----同步相关的知识,希望对你有一定的参考价值。
上一节我们知道了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线程与并发编程实践----同步的主要内容,如果未能解决你的问题,请参考以下文章