我是靠谱客的博主 沉默鸡,最近开发中收集的这篇文章主要介绍java 多线程 原子性和易变性的理解(还有可视性),觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

volatile关键字还确保了应用中的可视性。如果你将一个域声明为volatile的,那么只要对这个域产生了写操作,那么所有的读操作就都可以
看到这个修改。即便使用了本地缓存,情况也确实如此,volatile域会立即被写入到主存中,而读取操作就发生在主存中。
    理解原子性和易变性是不同的概念这一点很重要。在非volatile域上的原子操作不必刷新到主存中去,因此其他读取该域的任务也不必看到这
个新值。如果多个任务在同事访问某个域,那么这个域就应该是volatile的,否则,这个域就应该只能经由同步来访问。同步也会导致主存中刷新,
因此如果一个域完全由synchronized方法或语句块来防护,那就不必将其设置为是volatile的。
    一个任务所作的任何写入操作对这个任务来说都是可视的,因此如果它只需要在这个任务内部可视,那么你就不需要将其设置为volatile的。
    当一个域的值依赖于它之前的值时(例如递增一个计数器),volatile就无法工作了。如果某个域的值受到其他域的值的限制,那么volatile
也无法工作,例如Range类的lower和upper边界就必须遵循lower<=upper的限制。
    使用volatile而不是synchronized的唯一安全的情况是类中只有一个可变的域。再次提醒,你的第一个选择应该是使用synchronized关键字,
这是最安全的方式,而尝试其他任何方式都是有风险的。
    什么才属于原子操作呢?对域中的值做赋值和返回操作通常都是原子性的,但是,在C++中,i++;i+=2都可能是原子性的。但是在C++中,这要
取决于编译器和处理器。你无法编写依赖于原子性的C++跨平台代码,因为C++没有像Java(在Java SE5中)那样一致的内存模型。

    在Java中,上面的操作可能不是原子性的,正如下面的方法所阐释的JVM指令中可以看到的那样:


//concurrency / Atomicity.java
//{Exec: javap -c Atomicity}
public class Atomicity{
int i;
void f1(){i++;}
void f2(){i+=3;}
}/* Output:(Sample)
....
void f1():
Code:
0:
aload_0
1:
dup
2:
getfield
#2; //Field i:I
5:
iconst_1
6:
iadd
7:
putfield
#2; //Field i:I
10:	return
void f2():
Code:
0:
aload_0
1:
dup
2:
getfield
#2;
//Field
i:I
5:
iconst_3
6:
iadd
7:
putfield
#2:
//Field
i:I
10:	return
*/

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**每条指令都会产生一个get和put,它们之间还有一些其他的指令。因此在获取和放置之间,
* 另一个任务可能会修改这个域,所以,这些操作不是原子性的:
* 如果你盲目地应用原子性概念,那么就会看到在下面程序中的getValue()符合上面的描述:
* 该程序降到奇数值并终止。
*
* 解决办法getValue()和evenIncrement()必须是synchronized的。
* 在诸如此类情况下,只有并发专家才有能力进行优化,而你还是应该运用Brian的同步规则。
*
* @create @author Henry @date 2016-11-28
*
*/
public class AtomicityTest implements Runnable {
/**
* 由于i也不是volatile的,因此还存在可视性问题。
*/
private int i=0;
/**
* 尽管return i 确实是原子性操作,但是缺少同步使得其数值可以子在处于不稳定的中间
* 状态时被读取。
* @return
*/
public int getValue(){return i;}
private synchronized void evenIncrement(){i++;i++;}
@Override
public void run() {
while(true)
evenIncrement();
}
public static void main(String[] args) {
ExecutorService exec=Executors.newCachedThreadPool();
AtomicityTest at=new AtomicityTest();
exec.execute(at);
while(true){
int val=at.getValue();
if(val%2!=0){
System.out.println(val);
System.exit(0);
}
}
}
}

