`
airu
  • 浏览: 270863 次
  • 性别: Icon_minigender_1
  • 来自: 云南
社区版块
存档分类
最新评论

java-concurrency 之 JMM

 
阅读更多
自从JSR-133在jdk5中完善后,java的并发能力大大提升。我们可以使用concurrent包来完成很多线程工作,而不用处理线程所带来的复杂性。这里要说的自然是JMM。这是JSR-133中描述的,并且在jdk1.5后得到完善。

如果抛开JMM谈java并发,就显得毫无底气了。因为JSR-133正是为了线程才针对JMM的规范。

如果你了解计算机的缓存机制,其实JMM很好理解。为了使得各个线程的数据不被其他线程无意或恶意修改,JMM把各个线程的私有内存和主要内存(可以看作是堆上的内存)分开。需要使用到主存的数据,就首先read到线程内存中,然后在load到变量里。于是,就多了8种操作。
lock 锁住主存变量,线程独享
unlock 解锁主存变量,对应lock
read  从主内存中读取变量到工作内存(线程内存)
load  从工作内存中读取read到的变量赋值给工作内存中的变量副本。
use   执行引擎使用到工作内存的变量(读取)
assign 执行引擎对变量的修改后赋值给工作内存变量(改变)
store  把工作内存中的变量放入到主内存中
write  把从工作内存中store得到的变量赋值给主内存变量。

可以看到,这几种操作,是必须遵守一定的顺序的。
1,成对出现。比如 read 和 write, load 和 store
2,确保assign后调用store,不允许没有assign操作,而store
3,新变量只能在主存中产生。也就是 use 和 store之前,必须有 load 和 assign
4,一次只能有一个线程lock操作,同一个线程可以多次lock(可重入)
5,lock之前,必须同步变量到主存。store 和 write
6,lock清空工作内存中的变量,重新 load 或者 assign
7,unlock不能单独使用,必须先有lock。

至此,JMM就初现雏形了。
详细的可以看 Doug Lea大牛的文章:http://g.oswego.edu/dl/jmm/cookbook.html

接下来,我们就要问了,JMM是要解决些什么具体问题呢。
1、原子性 atomicity
2、可见性 visibility
3、有序性 ordering

原子性,就是保证程序的执行不被打断(线程中断)。read,load, assign ,use, store, write 对于基本数据类型都是原子性的(除去long,double,但是多数虚拟机的实现都尽量使得这两个类型的读写是原子性)。如果要对更大的范围保证原子性,有lock和unlock(为开放),这里有更高层次的 monitorenter 和 monitorexit,这就是synchronized 关键字的指令。

可见性,就是一个线程修改了某个变量,其他线程可以立即见到。由于每个线程所做的操作,只能是修改自己线程中的工作内存,所以并不是其他线程立即可见的。volatile关键字可以使得对他的修改,对其他线程立即可见。后面会讲到。当然,synchronized也可一,因为在unlock了之前,是必须要store 和 write的。还有一个final,也放在后面说。

有序性,线程内的看到自己的执行总是有序的,而线程之外都是无序的。这里有编译和JIT优化的问题,也有工作内存和主存同步的问题。volatile 也是禁止重排序的(虚拟机支持)synchronized因为 lock 一个线程独享,所以也是可以的。

对于有序性,这里如何判断jvm是否要reordering,这里有一个先行发生原则。也就是先发生的操作,可以被之后发生的操作观察到,这样就不许要使用之前说的那些关键字了。但是这样的原则(happens-before)只适用以下几个细则中:
1,(Program Order Rule)在同一个线程中,按代码顺序执行。也就是根据控制流,执行到哪里就是哪里。
2,(Monitor Lock Rule) unlock 操作先行发生后面的 lock ,针对同一个锁。
3,(Volatile Variable Rule) 对于一个volatileo变量的写先于后面对这个变量的读。
4,(Thread start Rule) 线程的start方法先行发生与此线程的其他动作
5,(Thread Termination Rule)线程的所有操作都优先于对此线程的终止检测。
6,(Thread Interruption Rule)对interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生。
7,(Finalizer Rule) 一个对象的初始化优先与finalize方法调用
8,(Transitivity) A优先与B , B 优先于 C , 则A 优先于 C
9 如不符合以上几点,JVM都可能reordering
分析:
private int value;
public int getValue() { return value;}
public void setValue(int value) { this.value = value; }

假设当前value值为1, 线程A 调用setValue(2) 然后(很快)线程B调用getValue(),那么线程B 会得到什么值呢?
首先,不在同一个线程,第一条不适用,也没有synchronized,volatile,等关键字,也不是发生在线程的启动终止等,所以2,3,4,5,6,7,都不适用。没有第三个参考线程,8 也不适用。所以,就是9,那么JVM就不能保证顺序性,也就是说,B 可能得到1,也可能得到2.

现在返回来看 volatile 关键字,他主要用于Visibility和Ordering
volatile的Visibility可以用JMM中的几条操作来说明。也就是use前,必须首先load,并且如果最后一个操作不是use,就不去load。这就要求每次使用前要刷新变量值。
其次,只有assign的时候,才store,如果最后一个操作不是store,就不允许assign。这就是说明每次修改后的变量要被更新,如果变量不能被store,就不被更新。
然后是volatile的Ordering确保。如果一个线程对volatile变量(V)的使用先于对另一个votaile变量(W)的使用,那么这个线程对V的read,write也一样要先于对W的read,write。这样保证不会被指令重排序优化。
那么 volatile 应用在什么地方呢。
首先看这样的代码
private volatile boolean run;
public void stop(){
    run = false;
}
...
public void exec() {
while(run) { dosomething... } 
}


但是有一点,并非支持Visibility的就一定是线程安全的。
再看一个例子
public class Counter {
    private volatile int visitorCount;
    
    public Counter(){
        this.visitorCount = 0;
    }
    
    public void increcement(){
        visitorCount++;
    }
    
    public int getCount(){
        return this.visitorCount;
    }
    
    public static void main(String[] args) throws Exception {
        final Counter counter = new Counter();
        for(int i = 0; i < 20 ;i++){
            Thread t = new Thread(new Runnable(){
                public void run(){
                    for(int j = 0; j < 100;j++)
                        counter.increcement(); 
                }
            });
            t.start();
        }
        while(Thread.activeCount() > 1)
            Thread.yield();
        System.out.println(counter.getCount());
    }
}

看看这个输出多少。理想情况是2000吧,实际会小于这个数字。这个问题的原因就是,虽然volatile是线程可见的,但是由于我们的改变不是原子操作,而且,在修改之前,我们需要获取到这个值,但是当我们修改的时候,可能这个值已经改变了。所以必须对增加值进行同步,才能保证正确性。这样说明,对于线程可见的变量,依然要同步才能确保线程安全。
补从一点,其实++操作不是原子性的,我们可以这样改:

import java.util.concurrent.atomic.AtomicInteger;

public class Counter {
    private volatile AtomicInteger visitorCount;
    
    public Counter(){
        this.visitorCount = new AtomicInteger(0);
    }
    
    public  void increcement(){
        visitorCount.incrementAndGet();
    }
    
    public int getCount(){
        return visitorCount.get();
    }
    
    public static void main(String[] args) throws Exception {
        final Counter counter = new Counter();
        for(int i = 0; i < 20 ;i++){
            Thread t = new Thread(new Runnable(){
                public void run(){
                    for(int j = 0; j < 100;j++)
                        counter.increcement(); 
                }
            });
            t.start();
        }
        while(Thread.activeCount() > 1)
            Thread.yield();
        System.out.println(counter.getCount());
    }
}


对AtomicInteger的操作是原子性的。也就是我们在取得值和操作值中,是只允许一个线程在操作的。除此之外,我们就是用synchronized了。

对于final关键字,他本身就是使得变量是不可变的。所以对于基本数据是线程安全的,对于对象引用,则对引用是不可变的。
分享到:
评论

相关推荐

    java-concurrency编程内部分享.zip_java-concurrency

    这份“java-concurrency编程内部分享”压缩包包含了关于Java并发编程的一份PPT,它可能涵盖了以下几个关键知识点: 1. **线程与进程**:首先,会讲解线程和进程的基本概念,它们的区别和联系。在Java中,线程是执行...

    Java 7 Concurrency Cookbook EN pdf及代码

    《Java 7 Concurrency Cookbook》是一本专注于Java并发编程实践的书籍,主要针对Java 7版本。这本书旨在帮助开发者在多线程环境下更好地理解和解决性能和正确性问题。以下是本书中涉及的一些关键知识点: 1. **Java...

    java8源码-concurrency:java并发总结

    java8 源码 Java 并发多线程从简到全 参考: 目录: [TOC] 相关文档: kwseeker/netty Executors线程池.md 1 基本概念 1.1 CPU与线程的关系 1.2 线程与进程的区别和关系 1.3 吞吐量 1.4 线程安全 1.5 线程声明周期 ...

    java concurrency

    结合`java-concurrency-core-learning`这个文件名,我们可以推测这可能是一个关于Java并发核心概念的学习资料。学习这个主题,你可以深入理解Java并发编程的原理,包括线程安全的数据结构、锁机制、并发容器如...

    Java.9.Concurrency.Cookbook.2nd.Edition

    《Java 9 Concurrency Cookbook 2nd Edition》是一本针对Java并发编程的权威指南,旨在帮助开发者深入理解和熟练掌握Java 9中的并发特性。这本书在2017年出版,结合了最新的Java版本特性,提供了丰富的实践案例和...

    Java - The Well-Grounded Java Developer

    - **Java Memory Model (JMM)**: Detailed explanation of the JMM, including its rules and the concept of "Happens-Before" to ensure correct concurrent behavior. - **Locks and Conditions**: Overview of...

    Addison.Wesley.Java.Concurrency.in.Practice.May.2006

    9. **Java内存模型**:解析Java内存模型(JMM),解释了变量可见性、有序性以及数据一致性的问题。 通过学习《Java并发实践》,开发者可以更好地理解和驾驭Java并发编程,从而编写出更高效、更可靠、更易于维护的...

    Java-JUC-多线程 进阶

    JUC(Java Utilities for Concurrency)是 Java 并发编程的核心API,提供了多种并发编程机制,帮助开发者编写高效、可靠的并发程序。JUC 中包括了各种锁机制、原子变量、并发集合类、线程池、异步回调等多种机制,...

    Java Concurrency in Practice

    总之,《Java Concurrency in Practice》是理解Java多线程编程的最佳资源之一,无论你是初学者还是经验丰富的开发者,都能从中受益匪浅。通过学习这本书,你可以掌握处理并发问题的技巧,写出更稳定、高效的Java并发...

    Addison.Wesley.Java.Concurrency.in.Practice.May.2006.zip

    2. **并发工具**:讨论了Java并发库(Java Concurrency API,自Java 5开始引入)中的核心组件,如Executor框架、Future、Callable、CyclicBarrier、Semaphore、CountDownLatch和Phaser等,这些都是构建并发程序的...

    Java Concurrency.chm

    书名中的"Java Concurrency"直译为“Java并发”,它涵盖了Java平台上的多线程和并发处理的各个方面,包括基本概念、设计模式、工具类以及最佳实践。这个CHM文件很可能是该书的电子版,或者是某个关于Java并发的教程...

    Java Concurrency的一些资料

    9. **Java内存模型(JMM)** - 描述了线程之间如何共享和交互数据,包括内存可见性和有序性保证。 10. **线程状态与生命周期** - 新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、等待...

    Java Lesson: Concurrency

    Java并发编程是Java平台的核心特性之一,它使得开发者可以创建能够充分利用多核处理器能力的高效应用程序。本课程将深入探讨Java中的并发概念,包括基础支持和高阶API,特别是`java.util.concurrent`包中的工具。 ...

    [Java并发编程实践].(Java.Concurrency.in.Practice).Brian.Goetz.文字版(1)

    10. **Java内存模型**:Java内存模型(JMM)定义了线程间如何共享和通信,书中解释了其工作原理,包括volatile关键字的作用以及happens-before关系。 以上只是《Java并发编程实践》一书中的部分关键知识点,实际...

    java concurrency in practice.rar

    - **JMM(Java Memory Model)**:定义了线程之间如何共享和通信数据。 - **happens-before原则**:保证了线程间的有序性和可见性。 通过阅读《Java并发编程实践》,开发者能够掌握Java并发编程的核心概念、工具...

    [Java并发编程实践].(Java.Concurrency.in.Practice).Brian.Goetz.文字版.rar下载地址

    《Java并发编程实践》是Java并发编程领域的一本经典著作,由Brian Goetz等多位作者共同撰写。这本书深入探讨了如何在Java...这本书是Java并发编程的必读之作,无论你是初学者还是有经验的开发者,都能从中受益匪浅。

    Addison.Wesley.Java.Concurrency.in.Practice.May.2006.pdf

    9. **JVM内存模型**:阐述了Java内存模型(JMM)和其对并发的影响,包括可见性、有序性和一致性,帮助开发者理解内存屏障和volatile的底层原理。 10. **性能调优**:提供了一些并发性能调优的指导原则和最佳实践,...

Global site tag (gtag.js) - Google Analytics