前言
对于Java多线程,接触最多的莫过于使用synchronized,这个简单易懂,但是这synchronized并非性能最优的。今天我就简单介绍一下几种锁。可能我下面讲的时候其实很多东西不会特别深刻,最好的方式是自己做实验,把各种场景在代码中实验一下,这样发发现很多细节。
volatile
synchronized
这个作为Java中“重量级”的线程安全机制被大家所熟知,这个就不在这里做解释了。
java.util.concurrent.locks.ReentrantLock
java.util.concurrent.中是JDK1.5中出的对于一些并发操作的类库,其中包括很多同学很喜欢的原子类,比如说AtomicInteger。它里面原理就是一个CAS,这里就不做过多的阐述,有兴趣的可以看看源码。
好,说一下ReentrantLock,这个锁主要是能显示的添加锁和释放锁,好处是更加灵活,能够更加准确的控制锁,也能确保系统的稳定,比如说“重连”。后面代码会有使用到。
实际场景
- <span style="font-size:14px">package com.bynow.m07.thread;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- import java.util.concurrent.atomic.AtomicInteger;
- import java.util.concurrent.locks.Lock;
- import java.util.concurrent.locks.ReentrantLock;
- /**
- *
- * @author 百恼| 2012-07-26
- *
- */
- public class TestMultiThread implements Runnable{
- private static int i;
- private static volatile Integer vi = 0;
- private static AtomicInteger ai = new AtomicInteger();
- private static Integer si = 0;
- private static int ri;
- private static AtomicInteger flag = new AtomicInteger();
- private Lock lock = new ReentrantLock();
- @Override
- public void run() {
- for(int k=0;k<200000;k++){
- i++;
- vi++;
- ai.incrementAndGet();
- synchronized(si){
- si++;
- }
- lock.lock();
- try{
- ri++;
- }finally{
- lock.unlock();
- }
- }
- flag.incrementAndGet();
- }
- public static void main(String[] args) throws InterruptedException{
- TestMultiThread t1 = new TestMultiThread();
- TestMultiThread t2 = new TestMultiThread();
- ExecutorService exec1 = Executors.newCachedThreadPool();
- ExecutorService exec2 = Executors.newCachedThreadPool();
- exec1.execute(t1);
- exec2.execute(t2);
- while(true){
- if(flag.intValue()==2){
- System.out.println("i>>>>>"+i);
- System.out.println("vi>>>>>"+vi);
- System.out.println("ai>>>>>"+ai);
- System.out.println("si>>>>>"+si);
- System.out.println("ri>>>>>"+ri);
- break;
- }
- Thread.sleep(50);
- }
- }
- }
- </span>
输出结果:
- <span style="font-size:14px">i>>>>>381890
- vi>>>>>353610
- ai>>>>>400000
- si>>>>>392718
- ri>>>>>392658</span>
从上面的输出结果来看真是让人大感意外:只有原子操作AtomicInteger的结果保证了多线程的安全性,而其他不管是用轻量级的volatile还是重量级的synchronized都没有达到我们想要的效果。这也让我产生了大在的怀疑。难道问题真的这么蹊跷?
从这里不难看出除了AtomicInteger用的是其自己的方法而其他都是用到了Java的语法糖++操作。而这让我想起了++操作并非原子操作,而可能在其中间操作导致了其他线程对其他进行了修改,虽然同样的问题我在《Think in Java》中也找到可以佐证的例子。这里有一个问题就是synchronized:因为我对si已经加了synchronized操作,但是输出的结果令人意外,难道还会有问题?这让我想把这段代码编译成字节码的冲动。好吧,下面看字节码(这里我单独把synchronized这一段操作抽出来,作为分析,其他几个就算了,不然编译后的字节码有点多)
为了方便看,先贴出源代码
- <span style="font-size:14px">public class TestSynchronizedThread implements Runnable{
- private static Integer si = 0;
- @Override
- public void run() {
- for(int k=0;k<200000;k++){
- synchronized(si){
- si++;
- }
- }
- }
- }</span>
下面是字节码,为了节省篇幅,一些不重要的部分我将不贴出
- <span style="font-size:14px"> 0: iconst_0
- 1: istore_1
- 2: iload_1
- 3: ldc #2; //int 200000
- 5: if_icmpge 55
- 8: getstatic #3; //Field si:Ljava/lang/Integer;
- 11: dup
- 12: astore_2
- 13: monitorenter
- 14: getstatic #3; //Field si:Ljava/lang/Integer;
- 17: astore_3
- 18: getstatic #3; //Field si:Ljava/lang/Integer;
- 21: invokevirtual #4; //Method java/lang/Integer.intValue:()I
- 24: iconst_1
- 25: iadd
- 26: invokestatic #5; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
- 29: dup
- 30: putstatic #3; //Field si:Ljava/lang/Integer;
- 33: astore 4
- 35: aload_3
- 36: pop
- 37: aload_2
- 38: monitorexit
- 39: goto 49
- 42: astore 5
- 44: aload_2
- 45: monitorexit
- 46: aload 5
- 48: athrow
- 49: iinc 1, 1
- 52: goto 2
- 55: return</span>
从这里一看从monitorenter进入安全区到monitorexit出安全区没有发现si是处于中间状态的,那又是在哪出的问题呢?这里简单说一下,归根结底仍然是(++)操作非原子操作,可是很多人疑惑了,这里不是加锁了吗?废话不多说,在我的深入探析Java线程锁机制有一个比较详细的分析
转载:http://blog.youkuaiyun.com/luohuacanyue/article/details/7796352