/**
*
SerialNumberGenerator与你想象的一样简单,如果你有C++或其他低层语音的背景,那么可能会期望
* 递增是原子性操作,因为C++递增通常可以作为一条微处理器指令来实现(尽管不是以任何可靠的、跨平台的形式实现)。
* 然而正如前面注意到的,Java递增操作不是原子性的,并且涉及一个读操作和一个写操作,所以即使是在这么简单的操作中,
* 也为产生线程问题留下了空间。正如你所看到的,易变性在这里实际上不是什么问题,真正的问题在于nextSerialNumber()
* 在没有同步的情况下对共享可变值进行了访问。
*
基本上,如果一个域可能会被多个任务同时访问,或者这些任务中至少有一个是写入任务,那么你就应该将这个域设置为
* volatile的。如果你将一个域定义为volatile,那么它就会告诉编译器不要执行任何移除读取和写入操作的优化,
* 这些操作的目的是用线程中的局部变量维护对这个域的精确同步。实际上,读取和写入都是直接针对内存,而却没有被缓存。
* 但是,volatile并不能对递增不是原子性操这一事实产生影响。
*
* @create @author Henry @date 2016-11-29
*
*/
public class SerialNumberGenerator {
private static volatile int serialNumber=0;
public static int nextSerialNumber(){
return serialNumber++;
}
}

import java.util.Iterator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
// : concurrency/SerialNumberChecker.java
// Operations that may seem safe are not.
// when threads are present.
// {Args:4}
/**
* 为了测试SerialNumberGenerator,我们需要不会耗尽内存的集(Set),以防需要花费很长的时间来探测问题。
* 这里所示的GircularSet重用了存储int数值的内存,并假设在你生成序列数时,产生数值覆盖冲突的可能性极小。
* add()和contains()方法都是synchronized,以防止线程冲突。
*
* Reuses storage so we don't run out of memory:
*
* @create @author Henry @date 2016-11-29
*/
class CircularSet {
private int[] array;
private int len;
private int index = 0;
public CircularSet(int size) {
array = new int[size];
len = size;
// Initialize to a value not produced
// by the SerialNumberGenerator
for (int i = 0; i < size; i++)
array[i] = -1;
}
public synchronized void add(int i) {
array[index] = i;
// Wrap index and write over lod elements;
index = ++index % len;
}
public synchronized boolean contains(int val) {
for (int i = 0; i < len; i++)
if (array[i] == val)
return true;
return false;
}
}
/**
* SerialNumberChecker包含一个静态的CircularSet,它持有所产生的所有序列数;另外还包含一个内嵌的SerialChecker类,
* 它可以确保序列数是唯一的。通过创建多个任务来竞争序列数,你将发现在和谐任务最终会得到重复的序列数,如果你运行的时间
* 足够长的话。为了解决这个问题,在nextSerialNumber()前面添加了synchronized关键字。
*
* 对基本类型的读取和赋值操作被认为是安全的原子性操作。但是,正如你在AtomicityTest.java中看到,当对象处于不稳定状态时,
* 仍旧很可能使用原子性操作来访问它们。对这个问题做出假设是棘手而危险的,最明智的做法就是遵循Brian的同步规则。
*
* @create @author Henry @date 2016-11-29
*
*/
public class SerialNumberChecker {
private static final int SIZE = 10;
private static CircularSet serials = new CircularSet(1000);
private static ExecutorService exec = Executors.newCachedThreadPool();
static class SerialChecker implements Runnable {
@Override
public void run() {
while (true) {
int serial = SerialNumberGenerator.nextSerialNumber();
if (serials.contains(serial)) {
System.out.println("Duplicate: " + serial);
System.exit(0);
}
serials.add(serial);
}
}
}
/**
* 运行结果:
* No duplicates detected
* Duplicate: 3920303
* Duplicate: 3920302
*
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
for (int i = 0; i < SIZE; i++) {
exec.execute(new SerialChecker());
// Stop after n seconds if there's an argument;
if (true) {// args.length>0
TimeUnit.SECONDS.sleep(new Integer("4"));// args[0]
System.out.println("No duplicates detected");
//System.exit(0);
}
}
}
}


最后

以上就是沉默鸡为你收集整理的java 多线程 原子性和易变性的理解(还有可视性)的全部内容,希望文章能够帮你解决java 多线程 原子性和易变性的理解(还有可视性)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(31)

评论列表共有 0 条评论

立即
投稿
返回
顶部