Java并发编程的特性

1. Java内存模型:

java内存模型

       java的对象都是在主内存(物理内存)中创建的;而各个线程在执行的时候,为了隔离相互之间的影响,以及降低CPU和内存IO之间速率相差太大的影响,会将主内存中的变量读取到工作内存中(高速缓存,寄存器),然后在工作内存中操作,完成之后再写回到主内存中。
       主内存和工作内存中的操作由以下几部分操作组成,每部分均是原子性的:
内存操作

       首先通过read、load将变量的值放入工作内存中;当执行引擎需要使用变量时,使用use操作,读取工作内存的变量,操作完之后使用assign,将新的变量值写回工作内存;当需要将变量值同步回主内存时,通过store、write操作将工作内存的值刷新到主内存中。
       java虚拟机规定read和load,store和write必须成对出现。
       每次lock一个变量,将会清空该变量在工作内存中的值。

2. 并发编程的3个特性

2.1 原子性:

  • 基本变量的读取和赋值是原子性的,结合上面的操作顺序,即可得知,因为每次read和write均是原子性的。但是java对于64位的变量,允许拆分成两部分的32位分别操作,因此对于double和long的读写,可能没有原子性。但实际上大多数jvm在具体实现时,也都将这两种类型的读写实现为原子性了。
  • synchronized。必须先lock才能进入同步代码块,因此一个线程在unlock之前,其他线程无法进入同步代码块,保证了同步代码块的原子性。

2.2 可见性

  • volatile。被volatile修饰的变量,在每次use之前,都需要先read和load,因此保证了每次读取的都是主内存的最新值;而在每次assign的时候,都需要store和write回主内存,因此保证了最新值可以刷新到主内存。
  • synchronized。每次进入同步代码块之前,都会先lock住变量,然后清空工作内存中共享变量的值;每次退出同步代码块之前,都会先将工作内存中的共享变量刷新到主内存中,然后unlock(关于synchronized的可见性,可以见:synchronized可见性)。

2.3 有序性

  • volatile。JVM规定,在volatile之前的操作,不能重排序到volatile之后。
  • synchronized。同步代码块对于不同线程来说串行进入的。

3. 一段同步代码的分析

import java.util.Date;
import java.util.concurrent.TimeUnit;

public class ThreadTest extends Thread {
 private static boolean stop;
 // private static volatile boolean stop;

 synchronized void f() {}

 @Override
 public void run() {
  System.out.println("start");
  while (!stop) {
   // f();
   // System.out.println("loop");
  }
  System.out.println("end");
 }

 public static void main(String[] args) {
  try {
   ThreadTest t = new ThreadTest();
   t.start();
   Thread.sleep(2000);
   t.stop = true;
   System.out.println("stop");
   Thread.sleep(2000);
  } catch (InterruptedException e) {
   System.out.println(e.getMessage());
  }
 }
}

       由于stop变量的不可见性的,所以线程并不会在stop设为true之后停止,原因是stop不具有可见性,但是如果用volatile修饰stop,或者在循环中调用synchronized方法,则可以使线程停下来;另外循环中调用控制台输出,也可以停下线程,猜测是输出到控制台的时候调用了synchronized方法。
       这段类似的代码在《Effective Java》中提到,编译器在优化的时候也可能优化为:

while(!stop){
    while(true){
        // do something
    }
}

4. 双重锁单例模式的一个细节分析

private static volatile TestSingleton instance = null;  

    public static TestSingleton getInstance() {  
           if (instance == null) {    
             synchronized (TestSingleton.class) {    
                if (singleton == null) {    
                   singleton = new TestSingleton();   
                }    
             }    
           }   
           return instance;  
    }  

       上面是一个典型的单例实现方式,关于volatile的作用,大部分地方会解释为可见性,但是此处使用synchronized已经保证了singleton的可见性,为何还要使用volatile。其实这里更多的是考虑有序性,防止代码重排序,如果在创建TestSingleton的时候,先将TestSingleton对象的内存地址返回给singleton了,而TestSingleton还没有完成初始化,此时,其他线程可能拿到一个不完整的singleton。具体可见:单例模式的实现,在这篇博文的评论中有说到。

参考资料:

  • 《深入理解Java虚拟机》
  • 《Effective Java》
  • http://blog.csdn.net/jason0539/article/details/23297037
  • http://www.imooc.com/video/6780

发表评论

电子邮件地址不会被公开。 必填项已用*标注