`

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多线程编程是Java开发中的重要组成部分,它允许程序同时执行多个任务,提升系统效率。在本教程中,我们将深入探讨Java中的多线程设计模式、并发核心编程概念以及线程池的工作原理和种类。 首先,让我们了解什么...

    Java多线程编程经验

    ### Java多线程编程经验 #### 一、Java线程:概念与原理 现代操作系统都是多任务操作系统,其中多线程是一种重要的实现多任务的方式。线程是进程内的一个执行单位,一个进程可以包含多个线程。例如,在Java应用...

    java多线程编程-详细炒作例子

    ### Java多线程编程详解与实战案例 #### 理解多线程概念与Java内存模型 多线程,作为现代编程中的一项关键技术,允许在单一应用程序中并发执行多个指令流,每个这样的指令流被称为一个线程。在Java中,线程被视为...

    Java多线程编程深入详解.docx

    Java多线程编程深入详解 多线程编程是Java编程语言中的一种重要技术,用于提高程序的执行效率和响应速度。在本文中,我们将深入探讨Java多线程编程的基础知识和高级技术。 什么是多进程和多线程? 在计算机科学中...

    Java多线程练习题

    Java多线程是Java编程中的核心概念,它允许程序同时执行多个任务,提高了系统的效率和响应性。在Java中,多线程的实现主要通过两种方式:继承Thread类和实现Runnable接口。理解并掌握多线程的使用对于任何Java开发者...

    java多线程编程实例_Source

    Java多线程编程是Java开发中的重要组成部分,它允许程序同时执行多个任务,提升系统效率。在本实例源码中,包含17个章节和上百个实例,旨在深入讲解Java多线程的核心概念和实际应用。 一、线程基础知识 在Java中,...

    JAVA多线程编程详解-详细操作例子(转自CSDN)

    Java多线程编程是Java开发中的重要组成部分,它允许程序同时执行多个任务,提升程序的效率和响应性。本文将详细解析Java中实现多线程的两种主要方式,并讨论线程的基本概念和内存模型。 首先,理解多线程的概念至关...

    Java多线程编程总结

    ### Java多线程编程总结 #### 一、Java线程:概念与原理 1. **操作系统中线程和进程的概念** - 当前的操作系统通常为多任务操作系统,多线程是实现多任务的一种手段。 - **进程**:指内存中运行的应用程序,每个...

    Java多线程编程环境中单例模式的实现

    ### Java多线程编程环境中单例模式的实现 #### 概述 单例模式是一种常用的软件设计模式,它确保一个类只有一个实例,并提供一个全局访问点。在Java中,单例模式的应用非常广泛,特别是在资源管理、日志记录、...

    Java_多线程与并发编程总结.doc

    Java中的多线程编程则基于这种操作系统级别的并发模型。 Java虚拟机(JVM)为每一个Java应用程序启动一个进程,而在这个进程中,所有的代码执行都是通过线程来完成的。默认情况下,Java程序的main方法在一个称为...

    JAVA多线程模型详解

    在多线程编程领域,Java作为一门广泛使用的编程语言,其内置的多线程模型一直是学习和应用的重点。本文将深入探讨Java多线程模型的相关知识点,包括线程与进程的区别、线程的实现原理、线程的创建方法以及线程的阻塞...

    Java多线程编程

    ### Java多线程编程知识点详解 #### 一、线程基础概述 - **定义与特点:** - **线程**是一种比进程更细粒度的执行单元,它允许在一个进程中并发执行多个任务。 - **轻量级进程**:线程有时被称为轻量级进程,...

    java多线程编程总结

    ### Java多线程编程总结 #### 一、Java线程:概念与原理 - **操作系统中线程和进程的概念** 当前的操作系统通常都是多任务操作系统,多线程是一种实现多任务的方式之一。在操作系统层面,进程指的是内存中运行的...

    java多线程编程大总结

    Java多线程编程是一个深奥且复杂的话题,它涉及到内存模型、线程调度、同步机制等多个层面的知识。Java通过提供丰富的API和并发工具库,使得开发者能够有效地利用多线程技术来构建高效的并发应用程序。对于Java...

    2022年Java多线程编程精要之基础Java教程.docx

    Java 多线程编程基础知识 Java 多线程编程精要之基础 Java 教程是 Java 程序中运用多线程的基本教程,旨在...Java 多线程编程的优点包括简洁的编程模型、高效的计算资源利用率、简单的线程间通讯和等待问题的解决。

Global site tag (gtag.js) - Google Analytics