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

前言        

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

volatile

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

synchronized

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

java.util.concurrent.locks.ReentrantLock

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

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

 实际场景

       上面介绍完了几种锁,下面用具体的代码来看看几种锁的实际用法,以及各种表现形式。代码有点长,这里最好自己实验一下,然后看看结果,并思考这个结果。
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);
		}

		
	}
	
}

输出结果:

i>>>>>381890
vi>>>>>353610
ai>>>>>400000
si>>>>>392718
ri>>>>>392658

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

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

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

public class TestSynchronizedThread  implements Runnable{

	private static Integer si = 0;
	
	@Override
	public void run() {
		for(int k=0;k<200000;k++){
			synchronized(si){
				si++;
			}
		}
	}
}


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

   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

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

 

 

 

  • 5
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 16
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值