synchronized,ReetrantLock与volatile(二)

原创
2015/07/15 00:21
阅读数 424

volatile 

     Volatile 变量具有 synchronized 的可见性特性,但是不具备原子特性。这就是说线程能够自动发现 volatile 变量的最新值。在理解volatile作用时候,我们先看看jvm的内存模型。

    

     Java内存模型规定,对于多个线程共享的变量,存储在主内存当中,每个线程都有自己独立的工作内存,线程只能访问自己的工作内存,不可以访问其它线程的工作内存。工作内存中保存了主内存共享变量的副本,线程要操作这些共享变量,只能通过操作工作内存中的副本来实现,操作完毕之后再同步回到主内存当中。

    一个变量被定义为volatile,那说明这个变量是易变的。如果一个线程修改了这个变量,那这个线程将通知其他线程这个变量已做修改,工作内存保持的变量副本时效,必须重新从共享内存读取这个变量的最新值,因此,被volatile非常适合做状态标识。

public class Volatile {

	private volatile static boolean flag = true;

	public static void work() {
		while (flag) {
			System.out.println("=============");
		}

	}

	public static void stop() {
		flag = false;
		System.out.println("----stop work----");

	}

	public static void main(String[] args) {
		new Thread(new Runnable() {
			public void run() {
				work();
			}

		}).start();
		new Thread(new Runnable() {
			public void run() {
				try {
					Thread.sleep(100l);
				} catch (InterruptedException e) {
				}
				stop();
			}

		}).start();

	}
}

      可以看见线程马上结束了自己的工作,但是我们如果不用volatile修饰flag,还是发现线程也马上停止了在控制台的打印。HotSpot编译器在server模式和client模式编译不同, 在client模式下,多线程读取变量时,都会直接从主内存中去读取,不会保存在工作内存中,所有加不加volatile效果一样,而server模式下,则对代码做了优化。而eclipse中默认的启动模式client。


