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.时间锁等候或者无块机构锁