Java多线程并发锁和原子操作,你真的了解吗?

来源:互联网 发布:手机淘宝安全中心官网 编辑:程序博客网 时间:2024/05/20 09:07

目录(?)[-]

  1. 前言        
  2. volatile
  3. synchronized
  4. javautilconcurrentlocksReentrantLock
  5.  实际场景

前言        

        对于Java多线程,接触最多的莫过于使用synchronized,这个简单易懂,但是这synchronized并非性能最优的。今天我就简单介绍一下几种锁。可能我下面讲的时候其实很多东西不会特别深刻,最好的方式是自己做实验,把各种场景在代码中实验一下,这样发发现很多细节。

volatile

        作为Java中的轻量级锁,当多线程中一个线程操作后可以保证其他线程可见,也就是书上所说的“可见性”,另外一个就是“重排序”。所谓重排序指的是JVM对指令的优化。很多人可能在实际实验中发现好像不是如此,最后的例子我也会说明这一点。

synchronized

        这个作为Java中“重量级”的线程安全机制被大家所熟知,这个就不在这里做解释了。

java.util.concurrent.locks.ReentrantLock

        java.util.concurrent.中是JDK1.5中出的对于一些并发操作的类库,其中包括很多同学很喜欢的原子类,比如说AtomicInteger。它里面原理就是一个CAS,这里就不做过多的阐述,有兴趣的可以看看源码。

       好,说一下ReentrantLock,这个锁主要是能显示的添加锁和释放锁,好处是更加灵活,能够更加准确的控制锁,也能确保系统的稳定,比如说“重连”。后面代码会有使用到。 

 实际场景

       上面介绍完了几种锁,下面用具体的代码来看看几种锁的实际用法,以及各种表现形式。代码有点长,这里最好自己实验一下,然后看看结果,并思考这个结果。
[java] view plain copy
  1. <span style="font-size:14px">package com.bynow.m07.thread;  
  2.   
  3. import java.util.concurrent.ExecutorService;  
  4. import java.util.concurrent.Executors;  
  5. import java.util.concurrent.atomic.AtomicInteger;  
  6. import java.util.concurrent.locks.Lock;  
  7. import java.util.concurrent.locks.ReentrantLock;  
  8.   
  9. /** 
  10.  *  
  11.  * @author 百恼| 2012-07-26 
  12.  * 
  13.  */  
  14. public class TestMultiThread  implements Runnable{  
  15.   
  16.     private static int i;  
  17.       
  18.     private static volatile Integer vi = 0;  
  19.       
  20.     private static AtomicInteger ai = new AtomicInteger();  
  21.       
  22.     private static Integer si = 0;  
  23.       
  24.     private static int ri;  
  25.       
  26.     private static AtomicInteger flag = new AtomicInteger();  
  27.       
  28.     private Lock lock = new ReentrantLock();  
  29.       
  30.     @Override  
  31.     public void run() {  
  32.         for(int k=0;k<200000;k++){  
  33.             i++;  
  34.             vi++;  
  35.             ai.incrementAndGet();  
  36.             synchronized(si){  
  37.                 si++;  
  38.             }  
  39.             lock.lock();  
  40.             try{  
  41.                 ri++;  
  42.             }finally{  
  43.                 lock.unlock();  
  44.             }  
  45.               
  46.         }  
  47.         flag.incrementAndGet();  
  48.     }  
  49.       
  50.     public static void main(String[] args) throws InterruptedException{  
  51.         TestMultiThread t1 = new TestMultiThread();  
  52.         TestMultiThread t2 = new TestMultiThread();  
  53.         ExecutorService exec1 = Executors.newCachedThreadPool();  
  54.         ExecutorService exec2 = Executors.newCachedThreadPool();  
  55.         exec1.execute(t1);  
  56.         exec2.execute(t2);  
  57.         while(true){  
  58.             if(flag.intValue()==2){  
  59.                 System.out.println("i>>>>>"+i);  
  60.                 System.out.println("vi>>>>>"+vi);  
  61.                 System.out.println("ai>>>>>"+ai);  
  62.                 System.out.println("si>>>>>"+si);      
  63.                 System.out.println("ri>>>>>"+ri);      
  64.                 break;  
  65.             }  
  66.             Thread.sleep(50);  
  67.         }  
  68.   
  69.           
  70.     }  
  71.       
  72. }  
  73. </span>  