ReetrantLock

     (画了一张不知道是什么图的图),图上我们可以看出,ReetrantLock有有属性Sync sync,FairSync和NonFairSync是Sync的两个子类,Sync继承自AQS(AbstractQueuedSynchronizer)。

   

    很明显,ReetrantLock实现了公平锁和非公平锁。通过源码我们可以看出ReetrantLock默认为非公平锁,并且提供了一个boolean参数的构造函数,true为公平锁,false为非公平锁。

 public ReentrantLock()
    {
        sync = new NonfairSync();
    }

    public ReentrantLock(boolean flag)
    {
        sync = ((Sync) (flag ? ((Sync) (new FairSync())) : ((Sync) (new NonfairSync()))));
    }

   现在我们来看看你ReetrantLock.lock()到底干了什么?

 //FairSync:
 final void lock()
        {
            acquire(1);
        }
   //NonFairSync:
   final void lock()
        {
            if(compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
   //acquire(int i):
   public final void acquire(int i)
    {
        if(!tryAcquire(i) && acquireQueued(addWaiter(Node.EXCLUSIVE), i))
            selfInterrupt();
    }

    非公平锁一开始尝试检查state的值,如果是0就通过CAS看是否能成功置成1,操作如果成功,那么该线程获得锁,而公平锁并没有,这也就是公平和非公平的区别。如果非公平锁对state的CAS没有成功,则和公平锁一样acquire()。

 //FairSync:
 protected final boolean tryAcquire(int i)
        {
            Thread thread = Thread.currentThread();
            int j = getState();
            if(j == 0)
            {
                if(!hasQueuedPredecessors() && compareAndSetState(0, i))
                {
                    setExclusiveOwnerThread(thread);
                    return true;
                }
            } else
            if(thread == getExclusiveOwnerThread())
            {
                int k = j + i;
                if(k < 0)
                {
                    throw new Error("Maximum lock count exceeded");
                } else
                {
                    setState(k);
                    return true;
                }
            }
            return false;
        }
  //NonFairSync:
  final boolean nonfairTryAcquire(int i)
        {
            Thread thread = Thread.currentThread();
            int j = getState();
            if(j == 0)
            {
                if(compareAndSetState(0, i))
                {
                    setExclusiveOwnerThread(thread);
                    return true;
                }
            } else
            if(thread == getExclusiveOwnerThread())
            {
                int k = j + i;
                if(k < 0)
                {
                    throw new Error("Maximum lock count exceeded");
                } else
                {
                    setState(k);
                    return true;
                }
            }
            return false;
        }

     同样在tryAcquire中,公平锁在state=0的情况下看了看sync队列中有没有人排在前面(确实是挺公平的),而非公平锁完全是不管这条件的,我们再来看看acquireQueued(addWaiter(Node.EXCLUSIVE), i),addWaiter是把线程包装成Node,这里就不贴出来了。

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
        final Node p = node.predecessor();
        //如果当前的节点是head说明他是队列中第一个“有效的”节点,因此尝试获取
        if (p == head &&tryAcquire(arg)) {
            setHead(node);
            p.next = null;
            failed = false;
            return interrupted;
        }
        //否则,检查前一个节点的状态为,看当前获取锁失败的线程是否需要挂起。
        //如果需要,借助JUC包下的LockSopport类的静态方法Park挂起当前线程。
        if (shouldParkAfterFailedAcquire(p, node)&&parkAndCheckInterrupt()) 
            interrupted = true;
        }
    } finally {
        //如果有异常
        if (failed)
        // 取消请求,对应到队列操作,就是将当前节点从队列中移除。 
        cancelAcquire(node);
    }
}

       到此为止,一个线程对于锁的一次竞争才告一段落,结果又两种,要么成功获取到锁(不用进入到AQS队列中),要么获取失败被挂起,等待下次唤醒后继续循环尝试获取锁,值得注意的是,AQS的队列为FIFO队列,所以,每次及时被CPU假唤醒,且当前线程不是出在头节点的位置,也是会被挂起的。(为了防止篇幅过长,AQS中的condition和node将在下篇讲)。


       那么问题来了,sychronized和ReentranLock各自要在什么场合用呢?下面贴一下各大博客的总结吧

       synchronized是托管给JVM执行的,而lock是java写的控制锁的代码。在Java1.5中,synchronize是性能低效的。因为这是一个重量级操作,需要调用操作接口,导致有可能加锁消耗的系统时间比加锁以外的操作还多。相比之下使用Java提供的Lock对象,性能更高一些。但是到了Java1.6,发生了变化。synchronize在语义上很清晰,可以进行很多优化,有适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在Java1.6上synchronize的性能并不比Lock差。官方也表示,他们也更支持synchronize,在未来的版本中还有优化余地。

       synchronized原语和ReentrantLock在一般情况下没有什么区别,但是在非常复杂的同步应用中,请考虑使用ReentrantLock,特别是遇到下面2种需求的时候。
     1.某个线程在等待一个锁的控制权时需要中断
     2.多个条件变量或者锁投票
     3.时间锁等候或者无块机构锁

展开阅读全文
打赏
0
2 收藏
分享
加载中
陈二C博主

引用来自“弹一曲Happy颂”的评论

我就想问:ReentranLock能不能像synchronized一样保证可见性,注意不是它自己那个state变量的可见性,而是获得锁之后,后面代码块里的任意变量的可见性。谢谢!
不好意思最近太忙,很少上博客了。那肯定是行的,因为ReentranLock tryLock并没有开启新线程去做CAS操作,也不可能开启新线程去竞争锁,所以不存在不同线程的变量,所以肯定是可见的
2016/07/26 18:17
回复
举报
我就想问:ReentranLock能不能像synchronized一样保证可见性,注意不是它自己那个state变量的可见性,而是获得锁之后,后面代码块里的任意变量的可见性。谢谢!
2016/07/03 15:44
回复
举报
更多评论
打赏
2 评论
2 收藏
0
分享
返回顶部
顶部