`

Java的多线程编程模型

 
阅读更多

原文地址:http://blog.csdn.net/sunnydogzhou/article/details/6425686

 

Java的多线程编程模型1

Java多线程的类库封装在java.util.concurrent.*中,java1.4到1.5的变化就是引入了这个支持并发编程的类库。首先得感谢下大名鼎鼎人类库作者Doug Lea,牛人总是让人膜拜的。

1 什么是线程安全
A class is thread-safe if it behaves correctly when accessed from multiple threads, regardless of the scheduling or interleaving of the execution of those threads by the runtime environment, and with no additional synchronization or other coordination on the part of the calling code.

译成中文意思就是
多个线程访问一个类时,如果不用考虑这些线程在运行时环境下的调度和交替运行,并且不需要额外的同步及在调用方代码不必做其他的协调,这个类的行为仍然是正确的,那么这个类就是线程安全的。 

从上面的解释可以看出
无状态对象永远是线程安全的 

2 什么是原子性
多个线程执行一个操作时,其中任何一个线程要么完全执行完此操作,要么没有执行此操作的任何步骤,那么这个操作就是原子的

3 为什么线程不安全
看一个线程不安全的经典例子:
package zl.study.concurrency;

public class ReorderingDemo {
    static int x = 0, y = 0, a = 0, b = 0;

    public static void main(String[] args) throws Exception {

        for (int i = 0; i < 100; i++) {
            x=y=a=b=0;
            Thread one = new Thread() {
                public void run() {
                    a = 1;
                    x = b;
                }
            };
            Thread two = new Thread() {
                public void run() {
                    b = 1;
                    y = a;
                }
            };
            one.start();
            two.start();
            one.join();
            two.join();
            System.out.println(x + " " + y);
        }
    } 

}
在这个例子中,如果你在一个单CPU的java测试环境中做测试,你可能只会得到一种结果(0,1),然而真的只有这样一种情况么?显然不是。JVM并不能保证线程的执行顺序,即使看起来你无数次测试都是(0,1),然而切换到别的环境中,出现其它结果(1,0)仍然是无法避免的。而在多CPU的结构中,就更不能保证了。线程可能在不同的CPU上执行,从而(0,0),(1,1)都是可能的。

4 为什么会出现这种情况
导致出现这些情况的原因有很多
Java内存分配
    寄存器:我们在程序中无法控制
    栈:存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中
    堆:存放用new产生的数据
    静态域:存放在对象中用static定义的静态成员    
    常量池:存放常量,比如.class信息,String

编译器优化
    调整语句执行顺序
    变量值存于寄存器而不是内存中
CPU自身的优化
    并行或者按其他顺序执行
    CPU本身的Cache会延迟变量的值刷新到内存的时间

 

 

 

Java的多线程编程模型2--怎样才线程安全

 

在Java多线程编程模型1里面讲到了为什么线程不安全,那怎样才能做到线程安全了?

 

先来看线程工作是跟内存是怎么打交道的。

在并发的线程中,分为主内存和工作内存,主内存就是程序分配的内存,工作内存就是线程所占的内存。线程可能在工作内存中存储了某些主内存对象的副本。当线程操作某个主内存的对象时,先从主内存中将变量的值拷贝到工作内存中,然后在工作内存中改变这个值,最后将这个值刷到主内存中。

 

在<<java concurrency in pratise>>中提出了线程安全的思路

1) 不要在线程间共享变量

2) 如果不行,就要final变量

3) 还是不行,就用volatile或其它并发控制。

其实基本的思路是尽量减少共享变量,如果实在要用,则需要并发控制。

 

那非要用的时候怎么办了,这个时候就需要拿出happens-before规则来检查多线程的程序了。

 

(1)同一个线程中的每个Action都happens-before于出现在其后的任何一个Action(Program order rule)
(2)对一个监视器的解锁happens-before于每一个后续对同一个监视器的加锁(Monitor lock rule)
(3)对volatile字段的写入操作happens-before于每一个后续的同一个字段的读(Volatile variable rule.)
(4)Thread.start()的调用会happens-before于启动线程里面的动作(Thread start rule.)
(5)Thread中的所有动作都happens-before于其他线程检查到此线程结束或者Thread.join()中返回或者Thread.isAlive()==false(Thread termination rule.)
(6)一个线程A调用另一个线程B的interrupt()都happens-before于线程A发现B被A中断(B抛出异常或者A检测到B的isInterrupted()或者interrupted())(Interruption rule)
(7)一个对象构造函数的结束happens-before于该对象的finalizer的开始(Finalizer rule.)
(8)如果A动作happens-before于B动作,而B动作happens-before与C动作,那么A动作happens-before于C动作(Transitivity)

 

如果你的程序能够满足上面的发则,那么恭喜你,不会在出现并发的问题了。

 


Java的多线程编程模型3 -- 在1.5之前怎么并发

 

在java1.5之前,java在并发上面的建树不多,只提供了为数不多的方式来提供提高并发的效率。

其中synchronized关键字是使用最多的,这个看似简单的锁方式,效率奇差,所以那会,java程序员对于c++程序员的在java并发上的诟病总是无力回击。

 

在1.5之前,java提供的并发容器Vector,我们来看下具体的实现java.util.Vector

 

[java]   view plain copy
  1. public   class  Vector<E>  
  2.     extends  AbstractList<E>  
  3.     implements  List<E>, RandomAccess, Cloneable, java.io.Serializable  
  4. {  
  5.     public   synchronized   void  copyInto(Object[] anArray) {  
  6.     System.arraycopy(elementData, 0 , anArray,  0 , elementCount);  
  7.     }  
  8.     public   synchronized   void  trimToSize() {  
  9.     modCount++;  
  10.     int  oldCapacity = elementData.length;  
  11.     if  (elementCount < oldCapacity) {  
  12.             elementData = Arrays.copyOf(elementData, elementCount);  
  13.     }  
  14.     }  
  15.     public   synchronized   void  ensureCapacity( int  minCapacity) {  
  16.     modCount++;  
  17.     ensureCapacityHelper(minCapacity);  
  18.     }  
  19. ... ...  
  20. }  

 

从中可以看出,Vector是把所有的方法前面的加上了synchronized关键字

 

在来看另外的一类静态方法,这类容器可以把List在包装一层,让后就可以作为并发的容器

[java]   view plain copy
  1. List list = Collections.synchronizedList( new  ArrayList());  
  2.     ...  
  3. synchronized (list) {  
  4.     Iterator i = list.iterator(); // Must be in synchronized block   
  5.     while  (i.hasNext())  
  6.         foo(i.next());  
  7. }  

 

仔细分析会发现

[java]   view plain copy
  1. public   static  <T> List<T> synchronizedList(List<T> list) {  
  2. urn (list instanceof  RandomAccess ?  
  3.             new  SynchronizedRandomAccessList<T>(list) :  
  4.             new  SynchronizedList<T>(list));  
  5. }  

原来Collections.synchronizedList(List<T> list))这个方法最终会新建一个SynchronizedList<E>,它是继承自SynchronizedCollection<E>,来看SynchronizedList<E>的构造函数,

[java]   view plain copy
  1. SynchronizedList(List<E> list) {  
  2.     super (list);  
  3.     this .list = list;  
  4. }  

看看父类的详细的构成

 

[java]   view plain copy
  1.     static   class  SynchronizedCollection<E>  implements  Collection<E>, Serializable {  
  2.     // use serialVersionUID from JDK 1.2.2 for interoperability   
  3.     private   static   final   long  serialVersionUID = 3053995032091335093L;  
  4.     final  Collection<E> c;   // Backing Collection   
  5.     final  Object mutex;      // Object on which to synchronize   
  6.     SynchronizedCollection(Collection<E> c) {  
  7.             if  (c== null )  
  8.                 throw   new  NullPointerException();  
  9.         this .c = c;  
  10.             mutex = this ;  
  11.         }  
  12.     SynchronizedCollection(Collection<E> c, Object mutex) {  
  13.         this .c = c;  
  14.             this .mutex = mutex;  
  15.         }  
  16.     public   int  size() {  
  17.         synchronized (mutex) { return  c.size();}  
  18.         }  
  19.     public   boolean  isEmpty() {  
  20.         synchronized (mutex) { return  c.isEmpty();}  
  21.         }  
  22.     public   boolean  contains(Object o) {  
  23.         synchronized (mutex) { return  c.contains(o);}  
  24.         }  
  25.     public  Object[] toArray() {  
  26.         synchronized (mutex) { return  c.toArray();}  
  27.         }  
  28.     public  <T> T[] toArray(T[] a) {  
  29.         synchronized (mutex) { return  c.toArray(a);}  
  30.         }  
  31. ... ...  
  32. }  

 

怎样,是不是有一种恍然大悟的感觉,原来在构造函数里面弄了一个 Object mutx = this,让后所有的方法在调用的时候都synchronized(mutex)

 

相同的方法有

    public static <T> Collection<T> synchronizedCollection(Collection<T> c)

    public static <T> List<T> synchronizedList(List<T> list)

    public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)

    public static <T> Set<T> synchronizedSet(Set<T> s)

    public static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m)

    public static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s)

 

 

Java的多线程编程模型4--synchronized

 

在Java1.5之前,synchronized应该是最常用的java支持并发手段。那synchronized是怎么做到的了,从java1.0开始,java中的每个对象就一个内部锁。如果一个类的方法被synchronized关键字所修饰,那么这个对象的锁将保护整个方法。

举例来说:

public synchronized void method(){

    method body

}

等价于

public void method(){

    this.intrinsicLock.lock();

    try{

        method body;

    }finally(){

        this.intrinsicLock.unlock();

    }

}

 

从上面的代码示例可以看出,synchronized的使用方式是比较简单的。这也导致了大量的初学者在碰到java编程的时候落入陷阱里,认为既然synhronized可以搞定一切,那么不管三七二十一,只要有并发可能性的地方,就加上synchronized的关键字,这显然是不对的。在java对象中,这个java对象只有这一个内部锁,其中一个synchronized方法获取到了这个锁,另外一个synchronized方法的调用将被阻塞。

class sync{

    public synchronized void methodA(){};

    public synchronized void methodB(){};

    ... ...

 

}

methodA 和methodB在初始就是互斥的,如果methodA和methodB进入互相等待,就很容易出现死锁的情况。那如果碰到这种情况,应该怎么做了?常用的方式是在方法内部新建一个无意义的对象,然后对这个无意义的对象加锅。

[java]   view plain copy
  1. package  zl.study.concurrency.synchronize;  
  2. public   class  Sync {  
  3.     private   int  i;  
  4.       
  5.     public   void  plus(){  
  6.         Object dummy = new  Object();  
  7.         synchronized (dummy){  
  8.             i++;  
  9.         }  
  10.     }  
  11.       
  12.     public   void  minus(){  
  13.         Object dummy = new  Object();  
  14.         synchronized (dummy){  
  15.             i--;  
  16.         }         
  17.     }  
  18. }  

 

另外需要注意的是将静态类声明为synchronized方法也是合法的。举例来说,如果Sync有一个static synchronized方法,那么这个方法被调用时,bank.class这个类对象本身在jvm中将被锁住。

 

 

Java的多线程编程模型5--从AtomicInteger开始

 

AtomicInteger,一个提供原子操作的Integer的类。在Java语言中,++i和i++操作并不是线程安全的,在使用的时候,不可避免的会用到synchronized关键字。而AtomicInteger则通过一种线程安全的加减操作接口。

来看AtomicInteger提供的接口。

//获取当前的值

public final int get()

//取当前的值,并设置新的值

 public final int getAndSet(int newValue)

//获取当前的值,并自增

 public final int getAndIncrement()

//获取当前的值,并自减

public final int getAndDecrement()

//获取当前的值,并加上预期的值

public final int getAndAdd(int delta)

... ...

我们在上一节提到的CAS主要是这两个方法

    public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

    public final boolean weakCompareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

这两个方法是名称不同,但是做的事是一样的,可能在后续的java版本里面会显示出区别来。

详细查看会发现,这两个接口都是调用一个unsafe的类来操作,这个是通过JNI实现的本地方法,细节就不考虑了。

 

下面是一个对比测试,我们写一个synchronized的方法和一个AtomicInteger的方法来进行测试,直观的感受下性能上的差异

[java]   view plain copy
  1. package  zl.study.concurrency;  
  2. import  java.util.concurrent.atomic.AtomicInteger;  
  3. public   class  AtomicIntegerCompareTest {  
  4.     private   int  value;  
  5.       
  6.     public  AtomicIntegerCompareTest( int  value){  
  7.         this .value = value;  
  8.     }  
  9.       
  10.     public   synchronized   int  increase(){  
  11.         return  value++;  
  12.     }  
  13.       
  14.     public   static   void  main(String args[]){  
  15.         long  start = System.currentTimeMillis();  
  16.           
  17.         AtomicIntegerCompareTest test = new  AtomicIntegerCompareTest( 0 );  
  18.         for int  i= 0 ;i<  1000000 ;i++){  
  19.             test.increase();  
  20.         }  
  21.         long  end = System.currentTimeMillis();  
  22.         System.out.println("time elapse:" +(end -start));  
  23.           
  24.         long  start1 = System.currentTimeMillis();  
  25.           
  26.         AtomicInteger atomic = new  AtomicInteger( 0 );  
  27.           
  28.         for int  i= 0 ;i<  1000000 ;i++){  
  29.             atomic.incrementAndGet();  
  30.         }  
  31.         long  end1 = System.currentTimeMillis();  
  32.         System.out.println("time elapse:" +(end1 -start1) );  
  33.           
  34.           
  35.     }  
  36. }  

结果

time elapse:31
time elapse:16
由此不难看出,通过JNI本地的CAS性能远超synchronized关键字

 

 

Java的多线程编程模型5--Java中的CAS理论

 

CAS,compare and swap的缩写,中文翻译成比较并交换。

我们都知道,在java语言之前,并发就已经广泛存在并在服务器领域得到了大量的应用。所以硬件厂商老早就在芯片中加入了大量直至并发操作的原语,从而在硬件层面提升效率。在intel的CPU中,使用cmpxchg指令。

在Java发展初期,java语言是不能够利用硬件提供的这些便利来提升系统的性能的。而随着java不断的发展,Java本地方法(JNI)的出现,使得java程序越过JVM直接调用本地方法提供了一种便捷的方式,因而java在并发的手段上也多了起来。而在Doug Lea提供的cucurenct包中,CAS理论是它实现整个java包的基石。

1. CAS:

CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该 位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前 值。)CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”

通常将 CAS 用于同步的方式是从地址 V 读取值 A,执行多步计算来获得新 值 B,然后使用 CAS 将 V 的值从 A 改为 B。如果 V 处的值尚未同时更改,则 CAS 操作成功。

类似于 CAS 的指令允许算法执行读-修改-写操作,而无需害怕其他线程同时 修改变量,因为如果其他线程修改变量,那么 CAS 会检测它(并失败),算法 可以对该操作重新计算。

2.非阻塞算法

如果每个线程在其他线程任意延迟(或甚至失败)时都将持续进行操作,就可以说该算法是无等待的。与此形成对比的是,无锁定算法要求仅 某个线程 总是执行操作。(无等待的另一种定义是保证每个线程在其有限的步骤中正确计 算自己的操作,而不管其他线程的操作、计时、交叉或速度。

3.用CAS来实现非阻塞算法

[java]   view plain copy
  1. package  zl.study.concurrency;  
  2. /**  
  3.  * AtomicInteger的模拟类,主要是用来测试CAS和非阻塞方式加锁  
  4.  * @author peter  
  5.  *  
  6.  */   
  7. public   class  SimulatedAtomicInteger {  
  8.     private   int  value=  0 ;  
  9.       
  10.     private   int  get(){  
  11.         return   this .value;  
  12.     }  
  13.     /**  
  14.      * 模拟CAS  
  15.      * @param current  
  16.      * @param next  
  17.      * @return  
  18.      */   
  19.     private   synchronized   boolean  compareAndSet( int  current, int  next){  
  20.         return  current == next? true : false ;  
  21.     }  
  22.     /**  
  23.      * 模拟非阻塞算法  
  24.      * @return  
  25.      */   
  26.     public   final   int  incrementAndGet() {  
  27.         for  (;;) {  
  28.             int  current = get();  
  29.             int  next = current +  1 ;  
  30.             if  (compareAndSet(current, next))   
  31.                 return  next;  
  32.         }  
  33.     }  
  34. }  

需要注意的是这个方法中的CAS是在jav代码实现的,这个并没有包含内存位置。在concurrent包中,是JNI的方式,内存位置也作为参数传入这个JNI方法中,在后面碰到了在做详细的介绍

在后面介绍java 5提供的并发工具时,我们还能经常看到类似于SimulatedAtomicInteger得写法,大家可以好好体会!


分享到:
评论

相关推荐

    Java多线程编程总结

    Java线程:线程栈模型与线程的变量 Java线程:线程状态的转换 Java线程:线程的同步与锁 Java线程:线程的交互 Java线程:线程的调度-休眠 Java线程:线程的调度-优先级 Java线程:线程的调度-让步 Java线程...

    java多线程编程总结

    详细的讲述了多线程的各种用法 Java线程:概念与原理 Java线程:创建与启动 Java线程:线程栈模型与线程的变量 Java线程:线程状态的转换 Java线程:线程的同步与锁 Java线程:线程的交互 Java线程:线程的调度-休眠...

    JAVA多线程编程详解-详细操作例子

    多线程是这样一种机制,它允许在程序中并发执行多个指令流,每个指令流都称为一个线程,彼此间互相独立。线程又称为轻量级进程,它和进程一样拥有独立的执行控制,由操作系统负责调度,区别在于线程没有独立的存储...

    java多线程下载课程设计工程源码

    网上找了份资料,是别人完成的Java实现多线程下载的功能。Java多线程的好处挺多的,可以充分利用CPU的资源,简化编程模型,简化异步事件的处理,使GUI更有效率,节约成本...

    java内存模型(有助理解多线程)

    java内存模型,对初学者比较实用,理解后有助于多线程编程等

    Java多线程设计模式(含实例源码)

    Java多线程设计模式,通过对多线程环境的分析,抽象出典型的多线程模型,结合Java编程语言的特性,总结出经典的多线程设计模式,通过本资料的学习,足以让您掌握如何使用JAVA编程技术来解决多线程问题,结合本书实例...

    Java程序设计案例教程-第8章-多线程编程.pptx

    第4页 主要内容 8.1 Java线程模型 8.2 创建线程 8.3 同步与线程间通信 8.4 获取线程状态 8.5 本章小结 8.6 思考和练习 Java程序设计案例教程-第8章-多线程编程全文共36页,当前为第4页。 8.1 Java线程模型 Java对多...

    图解java多线程设计模式

    java.util.concurrent包、synchronized关键字、Swing框架、Java内存模型等内容也均有涉及,不仅能够了解Java多线程的相关知识,还可加深对Java语言的理解。 本书适合以下读者阅读 a.对多线程感兴趣的人 b.对Java...

    java会话管理、多线程.docx

    java多线程的创建,主流的几种创建方式都有详细的讲解。线程的交互以及线程的同步锁的问题都有具体的实例。java的内存模型,java会话都有讲解,如果是刚接触java多线程,可以下载来看看

    《java 并发编程实战高清PDF版》

    深入讲解java并发编程技术,多线程、锁以及java内存模型等

    Java并发编程实践

    虽然当前CPU主频在不断升高,但是X86架构的硬件已经成为瓶颈,这种架构的CPU主频最高为4G,事实上目前3.6G主频的CPU已经接近顶峰,多线程编程模型不仅是目前提高应用性能的手段,更是下一代编程模型的核心思想

    java并发编程理论基础精讲

    本资源为您提供了关于 Java 并发编程理论基础的精讲,涵盖了多线程编程的核心概念、基本原理以及在 Java 中的应用。通过深入学习,您将建立坚实的并发编程基础,能够更好地理解和应对多线程编程中的挑战。 并发编程...

    java学习笔记_多线程网络编程.txt

    java学习笔记_多线程网络编程.txt,包括多线程、网络编程、网络模型、TCP客户端和服务端代码

    Java多线程编程之限制优先级

    限制线程优先级和调度 Java 线程模型涉及可以动态更改的线程优先级。本质上,线程的优先级是从 1 到 10 之间的一个数字,数字越大表明任务越紧急。JVM 标准首先调用优先级较高的线程,然后才调用优先级较低的线程。...

    并发编程基础知识,java内存模型及多线程、volatile

    ● JMM的关键技术点都是围绕着多线程的原⼦性、可⻅性和有序性来创建的。所以,下⾯我们来⼀⼀ 介绍这三种特性。原子性、可见性、有序性。 正常情况下,如果我们不使⽤volatile,那么每条线程都会有⾃⼰的缓存,当...

    多线程并发编程在Netty中的应用分析.zip

    大致内容包括: Java内存模型 Java内存交互协议 Java的线程 Netty的并发编程分析 正确的使用锁 volatile的正确使用 CAS指令和原子类 线程安全类 读写锁的应用

    concurrent 多线程 教材

    00 IBM developerWorks 中国 : Java 多线程与并发编程专题 02 Java 程序中的多线程 03 编写多线程的 Java 应用程序 04 如果我是国王:关于解决 Java编程语言线程问题的建议 (2) 05 构建Java并发模型框架 (2) 06...

Global site tag (gtag.js) - Google Analytics