输出结果:

[java] view plain copy
  1. <span style="font-size:14px">i>>>>>381890  
  2. vi>>>>>353610  
  3. ai>>>>>400000  
  4. si>>>>>392718  
  5. ri>>>>>392658</span>  

       从上面的输出结果来看真是让人大感意外:只有原子操作AtomicInteger的结果保证了多线程的安全性,而其他不管是用轻量级的volatile还是重量级的synchronized都没有达到我们想要的效果。这也让我产生了大在的怀疑。难道问题真的这么蹊跷?

       从这里不难看出除了AtomicInteger用的是其自己的方法而其他都是用到了Java的语法糖++操作。而这让我想起了++操作并非原子操作,而可能在其中间操作导致了其他线程对其他进行了修改,虽然同样的问题我在《Think in Java》中也找到可以佐证的例子。这里有一个问题就是synchronized:因为我对si已经加了synchronized操作,但是输出的结果令人意外,难道还会有问题?这让我想把这段代码编译成字节码的冲动。好吧,下面看字节码(这里我单独把synchronized这一段操作抽出来,作为分析,其他几个就算了,不然编译后的字节码有点多)

      为了方便看,先贴出源代码

[java] view plain copy
  1. <span style="font-size:14px">public class TestSynchronizedThread  implements Runnable{  
  2.   
  3.     private static Integer si = 0;  
  4.       
  5.     @Override  
  6.     public void run() {  
  7.         for(int k=0;k<200000;k++){  
  8.             synchronized(si){  
  9.                 si++;  
  10.             }  
  11.         }  
  12.     }  
  13. }</span>  


下面是字节码,为了节省篇幅,一些不重要的部分我将不贴出

[plain] view plain copy
  1. <span style="font-size:14px">   0:   iconst_0  
  2.    1:   istore_1  
  3.    2:   iload_1  
  4.    3:   ldc     #2; //int 200000  
  5.    5:   if_icmpge       55  
  6.    8:   getstatic       #3; //Field si:Ljava/lang/Integer;  
  7.    11:  dup  
  8.    12:  astore_2  
  9.    13:  monitorenter  
  10.    14:  getstatic       #3; //Field si:Ljava/lang/Integer;  
  11.    17:  astore_3  
  12.    18:  getstatic       #3; //Field si:Ljava/lang/Integer;  
  13.    21:  invokevirtual   #4; //Method java/lang/Integer.intValue:()I  
  14.    24:  iconst_1  
  15.    25:  iadd  
  16.    26:  invokestatic    #5; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;  
  17.    29:  dup  
  18.    30:  putstatic       #3; //Field si:Ljava/lang/Integer;  
  19.    33:  astore  4  
  20.    35:  aload_3  
  21.    36:  pop  
  22.    37:  aload_2  
  23.    38:  monitorexit  
  24.    39:  goto    49  
  25.    42:  astore  5  
  26.    44:  aload_2  
  27.    45:  monitorexit  
  28.    46:  aload   5  
  29.    48:  athrow  
  30.    49:  iinc    1, 1  
  31.    52:  goto    2  
  32.    55:  return</span>  

        从这里一看从monitorenter进入安全区到monitorexit出安全区没有发现si是处于中间状态的,那又是在哪出的问题呢?这里简单说一下,归根结底仍然是(++)操作非原子操作,可是很多人疑惑了,这里不是加锁了吗?废话不多说,在我的深入探析Java线程锁机制有一个比较详细的分析。

0 0
原创粉丝点击