??xml version="1.0" encoding="utf-8" standalone="yes"?>日本亚洲色大成网站www久久,亚洲色大成网站www永久,亚洲国产精品乱码一区二区http://m.tkk7.com/DLevin/category/54887.htmlIn general the OO style is to use a lot of little objects with a lot of little methods that give us a lot of plug points for overriding and variation. To do is to be -Nietzsche, To bei is to do -Kant, Do be do be do -Sinatrazh-cnFri, 04 Sep 2015 22:19:15 GMTFri, 04 Sep 2015 22:19:15 GMT60实现自己的Lock对象http://m.tkk7.com/DLevin/archive/2015/08/11/426723.htmlDLevinDLevinMon, 10 Aug 2015 22:08:00 GMThttp://m.tkk7.com/DLevin/archive/2015/08/11/426723.htmlhttp://m.tkk7.com/DLevin/comments/426723.htmlhttp://m.tkk7.com/DLevin/archive/2015/08/11/426723.html#Feedback0http://m.tkk7.com/DLevin/comments/commentRss/426723.htmlhttp://m.tkk7.com/DLevin/services/trackbacks/426723.html一直想好好学习(fn)concurrent包中的各个类的实玎ͼ然而经常看?jin)一点就因ؓ(f)其他事情q扰而放下了(jin)。发现这样太不利于自q成长?jin),因而最q打潜?j)一件一件的完成自己惛_?fn)的东西?br />
对concurrent包的学习(fn)打算先从Lock的实现开始,因而自然而然的就端v?jin)AbstractQueuedSynchronizerQ然而要Lq个cȝ源码q不是那么容易,因而我开始问自己一个问题:(x)如果自己要去实现q个一个Lock对象Q应该如何实现呢Q?br />
要实现Lock对象Q首先理解什么是锁?我自׃~程角度单的理解Q所谓锁对象Q互斥锁Q就是它能保证一ơ只有一个线E能q入它保护的临界区,如果有一个线E已l拿到锁对象Q那么其他对象必让权等待,而在该线E退?gu)个?f)界区旉要唤醒等待列表中的其他线E。更学术一些,《计机操作pȝ?/a>中对同步机制准则的归UIP50Q:(x)

  1. I闲让进。当无进E处于(f)界区Ӟ表明临界资源处于I闲状态,应允怸个请求进入(f)界区的进E立卌入自q临界区,以有效的利用临界资源?/li>
  2. 忙则{待。当已有q程q入临界区时Q表明(f)界资源正在被讉KQ因而其他试图进入(f)界区的进E必ȝ待,以保证对临界源的互斥讉K?/li>
  3. 有限{待。对要求讉K临界资源的进E,应保证在有限旉内能q入自己的(f)界区Q以免陷?#8220;ȝ”状态?/li>
  4. 让权{待。当q程不能q入自己的(f)界区Ӟ应该释放处理机,以免q程陷入“忙等”状态?/li>

说了(jin)那么多,其实对互斥锁很简单,只需要一个标CQ如果该标记位ؓ(f)0Q表C没有被占用Q因而直接获得锁Q然后把该标C|ؓ(f)1Q此时其他线E发现该标记位已l是1Q因而需要等待。这里对q个标记位的比较q设值必L原子操作Q而在JDK5以后提供的atomic包里的工L(fng)可以很方便的提供q个原子操作。然而上面的四个准则应该漏了(jin)一点,即释N的线E(q程Q和得到锁的U程Q进E)(j)应该是同一个,像一把钥匙对应一把锁Q理想的Q,所以一个非常简单的Lockcd以这么实玎ͼ(x)

public class SpinLockV1 {
    
private final AtomicInteger state = new AtomicInteger(0);
    
private volatile Thread owner; // q里owner字段可能存在中间|不可靠,因而其他线E不可以依赖q个字段的?/span>
    
    
public void lock() {
        
while (!state.compareAndSet(01)) { }
        owner 
= Thread.currentThread();
    }
    
    
public void unlock() {
        Thread currentThread 
= Thread.currentThread();
        
if (owner != currentThread || !state.compareAndSet(10)) {
            
throw new IllegalStateException("The lock is not owned by thread: " + currentThread);
        }
        owner 
= null;
    }
}

一个简单的试Ҏ(gu)Q?br />

    @Test
    
public void testLockCorrectly() throws InterruptedException {
        
final int COUNT = 100;
        Thread[] threads 
= new Thread[COUNT];
        SpinLockV1 lock 
= new SpinLockV1();
        AddRunner runner 
= new AddRunner(lock);
        
for (int i = 0; i < COUNT; i++) { 
            threads[i] 
= new Thread(runner, "thread-" + i);
            threads[i].start();
        }
        
        
for (int i = 0; i < COUNT; i++) {
            threads[i].join();
        }
        
        assertEquals(COUNT, runner.getState());
    }
    
    
private static class AddRunner implements Runnable {
        
private final SpinLockV1 lock;
        
private int state = 0;

        
public AddRunner(SpinLockV1 lock) {
            
this.lock = lock;
        }
        
        
public void run() {
            lock.lock();
            
try {
                quietSleep(
10);
                state
++;
                System.out.println(Thread.currentThread().getName() 
+ "" + state);
            } 
finally {
                lock.unlock();
            }
        }
        
        
public int getState() {
            
return state;
        }
    }

然而这个SpinLock其实q不需要stateq个字段Q因为owner的赋g否也是一U状态,因而可以用它作ZU互斥状态:(x)

public class SpinLockV2 {
    
private final AtomicReference<Thread> owner = new AtomicReference<Thread>(null);
    
    
public void lock() {
        
final Thread currentThread = Thread.currentThread();
        
while (!owner.compareAndSet(null, currentThread)) { }
    }
    
    
public void unlock() {
        Thread currentThread 
= Thread.currentThread();
        
if (!owner.compareAndSet(currentThread, null)) {
            
throw new IllegalStateException("The lock is not owned by thread: " + currentThread);
        }
    }
}

q在操作pȝ中被定义为整形信号量Q然而整形信号量如果没拿到锁?x)一直处?#8220;忙等”状态(没有遵@有限{待和让权等待的准则Q,因而这U锁也叫Spin LockQ在短暂的等待中它可以提升性能Q因为可以减线E的切换Qconcurrent包中的Atomic大部分都采用q种机制实现Q然而如果需要长旉的等待,“忙等”?x)占用不必要的CPU旉Q从而性能?x)变的很差,q个时候就需要将没有拿到锁的U程攑ֈ{待列表中,q种方式在操作系l中也叫记录型信号量Q它遵@?jin)让权等待准则(当前没有实现有限{待准则Q。在JDK6以后提供?jin)LockSupport.park()/LockSupport.unpark()操作Q可以将当前U程攑օ一个等待列表或一个线E从q个{待列表中唤醒。然而这个park/unpark的等待列表是一个全局的等待列表,在unpartk的时候还是需要提供需要唤醒的Thread对象Q因而我们需要维护自q{待列表Q但是如果我们可以用JDK提供的工L(fng)ConcurrentLinkedQueueQ就非常Ҏ(gu)实现Q如LockSupport文档中给出来?a >代码事例Q?br />

class FIFOMutex {
   
private final AtomicBoolean locked = new AtomicBoolean(false);
   
private final Queue<Thread> waiters = new ConcurrentLinkedQueue<Thread>();

   
public void lock() {
     
boolean wasInterrupted = false;
     Thread current 
= Thread.currentThread();
     waiters.add(current);

     
// Block while not first in queue or cannot acquire lock
     while (waiters.peek() != current || !locked.compareAndSet(falsetrue)) {
        LockSupport.park(
this);
        
if (Thread.interrupted()) // ignore interrupts while waiting
          wasInterrupted = true;
     }

     waiters.remove();
     
if (wasInterrupted)          // reassert interrupt status on exit
        current.interrupt();
   }

   
public void unlock() {
     locked.set(
false);
     LockSupport.unpark(waiters.peek());
   }
 }

在该代码事例中,有一个线E等待队列和锁标记字D,每次调用lock时先当前线E放入这个等待队列中Q然后拿出队列头U程对象Q如果该U程对象正好是当前线E,q且成功 使用CAS方式讄locked字段Q这里需要两个同时满I因ؓ(f)可能出现一个线E已l从队列中移除了(jin)但还没有unlockQ此时另一个线E调用lockҎ(gu)Q此旉列头的线E就是第二个U程Q然而由于第一个线E还没有unlock或者正在unlockQ因而需要用CAS原子操作来判断是否要parkQ,表示该线E竞争成功,获得锁,否则当前线EparkQ这里之所以要攑֜ while循环中,因ؓ(f)park操作可能无理p?spuriously)Q如文档中给出的描述Q?br />

LockSupport.park()
public static void park(Object blocker)
Disables the current thread for thread scheduling purposes unless the permit is available.

If the permit is available then it is consumed and the call returns immediately; otherwise the current thread becomes disabled for thread scheduling purposes and lies dormant until one of three things happens:

  • Some other thread invokes unpark with the current thread as the target; or
  • Some other thread interrupts the current thread; or
  • The call spuriously (that is, for no reason) returns.

This method does not report which of these caused the method to return. Callers should re-check the conditions which caused the thread to park in the first place. Callers may also determine, for example, the interrupt status of the thread upon return.

Parameters:
blocker - the synchronization object responsible for this thread parking
Since:
1.6
我在实现自己的类时就被这?#8220;无理p?#8221;坑了(jin)好久。对于已l获得锁的线E,该U程从等待队列中U除Q这里由于ConcurrentLinkedQueue是线E安全的Q因而能保证每次都是队列头的U程得到锁,因而在得到锁匙队列头U除。unlock逻辑比较单,只需要将locked字段打开Q设|ؓ(f)falseQ,唤醒QunparkQ队列头的线E即可,然后该线E会(x)l箋在lockҎ(gu)的while循环中l竞争unlocked字段Qƈ它自己从线E队列中U除表示获得锁成功。当然安全v见,最好在unlock中加入一些验证逻辑Q如解锁的线E和加锁的线E需要相同?br />
然而本文的目的是自己实C个Lock对象Q即只用一些基本的操作Q而不使用JDK提供的AtomiccdConcurrentLinkedQueue。类似的首先我们也需要一个队列存攄待线E队列(公^赯Q用先q先出队列)(j)Q因而先定义一个Node对象用以构成q个队列Q?br />

 

    protected static class Node {
        
volatile Thread owner;
        
volatile Node prev;
        
volatile Node next;
        
        
public Node(Thread owner) {
            
this.owner = owner;
            
this.state = INIT;
        }
        
        
public Node() {
            
this(Thread.currentThread());
        }
    }

单v见,队列头是一个v点的placeholderQ每个调用lock的线E都先将自己竞争攑օq个队列,每个队列头后一个线E(NodeQ即是获得锁的线E,所以我们需要有head Node字段用以快速获取队列头的后一个NodeQ而tail Node字段用来快速插入新的NodeQ所以关键在于如何线E安全的构徏q个队列Q方法还是一L(fng)Q用CAS操作Q即CASҎ(gu)自p|成tail|然后重新构徏q个列表Q?br />

    protected boolean enqueue(Node node) {
        
while (true) {
            
final Node preTail = tail;
            node.prev 
= preTail;
            
if (compareAndSetTail(preTail, node)) {
                preTail.next 
= node;
                
return node.prev == head;
            }
        }
    }

在当前线ENode以线E安全的方式攑օq个队列后,lock实现相对比较简单了(jin)Q如果当前Node是的前驱是headQ该U程获得锁,否则park当前U程Q处理park无理p回的问题Q因而将park攑օwhile循环中(该实现是一个不可重入的实现Q:(x)

    public void lock() {
        
// Put the latest node to a queue first, then check if the it is the first node
        
// this way, the list is the only shared resource to deal with
        Node node = new Node();
        
if (enqueue(node)) {
            current 
= node.owner;
        } 
else {
            
while (node.prev != head) {
                LockSupport.park(
this); // This may return "spuriously"!!, so put it to while
            }

            current 
= node.owner;
        }
    }

unlock的实现需要考虑多种情况Q如果当前Node(head.next)有后驱,那么直接unpark该后驱即可;如果没有Q表C当前已l没有其他线E在{待队列中,然而在q个判断q程中可能会(x)有其他线E进入,因而需要用CAS的方式设|tailQ如果设|失败,表示此时有其他线E进入,因而需要将该新q入的线Eunpark从而该新进入的U程在调用park后可以立卌回(q里的CAS和enqueue的CAS都是对tail操作Q因而能保证状态一_(d)(j)Q?br />

    public void unlock() {
        Node curNode 
= unlockValidate();
        Node next 
= curNode.next;
        
if (next != null) {
           
head.next = next;
            next.prev 
= head;
            LockSupport.unpark(next.owner);
        } 
else {
            
if (!compareAndSetTail(curNode, head)) {
               
while (curNode.next == null) { } // Wait until the next available
                // Another node queued during the time, so we have to unlock that, or else, this node can never unparked
                unlock();
            } 
else {
               
compareAndSetNext(head, curNode, null); // Still use CAS here as the head.next may already been changed
            }
        }
    }

具体的代码和试cd以参考查?a >q里?br />


其实直到自己写完q个cd才直到者其实这是一个MCS锁的变种Q因而这个实现每个线Epark在自w对应的node上,而由前一个线Eunpark它;而AbstractQueuedSynchronizer是CLH锁,因ؓ(f)它的park由前q态决定,虽然它也是由前一个线Eunpark它。具体可以参?a >q里?/p>

DLevin 2015-08-11 06:08 发表评论
]]>
[转]自旋锁、排队自旋锁、MCS锁、CLH?/title><link>http://m.tkk7.com/DLevin/archive/2015/08/07/416102.html</link><dc:creator>DLevin</dc:creator><author>DLevin</author><pubDate>Thu, 06 Aug 2015 16:18:00 GMT</pubDate><guid>http://m.tkk7.com/DLevin/archive/2015/08/07/416102.html</guid><wfw:comment>http://m.tkk7.com/DLevin/comments/416102.html</wfw:comment><comments>http://m.tkk7.com/DLevin/archive/2015/08/07/416102.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://m.tkk7.com/DLevin/comments/commentRss/416102.html</wfw:commentRss><trackback:ping>http://m.tkk7.com/DLevin/services/trackbacks/416102.html</trackback:ping><description><![CDATA[转自Qhttp://coderbee.net/index.php/concurrent/20131115/577<br /><br /><h3>自旋锁(Spin lockQ?/h3><p style="margin: 0px 0px 1.714285714rem; padding: 0px; border: 0px; vertical-align: baseline; line-height: 24px; color: #444444; font-family: 'Open Sans', Helvetica, Arial, sans-serif; background-color: #ffffff;">自旋锁是指当一个线E尝试获取某个锁Ӟ如果该锁已被其他U程占用Q就一直@环检锁是否被释放,而不是进入线E挂h睡眠状态?/p><p style="margin: 0px 0px 1.714285714rem; padding: 0px; border: 0px; vertical-align: baseline; line-height: 24px; color: #444444; font-family: 'Open Sans', Helvetica, Arial, sans-serif; background-color: #ffffff;">自旋锁适用于锁保护的(f)界区很小的情况,临界区很的话,锁占用的旉很短?/p><h4>单的实现</h4><div style="background-color:#eeeeee;font-size:13px;border:1px solid #CCCCCC;padding-right: 5px;padding-bottom: 4px;padding-left: 4px;padding-top: 4px;width: 98%;word-break:break-all"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><span style="color: #0000FF; ">import</span><span style="color: #000000; "> java.util.concurrent.atomic.AtomicReference;<br /><br /></span><span style="color: #0000FF; ">public</span><span style="color: #000000; "> </span><span style="color: #0000FF; ">class</span><span style="color: #000000; "> SpinLock {<br />    </span><span style="color: #0000FF; ">private</span><span style="color: #000000; "> AtomicReference</span><span style="color: #000000; "><</span><span style="color: #000000; ">Thread</span><span style="color: #000000; ">></span><span style="color: #000000; "> owner </span><span style="color: #000000; ">=</span><span style="color: #000000; "> </span><span style="color: #0000FF; ">new</span><span style="color: #000000; "> AtomicReference</span><span style="color: #000000; "><</span><span style="color: #000000; ">Thread</span><span style="color: #000000; ">></span><span style="color: #000000; ">();<br />    <br />    </span><span style="color: #0000FF; ">public</span><span style="color: #000000; "> </span><span style="color: #0000FF; ">void</span><span style="color: #000000; "> lock() {<br />        Thread currentThread </span><span style="color: #000000; ">=</span><span style="color: #000000; "> Thread.currentThread();</span><span style="color: #008000; ">//</span><span style="color: #008000; "> 如果锁未被占用,则设|当前线Eؓ(f)锁的拥有?/span><span style="color: #008000; "><br /></span><span style="color: #000000; ">        </span><span style="color: #0000FF; ">while</span><span style="color: #000000; "> (owner.compareAndSet(</span><span style="color: #0000FF; ">null</span><span style="color: #000000; ">, currentThread)) { }<br />    }<br />    <br />    </span><span style="color: #0000FF; ">public</span><span style="color: #000000; "> </span><span style="color: #0000FF; ">void</span><span style="color: #000000; "> unlock() {<br />        Thread currentThread </span><span style="color: #000000; ">=</span><span style="color: #000000; "> Thread.currentThread();</span><span style="color: #008000; ">//</span><span style="color: #008000; "> 只有锁的拥有者才能释N</span><span style="color: #008000; "><br /></span><span style="color: #000000; ">        owner.compareAndSet(currentThread, </span><span style="color: #0000FF; ">null</span><span style="color: #000000; ">);<br />    }<br />}</span></div><p style="margin: 0px 0px 1.714285714rem; padding: 0px; border: 0px; vertical-align: baseline; line-height: 24px; color: #444444; font-family: 'Open Sans', Helvetica, Arial, sans-serif; background-color: #ffffff;"><br />SimpleSpinLock里有一个owner属性持有锁当前拥有者的U程的引用,如果该引用ؓ(f)nullQ则表示锁未被占用,不ؓ(f)null则被占用?/p><p style="margin: 0px 0px 1.714285714rem; padding: 0px; border: 0px; vertical-align: baseline; line-height: 24px; color: #444444; font-family: 'Open Sans', Helvetica, Arial, sans-serif; background-color: #ffffff;">q里用AtomicReference是ؓ(f)?jin)用它的原子性的compareAndSetҎ(gu)QCAS操作Q,解决?jin)多U程q发操作D数据不一致的问题Q确保其他线E可以看到锁的真实状?br /></p><h4>~点</h4><ol style="margin: 0px 0px 1.714285714rem; padding: 0px; border: 0px; vertical-align: baseline; list-style-position: outside; list-style-image: initial; line-height: 24px; color: #444444; font-family: 'Open Sans', Helvetica, Arial, sans-serif; background-color: #ffffff;"><li style="margin: 0px 0px 0px 2.571428571rem; padding: 0px; border: 0px; vertical-align: baseline;">CAS操作需要硬件的配合Q?/li><li style="margin: 0px 0px 0px 2.571428571rem; padding: 0px; border: 0px; vertical-align: baseline;">保证各个CPU的缓存(L1、L2、L3、跨CPU Socket、主存)(j)的数据一致性,通讯开销很大Q在多处理器pȝ上更严重Q?/li><li style="margin: 0px 0px 0px 2.571428571rem; padding: 0px; border: 0px; vertical-align: baseline;">没法保证公^性,不保证等待进E?U程按照FIFO序获得锁?/li></ol><h3>Ticket Lock</h3><p style="margin: 0px 0px 1.714285714rem; padding: 0px; border: 0px; vertical-align: baseline; line-height: 24px; color: #444444; font-family: 'Open Sans', Helvetica, Arial, sans-serif; background-color: #ffffff;">Ticket Lock 是ؓ(f)?jin)解决上面的公^性问题,cM于现实中银行柜台的排队叫P(x)锁拥有一个服务号Q表C正在服务的U程Q还有一个排队号Q每个线E尝试获取锁之前先拿一个排队号Q然后不断轮询锁的当前服务号是否是自q排队P如果是,则表C己拥有了(jin)锁,不是则l轮询?/p><p style="margin: 0px 0px 1.714285714rem; padding: 0px; border: 0px; vertical-align: baseline; line-height: 24px; color: #444444; font-family: 'Open Sans', Helvetica, Arial, sans-serif; background-color: #ffffff;">当线E释NӞ服务号?Q这样下一个线E看到这个变化,退?gu)旋?/p><h4>单的实现</h4><div style="background-color:#eeeeee;font-size:13px;border:1px solid #CCCCCC;padding-right: 5px;padding-bottom: 4px;padding-left: 4px;padding-top: 4px;width: 98%;word-break:break-all"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><span style="color: #0000FF; ">import</span><span style="color: #000000; "> java.util.concurrent.atomic.AtomicInteger;  <br /><br /></span><span style="color: #0000FF; ">public</span><span style="color: #000000; "> </span><span style="color: #0000FF; ">class</span><span style="color: #000000; "> TicketLock {<br />    </span><span style="color: #0000FF; ">private</span><span style="color: #000000; "> AtomicInteger serviceNum </span><span style="color: #000000; ">=</span><span style="color: #000000; "> </span><span style="color: #0000FF; ">new</span><span style="color: #000000; "> AtomicInteger(); </span><span style="color: #008000; ">//</span><span style="color: #008000; "> 服务?/span><span style="color: #008000; "><br /></span><span style="color: #000000; ">    </span><span style="color: #0000FF; ">private</span><span style="color: #000000; "> AtomicInteger ticketNum </span><span style="color: #000000; ">=</span><span style="color: #000000; "> </span><span style="color: #0000FF; ">new</span><span style="color: #000000; "> AtomicInteger(); </span><span style="color: #008000; ">//</span><span style="color: #008000; "> 排队?/span><span style="color: #008000; "><br /></span><span style="color: #000000; ">    </span><span style="color: #0000FF; ">public</span><span style="color: #000000; "> </span><span style="color: #0000FF; ">int</span><span style="color: #000000; "> lock() { </span><span style="color: #008000; ">//</span><span style="color: #008000; "> 首先原子性地获得一个排队号</span><span style="color: #008000; "><br /></span><span style="color: #000000; ">        </span><span style="color: #0000FF; ">int</span><span style="color: #000000; "> myTicketNum </span><span style="color: #000000; ">=</span><span style="color: #000000; "> ticketNum.getAndIncrement(); </span><span style="color: #008000; ">//</span><span style="color: #008000; "> 只要当前服务号不是自q׃断轮?/span><span style="color: #008000; "><br /></span><span style="color: #000000; ">        </span><span style="color: #0000FF; ">while</span><span style="color: #000000; "> (serviceNum.get() </span><span style="color: #000000; ">!=</span><span style="color: #000000; "> myTicketNum) { }<br />        </span><span style="color: #0000FF; ">return</span><span style="color: #000000; "> myTicketNum;<br />    }<br />    <br />    </span><span style="color: #0000FF; ">public</span><span style="color: #000000; "> </span><span style="color: #0000FF; ">void</span><span style="color: #000000; "> unlock(</span><span style="color: #0000FF; ">int</span><span style="color: #000000; "> myTicket) { </span><span style="color: #008000; ">//</span><span style="color: #008000; "> 只有当前U程拥有者才能释N</span><span style="color: #008000; "><br /></span><span style="color: #000000; ">        </span><span style="color: #0000FF; ">int</span><span style="color: #000000; "> next </span><span style="color: #000000; ">=</span><span style="color: #000000; "> myTicket </span><span style="color: #000000; ">+</span><span style="color: #000000; "> </span><span style="color: #000000; ">1</span><span style="color: #000000; ">;<br />        serviceNum.compareAndSet(myTicket, next);<br />    }<br />}</span></div><h4>~点</h4><p style="margin: 0px 0px 1.714285714rem; padding: 0px; border: 0px; vertical-align: baseline; line-height: 24px; color: #444444; font-family: 'Open Sans', Helvetica, Arial, sans-serif; background-color: #ffffff;">Ticket Lock 虽然解决?jin)公qx的问题Q但是多处理器系l上Q每个进E?U程占用的处理器都在d同一个变量serviceNum Q每ơ读写操作都必须在多个处理器~存之间q行~存同步Q这?x)导致繁重的pȝȝ和内存的量Q大大降低系l整体的性能?/p><p style="margin: 0px 0px 1.714285714rem; padding: 0px; border: 0px; vertical-align: baseline; line-height: 24px; color: #444444; font-family: 'Open Sans', Helvetica, Arial, sans-serif; background-color: #ffffff;">下面介绍的CLH锁和MCS锁都是ؓ(f)?jin)解册个问题的?/p><p style="margin: 0px 0px 1.714285714rem; padding: 0px; border: 0px; vertical-align: baseline; line-height: 24px; color: #444444; font-family: 'Open Sans', Helvetica, Arial, sans-serif; background-color: #ffffff;">MCS 来自于其发明人名字的首字母:(x) John Mellor-Crummey和Michael Scott?/p><p style="margin: 0px 0px 1.714285714rem; padding: 0px; border: 0px; vertical-align: baseline; line-height: 24px; color: #444444; font-family: 'Open Sans', Helvetica, Arial, sans-serif; background-color: #ffffff;">CLH的发明h是:(x)CraigQLandin and Hagersten?/p><h3>MCS?/h3><p style="margin: 0px 0px 1.714285714rem; padding: 0px; border: 0px; vertical-align: baseline; line-height: 24px; color: #444444; font-family: 'Open Sans', Helvetica, Arial, sans-serif; background-color: #ffffff;">MCS Spinlock 是一U基于链表的可扩展、高性能、公q的自旋锁,甌U程只在本地变量上自旋,直接前驱负责通知其结束自旋,从而极大地减少?jin)不必要的处理器~存同步的次敎ͼ降低?jin)ȝ和内存的开销?/p><div style="background-color:#eeeeee;font-size:13px;border:1px solid #CCCCCC;padding-right: 5px;padding-bottom: 4px;padding-left: 4px;padding-top: 4px;width: 98%;word-break:break-all"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><span style="color: #0000FF; ">import</span><span style="color: #000000; "> java.util.concurrent.atomic.AtomicReferenceFieldUpdater;<br /><br /></span><span style="color: #0000FF; ">public</span><span style="color: #000000; "> </span><span style="color: #0000FF; ">class</span><span style="color: #000000; "> MCSLock {<br />    </span><span style="color: #0000FF; ">public</span><span style="color: #000000; "> </span><span style="color: #0000FF; ">static</span><span style="color: #000000; "> </span><span style="color: #0000FF; ">class</span><span style="color: #000000; "> MCSNode {<br />        </span><span style="color: #0000FF; ">volatile</span><span style="color: #000000; "> MCSNode next;<br />        </span><span style="color: #0000FF; ">volatile</span><span style="color: #000000; "> </span><span style="color: #0000FF; ">boolean</span><span style="color: #000000; "> isBlock </span><span style="color: #000000; ">=</span><span style="color: #000000; "> </span><span style="color: #0000FF; ">true</span><span style="color: #000000; ">; </span><span style="color: #008000; ">//</span><span style="color: #008000; "> 默认是在{待?/span><span style="color: #008000; "><br /></span><span style="color: #000000; ">    }<br /><br />    </span><span style="color: #0000FF; ">volatile</span><span style="color: #000000; "> MCSNode queue;</span><span style="color: #008000; ">//</span><span style="color: #008000; "> 指向最后一个申请锁的MCSNode</span><span style="color: #008000; "><br /></span><span style="color: #000000; ">    </span><span style="color: #0000FF; ">private</span><span style="color: #000000; "> </span><span style="color: #0000FF; ">static</span><span style="color: #000000; "> </span><span style="color: #0000FF; ">final</span><span style="color: #000000; "> AtomicReferenceFieldUpdater UPDATER </span><span style="color: #000000; ">=</span><span style="color: #000000; "> AtomicReferenceFieldUpdater<br />            .newUpdater(MCSLock.</span><span style="color: #0000FF; ">class</span><span style="color: #000000; ">, MCSNode.</span><span style="color: #0000FF; ">class</span><span style="color: #000000; ">, </span><span style="color: #000000; ">"</span><span style="color: #000000; ">queue</span><span style="color: #000000; ">"</span><span style="color: #000000; ">);<br /><br />    </span><span style="color: #0000FF; ">public</span><span style="color: #000000; "> </span><span style="color: #0000FF; ">void</span><span style="color: #000000; "> lock(MCSNode currentThread) {<br />        MCSNode predecessor </span><span style="color: #000000; ">=</span><span style="color: #000000; "> UPDATER.getAndSet(</span><span style="color: #0000FF; ">this</span><span style="color: #000000; ">, currentThread);</span><span style="color: #008000; ">//</span><span style="color: #008000; "> step 1</span><span style="color: #008000; "><br /></span><span style="color: #000000; ">        </span><span style="color: #0000FF; ">if</span><span style="color: #000000; "> (predecessor </span><span style="color: #000000; ">!=</span><span style="color: #000000; "> </span><span style="color: #0000FF; ">null</span><span style="color: #000000; ">) {<br />            predecessor.next </span><span style="color: #000000; ">=</span><span style="color: #000000; "> currentThread;</span><span style="color: #008000; ">//</span><span style="color: #008000; "> step 2</span><span style="color: #008000; "><br /></span><span style="color: #000000; "><br />            </span><span style="color: #0000FF; ">while</span><span style="color: #000000; "> (currentThread.isBlock) {</span><span style="color: #008000; ">//</span><span style="color: #008000; "> step 3</span><span style="color: #008000; "><br /></span><span style="color: #000000; ">            }<br />        }<br />    }<br /><br />    </span><span style="color: #0000FF; ">public</span><span style="color: #000000; "> </span><span style="color: #0000FF; ">void</span><span style="color: #000000; "> unlock(MCSNode currentThread) {<br />        </span><span style="color: #0000FF; ">if</span><span style="color: #000000; "> (currentThread.isBlock) {</span><span style="color: #008000; ">//</span><span style="color: #008000; "> 锁拥有者进行释N才有意义</span><span style="color: #008000; "><br /></span><span style="color: #000000; ">            </span><span style="color: #0000FF; ">return</span><span style="color: #000000; ">;<br />        }<br /><br />        </span><span style="color: #0000FF; ">if</span><span style="color: #000000; "> (currentThread.next </span><span style="color: #000000; ">==</span><span style="color: #000000; "> </span><span style="color: #0000FF; ">null</span><span style="color: #000000; ">) {</span><span style="color: #008000; ">//</span><span style="color: #008000; "> (g)查是否有人排在自己后?/span><span style="color: #008000; "><br /></span><span style="color: #000000; ">            </span><span style="color: #0000FF; ">if</span><span style="color: #000000; "> (UPDATER.compareAndSet(</span><span style="color: #0000FF; ">this</span><span style="color: #000000; ">, currentThread, </span><span style="color: #0000FF; ">null</span><span style="color: #000000; ">)) {</span><span style="color: #008000; ">//</span><span style="color: #008000; "> step 4<br />                </span><span style="color: #008000; ">//</span><span style="color: #008000; "> compareAndSetq回true表示实没有人排在自己后?/span><span style="color: #008000; "><br /></span><span style="color: #000000; ">                </span><span style="color: #0000FF; ">return</span><span style="color: #000000; ">;<br />            } </span><span style="color: #0000FF; ">else</span><span style="color: #000000; "> {<br />                </span><span style="color: #008000; ">//</span><span style="color: #008000; "> H然有h排在自己后面?jin),可能q不知道是谁Q下面是{待后箋?br />                </span><span style="color: #008000; ">//</span><span style="color: #008000; "> q里之所以要忙等是因为:(x)step 1执行完后Qstep 2可能q没执行?/span><span style="color: #008000; "><br /></span><span style="color: #000000; ">                </span><span style="color: #0000FF; ">while</span><span style="color: #000000; "> (currentThread.next </span><span style="color: #000000; ">==</span><span style="color: #000000; "> </span><span style="color: #0000FF; ">null</span><span style="color: #000000; ">) { </span><span style="color: #008000; ">//</span><span style="color: #008000; "> step 5</span><span style="color: #008000; "><br /></span><span style="color: #000000; ">                }<br />            }<br />        }<br /><br />        currentThread.next.isBlock </span><span style="color: #000000; ">=</span><span style="color: #000000; "> </span><span style="color: #0000FF; ">false</span><span style="color: #000000; ">;<br />        currentThread.next </span><span style="color: #000000; ">=</span><span style="color: #000000; "> </span><span style="color: #0000FF; ">null</span><span style="color: #000000; ">;</span><span style="color: #008000; ">//</span><span style="color: #008000; "> for GC</span><span style="color: #008000; "><br /></span><span style="color: #000000; ">    }<br />}</span></div><h3>CLH?/h3><p style="margin: 0px 0px 1.714285714rem; padding: 0px; border: 0px; vertical-align: baseline; line-height: 24px; color: #444444; font-family: 'Open Sans', Helvetica, Arial, sans-serif; background-color: #ffffff;">CLH锁也是一U基于链表的可扩展、高性能、公q的自旋锁,甌U程只在本地变量上自旋,它不断轮询前q状态,如果发现前驱释放?jin)锁q束自旋?br /></p><div style="background-color:#eeeeee;font-size:13px;border:1px solid #CCCCCC;padding-right: 5px;padding-bottom: 4px;padding-left: 4px;padding-top: 4px;width: 98%;word-break:break-all"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><span style="color: #0000FF; ">import</span><span style="color: #000000; "> java.util.concurrent.atomic.AtomicReferenceFieldUpdater;<br /><br /></span><span style="color: #0000FF; ">public</span><span style="color: #000000; "> </span><span style="color: #0000FF; ">class</span><span style="color: #000000; "> CLHLock {<br />    </span><span style="color: #0000FF; ">public</span><span style="color: #000000; "> </span><span style="color: #0000FF; ">static</span><span style="color: #000000; "> </span><span style="color: #0000FF; ">class</span><span style="color: #000000; "> CLHNode {<br />        </span><span style="color: #0000FF; ">private</span><span style="color: #000000; "> </span><span style="color: #0000FF; ">boolean</span><span style="color: #000000; "> isLocked </span><span style="color: #000000; ">=</span><span style="color: #000000; "> </span><span style="color: #0000FF; ">true</span><span style="color: #000000; ">; </span><span style="color: #008000; ">//</span><span style="color: #008000; "> 默认是在{待?/span><span style="color: #008000; "><br /></span><span style="color: #000000; ">    }<br /><br />    @SuppressWarnings(</span><span style="color: #000000; ">"</span><span style="color: #000000; ">unused</span><span style="color: #000000; ">"</span><span style="color: #000000; "> )<br />    </span><span style="color: #0000FF; ">private</span><span style="color: #000000; "> </span><span style="color: #0000FF; ">volatile</span><span style="color: #000000; "> CLHNode tail ;<br />    </span><span style="color: #0000FF; ">private</span><span style="color: #000000; "> </span><span style="color: #0000FF; ">static</span><span style="color: #000000; "> </span><span style="color: #0000FF; ">final</span><span style="color: #000000; "> AtomicReferenceFieldUpdater</span><span style="color: #000000; "><</span><span style="color: #000000; ">CLHLock, CLHNode</span><span style="color: #000000; ">></span><span style="color: #000000; "> UPDATER </span><span style="color: #000000; ">=</span><span style="color: #000000; "> AtomicReferenceFieldUpdater<br />                  . newUpdater(CLHLock.</span><span style="color: #0000FF; ">class</span><span style="color: #000000; ">, CLHNode .</span><span style="color: #0000FF; ">class</span><span style="color: #000000; "> , </span><span style="color: #000000; ">"</span><span style="color: #000000; ">tail</span><span style="color: #000000; ">"</span><span style="color: #000000; "> );<br /><br />    </span><span style="color: #0000FF; ">public</span><span style="color: #000000; "> </span><span style="color: #0000FF; ">void</span><span style="color: #000000; "> lock(CLHNode currentThread) {<br />        CLHNode preNode </span><span style="color: #000000; ">=</span><span style="color: #000000; "> UPDATER.getAndSet( </span><span style="color: #0000FF; ">this</span><span style="color: #000000; ">, currentThread);<br />        </span><span style="color: #0000FF; ">if</span><span style="color: #000000; ">(preNode </span><span style="color: #000000; ">!=</span><span style="color: #000000; "> </span><span style="color: #0000FF; ">null</span><span style="color: #000000; ">) {</span><span style="color: #008000; ">//</span><span style="color: #008000; ">已有U程占用?jin)锁Q进入自?/span><span style="color: #008000; "><br /></span><span style="color: #000000; ">            </span><span style="color: #0000FF; ">while</span><span style="color: #000000; ">(preNode.isLocked ) {<br />            }<br />        }<br />    }<br /><br />    </span><span style="color: #0000FF; ">public</span><span style="color: #000000; "> </span><span style="color: #0000FF; ">void</span><span style="color: #000000; "> unlock(CLHNode currentThread) {<br />        </span><span style="color: #008000; ">//</span><span style="color: #008000; "> 如果队列里只有当前线E,则释攑֯当前U程的引用(for GCQ?/span><span style="color: #008000; "><br /></span><span style="color: #000000; ">        </span><span style="color: #0000FF; ">if</span><span style="color: #000000; "> (</span><span style="color: #000000; ">!</span><span style="color: #000000; ">UPDATER .compareAndSet(</span><span style="color: #0000FF; ">this</span><span style="color: #000000; ">, currentThread, </span><span style="color: #0000FF; ">null</span><span style="color: #000000; ">)) {<br />            </span><span style="color: #008000; ">//</span><span style="color: #008000; "> q有后箋U程</span><span style="color: #008000; "><br /></span><span style="color: #000000; ">            currentThread. isLocked </span><span style="color: #000000; ">=</span><span style="color: #000000; "> </span><span style="color: #0000FF; ">false</span><span style="color: #000000; "> ;</span><span style="color: #008000; ">//</span><span style="color: #008000; "> 改变状态,让后l线E结束自?/span><span style="color: #008000; "><br /></span><span style="color: #000000; ">        }<br />    }<br />}</span></div><p style="margin: 0px 0px 1.714285714rem; padding: 0px; border: 0px; vertical-align: baseline; line-height: 24px; color: #444444; font-family: 'Open Sans', Helvetica, Arial, sans-serif; background-color: #ffffff;"><br /></p><h3>CLH??MCS?的比?/h3><p style="margin: 0px 0px 1.714285714rem; padding: 0px; border: 0px; vertical-align: baseline; line-height: 24px; color: #444444; font-family: 'Open Sans', Helvetica, Arial, sans-serif; background-color: #ffffff;">下图是CLH锁和MCS锁队列图C:(x)<br /><img src="http://coderbee.net/wp-content/uploads/2013/11/CLH-MCS-SpinLock.png" alt="CLH-MCS-SpinLock" style="margin: 0px; padding: 0px; border: 0px; vertical-align: baseline; max-width: 100%; border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; box-shadow: rgba(0, 0, 0, 0.2) 0px 1px 4px;" /></p><p style="margin: 0px 0px 1.714285714rem; padding: 0px; border: 0px; vertical-align: baseline; line-height: 24px; color: #444444; font-family: 'Open Sans', Helvetica, Arial, sans-serif; background-color: #ffffff;">差异Q?/p><ol style="margin: 0px 0px 1.714285714rem; padding: 0px; border: 0px; vertical-align: baseline; list-style-position: outside; list-style-image: initial; line-height: 24px; color: #444444; font-family: 'Open Sans', Helvetica, Arial, sans-serif; background-color: #ffffff;"><li style="margin: 0px 0px 0px 2.571428571rem; padding: 0px; border: 0px; vertical-align: baseline;">从代码实现来看,CLH比MCS要简单得多?/li><li style="margin: 0px 0px 0px 2.571428571rem; padding: 0px; border: 0px; vertical-align: baseline;">从自旋的条g来看QCLH是在本地变量上自旋,MCS是自旋在其他对象的属性?/li><li style="margin: 0px 0px 0px 2.571428571rem; padding: 0px; border: 0px; vertical-align: baseline;">从链表队列来看,CLH的队列是隐式的,CLHNodeq不实际持有下一个节点;MCS的队列是物理存在的?/li><li style="margin: 0px 0px 0px 2.571428571rem; padding: 0px; border: 0px; vertical-align: baseline;">CLH锁释放时只需要改变自q属性,MCS锁释攑ֈ需要改变后l节点的属性?/li></ol><p style="margin: 0px 0px 1.714285714rem; padding: 0px; border: 0px; vertical-align: baseline; line-height: 24px; color: #444444; font-family: 'Open Sans', Helvetica, Arial, sans-serif; background-color: #ffffff;"><strong style="margin: 0px; padding: 0px; border: 0px; vertical-align: baseline;">注意Q这里实现的锁都是独占的Q且不能重入的?/strong></p><img src ="http://m.tkk7.com/DLevin/aggbug/416102.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://m.tkk7.com/DLevin/" target="_blank">DLevin</a> 2015-08-07 00:18 <a href="http://m.tkk7.com/DLevin/archive/2015/08/07/416102.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[转]q发(Concurrent)与ƈ?Parallel)的区?/title><link>http://m.tkk7.com/DLevin/archive/2015/07/28/426471.html</link><dc:creator>DLevin</dc:creator><author>DLevin</author><pubDate>Tue, 28 Jul 2015 15:43:00 GMT</pubDate><guid>http://m.tkk7.com/DLevin/archive/2015/07/28/426471.html</guid><wfw:comment>http://m.tkk7.com/DLevin/comments/426471.html</wfw:comment><comments>http://m.tkk7.com/DLevin/archive/2015/07/28/426471.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://m.tkk7.com/DLevin/comments/commentRss/426471.html</wfw:commentRss><trackback:ping>http://m.tkk7.com/DLevin/services/trackbacks/426471.html</trackback:ping><description><![CDATA[<p>看到一非常简z的解释q发QConcurrentQ与q行QParallelQ的区别的文章,U录一下,以供参考。原文出自:(x)<strong><a >http://joearms.github.io/2013/04/05/concurrent-and-parallel-programming.html</a><br /><br /></strong></p><p style="margin: 0px 0px 10px; text-align: justify; color: #555555; font-family: 'Open Sans', Calibri, Candara, Arial, sans-serif; line-height: 20px; background-color: #ffffff;">What’s the difference between concurrency and parallelism?</p><p style="margin: 0px 0px 10px; text-align: justify; color: #555555; font-family: 'Open Sans', Calibri, Candara, Arial, sans-serif; line-height: 20px; background-color: #ffffff;">Explain it to a five year old.</p><p><strong><img src="http://m.tkk7.com/images/blogjava_net/dlevin/con_and_par.jpg" width="600" height="451" alt="" /><br /></strong></p><p style="margin: 0px 0px 10px; text-align: justify; color: #555555; font-family: 'Open Sans', Calibri, Candara, Arial, sans-serif; line-height: 20px; background-color: #ffffff;"><strong>Concurrent</strong> = Two queues and one coffee machine.</p><p style="margin: 0px 0px 10px; text-align: justify; color: #555555; font-family: 'Open Sans', Calibri, Candara, Arial, sans-serif; line-height: 20px; background-color: #ffffff;"><strong>Parallel</strong> = Two queues and two coffee machines.</p><p><strong><br /></strong></p><img src ="http://m.tkk7.com/DLevin/aggbug/426471.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://m.tkk7.com/DLevin/" target="_blank">DLevin</a> 2015-07-28 23:43 <a href="http://m.tkk7.com/DLevin/archive/2015/07/28/426471.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[转]深入JVM锁机?-Lockhttp://m.tkk7.com/DLevin/archive/2014/07/22/416101.htmlDLevinDLevinTue, 22 Jul 2014 13:46:00 GMThttp://m.tkk7.com/DLevin/archive/2014/07/22/416101.htmlhttp://m.tkk7.com/DLevin/comments/416101.htmlhttp://m.tkk7.com/DLevin/archive/2014/07/22/416101.html#Feedback0http://m.tkk7.com/DLevin/comments/commentRss/416101.htmlhttp://m.tkk7.com/DLevin/services/trackbacks/416101.html

前文Q?a style="color: #ca0000; text-decoration: none;">深入JVM锁机?synchronizedQ分析了(jin)JVM中的synchronized实现Q本文l分析JVM中的另一U锁Lock的实现。与synchronized不同的是QLock完全用Java写成Q在javaq个层面是无关JVM实现的?/p>

在java.util.concurrent.locks包中有很多Lock的实现类Q常用的有ReentrantLock、ReadWriteLockQ实现类ReentrantReadWriteLockQ,其实现都依赖java.util.concurrent.AbstractQueuedSynchronizerc,实现思\都大同小异,因此我们以ReentrantLock作ؓ(f)讲解切入炏V?/p>

1. ReentrantLock的调用过E?/h3>

l过观察ReentrantLock把所有Lock接口的操作都委派C个SynccMQ该cȝ承了(jin)AbstractQueuedSynchronizerQ?/p>

  1. static abstract class Sync extends AbstractQueuedSynchronizer  

Sync又有两个子类Q?/p>

  1. final static class NonfairSync extends Sync  
  1. final static class FairSync extends Sync  

昄是ؓ(f)?jin)支持公q锁和非公^锁而定义,默认情况下ؓ(f)非公q锁?/p>

先理一下Reentrant.lock()Ҏ(gu)的调用过E(默认非公q锁Q:(x)

q些讨厌的Template模式D很难直观的看到整个调用过E,其实通过上面调用q程?qing)AbstractQueuedSynchronizer的注释可以发玎ͼAbstractQueuedSynchronizer中抽象了(jin)l大多数Lock的功能,而只把tryAcquireҎ(gu)延迟到子cM实现。tryAcquireҎ(gu)的语义在于用具体子类判断hU程是否可以获得锁,无论成功与否AbstractQueuedSynchronizer都将处理后面的流E?/p>

2. 锁实玎ͼ加锁Q?/h3>

单说来,AbstractQueuedSynchronizer?x)把所有的hU程构成一个CLH队列Q当一个线E执行完毕(lock.unlock()Q时?x)激z自q后节点Q但正在执行的线Eƈ不在队列中,而那些等待执行的U程全部处于d状态,l过调查U程的显式阻塞是通过调用LockSupport.park()完成Q而LockSupport.park()则调用sun.misc.Unsafe.park()本地Ҏ(gu)Q再q一步,HotSpot在Linux中中通过调用pthread_mutex_lock函数把线E交l系l内核进行阻塞?/p>

该队列如图:(x)

与synchronized相同的是Q这也是一个虚拟队列,不存在队列实例,仅存在节点之间的前后关系。o(h)人疑惑的是ؓ(f)什么采用CLH队列呢?原生的CLH队列是用于自旋锁Q但Doug Lea把其攚wؓ(f)d锁?/p>

当有U程竞争锁时Q该U程?x)首先尝试获得锁Q这对于那些已经在队列中排队的线E来说显得不公^Q这也是非公q锁的由来,与synchronized实现cMQ这样会(x)极大提高吞吐量?/p>

如果已经存在RunningU程Q则新的竞争U程?x)被q加到队,具体是采用基于CAS的Lock-Free法Q因为线Eƈ发对Tail调用CAS可能?x)导致其他线ECASp|Q解军_法是循环CAS直至成功。AbstractQueuedSynchronizer的实现非常精巧,令h叹ؓ(f)观止Q不入细节难以完全领?x)其_NQ下面详l说明实现过E:(x)

2.1 Sync.nonfairTryAcquire

nonfairTryAcquireҎ(gu)是lockҎ(gu)间接调用的第一个方法,每次h锁时都会(x)首先调用该方法?/p>

  1. final boolean nonfairTryAcquire(int acquires) {  
  2.     final Thread current = Thread.currentThread();  
  3.     int c = getState();  
  4.     if (c == 0) {  
  5.         if (compareAndSetState(0, acquires)) {  
  6.             setExclusiveOwnerThread(current);  
  7.             return true;  
  8.         }  
  9.     }  
  10.     else if (current == getExclusiveOwnerThread()) {  
  11.         int nextc = c + acquires;  
  12.         if (nextc < 0) // overflow  
  13.             throw new Error("Maximum lock count exceeded");  
  14.         setState(nextc);  
  15.         return true;  
  16.     }  
  17.     return false;  
  18. }  

该方法会(x)首先判断当前状态,如果c==0说明没有U程正在竞争该锁Q如果不c !=0 说明有线E正拥有?jin)该锁?/p>

如果发现c==0Q则通过CAS讄该状态gؓ(f)acquires,acquires的初始调用gؓ(f)1Q每ơ线E重入该锁都?1Q每ơunlock都会(x)-1Q但?旉N。如果CAS讄成功Q则可以预计其他MU程调用CAS都不?x)再成功Q也p为当前线E得C(jin)该锁Q也作ؓ(f)RunningU程Q很昄q个RunningU程q未q入{待队列?/p>

如果c !=0 但发现自己已l拥有锁Q只是简单地++acquiresQƈ修改status|但因为没有竞争,所以通过setStatus修改Q而非CASQ也是说这D代码实C(jin)偏向锁的功能Qƈ且实现的非常漂亮?br />

2.2 AbstractQueuedSynchronizer.addWaiter

addWaiterҎ(gu)负责把当前无法获得锁的线E包装ؓ(f)一个Noded到队:(x)

  1. private Node addWaiter(Node mode) {  
  2.     Node node = new Node(Thread.currentThread(), mode);  
  3.     // Try the fast path of enq; backup to full enq on failure  
  4.     Node pred = tail;  
  5.     if (pred != null) {  
  6.         node.prev = pred;  
  7.         if (compareAndSetTail(pred, node)) {  
  8.             pred.next = node;  
  9.             return node;  
  10.         }  
  11.     }  
  12.     enq(node);  
  13.     return node;  
  14. }  

其中参数mode是独占锁q是׃n锁,默认为nullQ独占锁。追加到队尾的动作分两步Q?/p>

  1. 如果当前队尾已经存在(tail!=null)Q则使用CAS把当前线E更Cؓ(f)Tail
  2. 如果当前Tail为null或则U程调用CAS讄队尾p|Q则通过enqҎ(gu)l箋讄Tail

下面是enqҎ(gu)Q?/p>

  1. private Node enq(final Node node) {  
  2.     for (;;) {  
  3.         Node t = tail;  
  4.         if (t == null) { // Must initialize  
  5.             Node h = new Node(); // Dummy header  
  6.             h.next = node;  
  7.             node.prev = h;  
  8.             if (compareAndSetHead(h)) {  
  9.                 tail = node;  
  10.                 return h;  
  11.             }  
  12.         }  
  13.         else {  
  14.             node.prev = t;  
  15.             if (compareAndSetTail(t, node)) {  
  16.                 t.next = node;  
  17.                 return t;  
  18.             }  
  19.         }  
  20.     }  
  21. }  


该方法就是@环调用CASQ即使有高ƈ发的场景Q无限@环将?x)最l成功把当前U程q加到队(或设|队_(d)(j)。总而言之,addWaiter的目的就是通过CAS把当前现在追加到队尾Qƈq回包装后的Node实例?/p>

把线E要包装为Node对象的主要原因,除了(jin)用Node构造供虚拟队列外,q用Node包装?jin)各U线E状态,q些状态被_ֿ(j)设计Z些数字|(x)

  • SIGNAL(-1) Q线E的后U程?已被dQ当该线Erelease或cancel时要重新q个后U程(unpark)
  • CANCELLED(1)Q因时或中断Q该U程已经被取?/li>
  • CONDITION(-2)Q表明该U程被处于条仉列,是因ؓ(f)调用?jin)Condition.await而被d
  • PROPAGATE(-3)Q传播共享锁
  • 0Q?代表无状?/li>

2.3 AbstractQueuedSynchronizer.acquireQueued

acquireQueued的主要作用是把已l追加到队列的线E节点(addWaiterҎ(gu)q回|(j)q行dQ但d前又通过tryAccquire重试是否能获得锁Q如果重试成功能则无需dQ直接返?/p>

  1. final boolean acquireQueued(final Node node, int arg) {  
  2.     try {  
  3.         boolean interrupted = false;  
  4.         for (;;) {  
  5.             final Node p = node.predecessor();  
  6.             if (p == head && tryAcquire(arg)) {  
  7.                 setHead(node);  
  8.                 p.next = null; // help GC  
  9.                 return interrupted;  
  10.             }  
  11.             if (shouldParkAfterFailedAcquire(p, node) &&  
  12.                 parkAndCheckInterrupt())  
  13.                 interrupted = true;  
  14.         }  
  15.     } catch (RuntimeException ex) {  
  16.         cancelAcquire(node);  
  17.         throw ex;  
  18.     }  
  19. }  


仔细看看q个Ҏ(gu)是个无限循环Q感觉如果p == head && tryAcquire(arg)条g不满_@环将永远无法l束Q当然不?x)出现死循环Q奥U在于第12行的parkAndCheckInterrupt?x)把当前U程挂vQ从而阻塞住U程的调用栈?/p>

  1. private final boolean parkAndCheckInterrupt() {  
  2.     LockSupport.park(this);  
  3.     return Thread.interrupted();  
  4. }  

如前面所qͼLockSupport.park最l把U程交给pȝQLinuxQ内核进行阻塞。当然也不是马上把请求不到锁的线E进行阻塞,q要(g)查该U程的状态,比如如果该线E处于Cancel状态则没有必要Q具体的(g)查在shouldParkAfterFailedAcquire中:(x)

  1.   private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {  
  2.       int ws = pred.waitStatus;  
  3.       if (ws == Node.SIGNAL)  
  4.           /* 
  5.            * This node has already set status asking a release 
  6.            * to signal it, so it can safely park 
  7.            */  
  8.           return true;  
  9.       if (ws > 0) {  
  10.           /* 
  11.            * Predecessor was cancelled. Skip over predecessors and 
  12.            * indicate retry. 
  13.            */  
  14.    do {  
  15. node.prev = pred = pred.prev;  
  16.    } while (pred.waitStatus > 0);  
  17.    pred.next = node;  
  18.       } else {  
  19.           /* 
  20.            * waitStatus must be 0 or PROPAGATE. Indicate that we 
  21.            * need a signal, but don't park yet. Caller will need to 
  22.            * retry to make sure it cannot acquire before parking.  
  23.            */  
  24.           compareAndSetWaitStatus(pred, ws, Node.SIGNAL);  
  25.       }   
  26.       return false;  
  27.   }  

(g)查原则在于:(x)

  • 规则1Q如果前l的节点状态ؓ(f)SIGNALQ表明当前节炚w要unparkQ则q回成功Q此时acquireQueuedҎ(gu)的第12行(parkAndCheckInterruptQ将DU程d
  • 规则2Q如果前l节点状态ؓ(f)CANCELLED(ws>0)Q说明前|节点已l被攑ּQ则回溯C个非取消的前l节点,q回falseQacquireQueuedҎ(gu)的无限@环将递归调用该方法,直至规则1q回trueQ导致线E阻?/li>
  • 规则3Q如果前l节点状态ؓ(f)非SIGNAL、非CANCELLEDQ则讄前的状态ؓ(f)SIGNALQ返回false后进入acquireQueued的无限@环,与规??/li>

M看来QshouldParkAfterFailedAcquire是靠前l节点判断当前线E是否应该被dQ如果前l节点处于CANCELLED状态,则顺便删除这些节炚w新构造队列?/p>

xQ锁住线E的逻辑已经完成Q下面讨锁的q程?/p>

3. 解锁

h锁不成功的线E会(x)被挂起在acquireQueuedҎ(gu)的第12行,12行以后的代码必须{线E被解锁锁才能执行,假如被阻塞的U程得到解锁Q则执行W?3行,卌|interrupted = trueQ之后又q入无限循环?/p>

从无限@环的代码可以看出Qƈ不是得到解锁的线E一定能获得锁,必须在第6行中调用tryAccquire重新竞争Q因为锁是非公^的,有可能被新加入的U程获得Q从而导致刚被唤醒的U程再次被阻塞,q个l节充分体现?#8220;非公q?#8221;的精髓。通过之后要介绍的解锁机制会(x)看到Q第一个被解锁的线E就是HeadQ因此p == head的判断基本都?x)成功?/p>

x可以看到Q把tryAcquireҎ(gu)延迟到子cM实现的做法非常精妙ƈh极强的可扩展性,令h叹ؓ(f)观止Q当然精妙的不是q个Templae设计模式Q而是Doug Lea寚wl构的精?j)布局?/p>

解锁代码相对单,主要体现在AbstractQueuedSynchronizer.release和Sync.tryReleaseҎ(gu)中:(x)

class AbstractQueuedSynchronizer

  1. public final boolean release(int arg) {  
  2.     if (tryRelease(arg)) {  
  3.         Node h = head;  
  4.         if (h != null && h.waitStatus != 0)  
  5.             unparkSuccessor(h);  
  6.         return true;  
  7.     }  
  8.     return false;  
  9. }  

class Sync

  1. protected final boolean tryRelease(int releases) {  
  2.     int c = getState() - releases;  
  3.     if (Thread.currentThread() != getExclusiveOwnerThread())  
  4.         throw new IllegalMonitorStateException();  
  5.     boolean free = false;  
  6.     if (c == 0) {  
  7.         free = true;  
  8.         setExclusiveOwnerThread(null);  
  9.     }  
  10.     setState(c);  
  11.     return free;  
  12. }  


tryRelease与tryAcquire语义相同Q把如何释放的逻辑延迟到子cM。tryRelease语义很明:(x)如果U程多次锁定Q则q行多次释放Q直至status==0则真正释NQ所谓释N卌|status?Q因为无竞争所以没有用CAS?/p>

release的语义在于:(x)如果可以释放锁,则唤醒队列第一个线E(HeadQ,具体唤醒代码如下Q?/p>

  1. private void unparkSuccessor(Node node) {  
  2.     /* 
  3.      * If status is negative (i.e., possibly needing signal) try 
  4.      * to clear in anticipation of signalling. It is OK if this 
  5.      * fails or if status is changed by waiting thread. 
  6.      */  
  7.     int ws = node.waitStatus;  
  8.     if (ws < 0)  
  9.         compareAndSetWaitStatus(node, ws, 0);   
  10.   
  11.     /* 
  12.      * Thread to unpark is held in successor, which is normally 
  13.      * just the next node.  But if cancelled or apparently null, 
  14.      * traverse backwards from tail to find the actual 
  15.      * non-cancelled successor. 
  16.      */  
  17.     Node s = node.next;  
  18.     if (s == null || s.waitStatus > 0) {  
  19.         s = null;  
  20.         for (Node t = tail; t != null && t != node; t = t.prev)  
  21.             if (t.waitStatus <= 0)  
  22.                 s = t;  
  23.     }  
  24.     if (s != null)  
  25.         LockSupport.unpark(s.thread);  
  26. }  


q段代码的意思在于找出第一个可以unpark的线E,一般说来head.next == headQHead是W一个线E,但Head.next可能被取消或被置为nullQ因此比较稳妥的办法是从后往前找W一个可用线E。貌似回溯会(x)D性能降低Q其实这个发生的几率很小Q所以不?x)有性能影响。之后便是通知pȝ内核l箋该线E,在Linux下是通过pthread_mutex_unlock完成。之后,被解锁的U程q入上面所说的重新竞争状态?/p>

4. Lock VS Synchronized

AbstractQueuedSynchronizer通过构造一个基于阻塞的CLH队列容纳所有的dU程Q而对该队列的操作均通过Lock-FreeQCASQ操作,但对已经获得锁的U程而言QReentrantLock实现?jin)偏向锁的功能?/p>

synchronized的底层也是一个基于CAS操作的等待队列,但JVM实现的更_Q把{待队列分ؓ(f)ContentionList和EntryListQ目的是Z(jin)降低U程的出列速度Q当然也实现?jin)偏向锁Q从数据l构来说二者设计没有本质区别。但synchronizedq实C(jin)自旋锁,q对不同的pȝ和硬件体p进行了(jin)优化Q而Lock则完全依靠系l阻塞挂L(fng)待线E?/p>

当然Lock比synchronized更适合在应用层扩展Q可以承AbstractQueuedSynchronizer定义各种实现Q比如实现读写锁QReadWriteLockQ,公^或不公^锁;同时QLock对应的Condition也比wait/notify要方便的多、灵zȝ多?/p>

DLevin 2014-07-22 21:46 发表评论
]]>
[转]深入JVM锁机?-synchronizedhttp://m.tkk7.com/DLevin/archive/2014/07/22/416100.htmlDLevinDLevinTue, 22 Jul 2014 13:38:00 GMThttp://m.tkk7.com/DLevin/archive/2014/07/22/416100.htmlhttp://m.tkk7.com/DLevin/comments/416100.htmlhttp://m.tkk7.com/DLevin/archive/2014/07/22/416100.html#Feedback0http://m.tkk7.com/DLevin/comments/commentRss/416100.htmlhttp://m.tkk7.com/DLevin/services/trackbacks/416100.html

   目前在Java中存在两U锁机制Qsynchronized和LockQLock接口?qing)其实现cLJDK5增加的内容,其作者是大名鼎鼎的ƈ发专家Doug Lea。本文ƈ不比较synchronized与LockC孰劣Q只是介l二者的实现原理?/p>

   数据同步需要依赖锁Q那锁的同步又依赖谁Qsynchronizedl出的答案是在Y件层面依赖JVMQ而Lockl出的方案是在硬件层面依赖特D的CPU指o(h)Q大家可能会(x)q一步追问:(x)JVM底层又是如何实现synchronized的?

   本文所指说的JVM是指Hotspot?u23版本Q下面首先介lsynchronized的实玎ͼ(x)

   synrhronized关键字简z、清晰、语义明,因此即有了(jin)Lock接口Q用的q是非常q泛。其应用层的语义是可以把M一个非null对象作ؓ(f)"?Q当synchronized作用在方法上Ӟ锁住的便是对象实例(thisQ;当作用在?rn)态方法时锁住的便是对象对应的Class实例Q因为Class数据存在于永久带Q因此静(rn)态方法锁相当于该cȝ一个全局锁;当synchronized作用于某一个对象实例时Q锁住的便是对应的代码块。在HotSpot JVM实现中,锁有个专门的名字Q对象监视器?/span>

  1. U程状态及(qing)状态{?/h3>

    当多个线E同时请求某个对象监视器Ӟ对象监视器会(x)讄几种状态用来区分请求的U程Q?/p>

  • Contention ListQ所有请求锁的线E将被首先放|到该竞争队?/li>
  • Entry ListQContention List中那些有资格成ؓ(f)候选h的线E被UdEntry List
  • Wait SetQ那些调用waitҎ(gu)被阻塞的U程被放|到Wait Set
  • OnDeckQQ何时L多只能有一个线E正在竞争锁Q该U程UCؓ(f)OnDeck
  • OwnerQ获得锁的线E称为Owner
  • !OwnerQ释N的线E?/li>
下图反映?jin)个状态{换关p:(x)
新请求锁的线E将首先被加入到ConetentionList中,当某个拥有锁的线E(Owner状态)(j)调用unlock之后Q如果发现EntryList为空则从ContentionList中移动线E到EntryListQ下面说明下ContentionList和EntryList的实现方式:(x)

1.1 ContentionList虚拟队列

ContentionListq不是一个真正的QueueQ而只是一个虚拟队列,原因在于ContentionList是由Node?qing)其next指针逻辑构成Qƈ不存在一个Queue的数据结构。ContentionList是一个后q先出(LIFOQ的队列Q每ơ新加入Node旉?x)在队头q行Q通过CAS改变W一个节点的的指针ؓ(f)新增节点Q同时设|新增节点的next指向后箋节点Q而取得操作则发生在队。显?dng)该结构其实是个Lock-Free的队列?/p>

因ؓ(f)只有OwnerU程才能从队֏元素Q也即线E出列操作无争用Q当然也避免了(jin)CAS的ABA问题?/p>

1.2 EntryList

EntryList与ContentionList逻辑上同属等待队列,ContentionList?x)被U程q发讉KQؓ(f)?jin)降低对ContentionList队尾的争用,而徏立EntryList。OwnerU程在unlock时会(x)从ContentionList中迁UȝE到EntryListQƈ?x)指定EntryList中的某个U程Q一般ؓ(f)HeadQؓ(f)ReadyQOnDeckQ线E。OwnerU程q不是把锁传递给OnDeckU程Q只是把竞争锁的权利交给OnDeckQOnDeckU程需要重新竞争锁。这样做虽然牺牲?jin)一定的公^性,但极大的提高?jin)整体吞吐量Q在Hotspot中把OnDeck的选择行ؓ(f)UC?#8220;竞争切换”?/div>
 
OnDeckU程获得锁后卛_为ownerU程Q无法获得锁则会(x)依然留在EntryList中,考虑到公qx,在EntryList中的位置不发生变化(依然在队_(d)(j)。如果OwnerU程被waitҎ(gu)dQ则转移到WaitSet队列Q如果在某个时刻被notify/notifyAll唤醒Q则再次转移到EntryList?/div>

2. 自旋?/h3>
那些处于ContetionList、EntryList、WaitSet中的U程均处于阻塞状态,d操作由操作系l完成(在Linxu下通过pthread_mutex_lock函数Q。线E被d后便q入内核QLinuxQ调度状态,q个?x)导致系l在用户态与内核态之间来回切换,严重影响锁的性能
~解上述问题的办法便是自旋,其原理是Q当发生争用Ӟ若OwnerU程能在很短的时间内释放锁,则那些正在争用线E可以稍微等一{(自旋Q,在OwnerU程释放锁后Q争用线E可能会(x)立即得到锁,从而避免了(jin)pȝd。但Ownerq行的时间可能会(x)出?jin)?f)界|争用U程自旋一D|间后q是无法获得锁,q时争用U程则会(x)停止自旋q入d状态(后退Q。基本思\是自旋Q不成功再阻塞,量降低d的可能性,q对那些执行旉很短的代码块来说有非帔R要的性能提高。自旋锁有个更脓(chung)切的名字Q自?指数后退锁,也即复合锁。很昄Q自旋在多处理器上才有意义?/div>
q有个问题是Q线E自旋时做些啥?其实啥都不做Q可以执行几ơfor循环Q可以执行几条空的汇~指令,目的是占着CPU不放Q等待获取锁的机?x)。所以说Q自旋是把双刃剑Q如果旋的时间过长会(x)影响整体性能Q时间过短又达不到gq阻塞的目的。显?dng)自旋的周期选择昑־非常重要Q但q与操作pȝ、硬件体pR系l的负蝲{诸多场景相养I很难选择Q如果选择不当Q不但性能得不到提高,可能q会(x)下降Q因此大家普遍认旋锁不具有扩展性?/div>
 
对自旋锁周期的选择上,HotSpot认ؓ(f)最x间应是一个线E上下文切换的时_(d)但目前ƈ没有做到。经q调查,目前只是通过汇编暂停?jin)几个CPU周期Q除?jin)自旋周期选择QHotSpotq进行许多其他的自旋优化{略Q具体如下:(x)
  • 如果q_负蝲于CPUs则一直自?/li>
  • 如果有超q?CPUs/2)个线E正在自旋,则后来线E直接阻?/li>
  • 如果正在自旋的线E发现Owner发生?jin)变化则延迟自旋旉Q自旋计敎ͼ(j)或进入阻?/li>
  • 如果CPU处于节电(sh)模式则停止自?/li>
  • 自旋旉的最坏情冉|CPU的存储gq(CPU A存储?jin)一个数据,到CPU B得知q个数据直接的时间差Q?/li>
  • 自旋时会(x)适当攑ּU程优先U之间的差异
那synchronized实现何时使用?jin)自旋锁Q答案是在线E进入ContentionListӞ也即W一步操作前。线E在q入{待队列旉先进行自旋尝试获得锁Q如果不成功再进入等待队列。这寚w些已l在{待队列中的U程来说Q稍微显得不公^。还有一个不公^的地Ҏ(gu)自旋U程可能?x)抢占?jin)ReadyU程的锁。自旋锁由每个监视对象维护,每个监视对象一个?/div>

3. 偏向?/h3>
在JVM1.6中引入了(jin)偏向锁,偏向锁主要解x竞争下的锁性能问题Q首先我们看下无竞争下锁存在什么问题:(x)
现在几乎所有的锁都是可重入的,也即已经获得锁的U程可以多次锁住/解锁监视对象Q按照之前的HotSpot设计Q每ơ加?解锁都会(x)涉及(qing)C些CAS操作Q比如对{待队列的CAS操作Q,CAS操作?x)gq本地调用,因此偏向锁的x是一旦线E第一ơ获得了(jin)监视对象Q之后让监视对象“偏向”q个U程Q之后的多次调用则可以避免CAS操作Q说白了(jin)是|个变量Q如果发Cؓ(f)true则无需再走各种加锁/解锁程。但q有很多概念需要解释、很多引入的问题需要解冻I(x)

3.1 CAS?qing)SMP架构

CASZ么会(x)引入本地延迟Q这要从SMPQ对U多处理器)(j)架构说vQ下囑֤概表明了(jin)SMP的结构:(x)
其意思是所有的CPU?x)共享一条系lȝQBUSQ,靠此ȝq接d。每个核都有自己的一U缓存,各核相对于BUS对称分布Q因此这U结构称?#8220;对称多处理器”?/div>
 
而CAS的全UCؓ(f)Compare-And-SwapQ是一条CPU的原子指令,其作用是让CPU比较后原子地更新某个位置的|l过调查发现Q其实现方式是基于硬件^台的汇编指o(h)Q就是说CAS是靠g实现的,JVM只是装?jin)汇~调用,那些AtomicIntegercM是用了(jin)q些装后的接口?/div>
 
Core1和Core2可能?x)同时把d中某个位|的值Load到自qL1 Cache中,当Core1在自qL1 Cache中修改这个位|的值时Q会(x)通过ȝQCore2中L1 Cache对应的?#8220;失效”Q而Core2一旦发现自己L1 Cache中的值失效(UCؓ(f)Cache命中~失Q则?x)通过ȝ从内存中加蝲该地址最新的|大家通过ȝ的来回通信UCؓ(f)“Cache一致性流?#8221;Q因为ȝ被设计ؓ(f)固定?#8220;通信能力”Q如果Cache一致性流量过大,ȝ成为瓶颈。而当Core1和Core2中的值再ơ一致时Q称?#8220;Cache一致?#8221;Q从q个层面来说Q锁设计的终极目标便是减Cache一致性流量?/div>
 
而CAS恰好?x)导致Cache一致性流量,如果有很多线E都׃n同一个对象,当某个Core CAS成功时必然会(x)引vȝ风暴Q这是所谓的本地延迟Q本质上偏向锁就是ؓ(f)?jin)消除CASQ降低Cache一致性流量?/div>
 
Cache一致性:(x)
上面提到Cache一致性,其实是有协议支持的,现在通用的协议是MESIQ最早由Intel开始支持)(j)Q具体参考:(x)http://en.wikipedia.org/wiki/MESI_protocolQ以后会(x)仔细讲解q部分?/div>
Cache一致性流量的例外情况Q?/em>
其实也不是所有的CAS都会(x)Dȝ风暴Q这跟Cache一致性协议有养I具体参考:(x)http://blogs.oracle.com/dave/entry/biased_locking_in_hotspot
NUMA(Non Uniform Memory Access AchitectureQ架构:(x)
与SMP对应q有非对U多处理器架构,现在主要应用在一些高端处理器上,主要特点是没有ȝQ没有公用主存,每个Core有自q内存Q针对这U结构此处不做讨论?/div>

3.2 偏向解除

偏向锁引入的一个重要问题是Q在多争用的场景下,如果另外一个线E争用偏向对象,拥有者需要释攑ց向锁Q而释攄q程?x)带来一些性能开销Q但M说来偏向锁带来的好处q是大于CAS代h(hun)的?/div>

4. ȝ

关于锁,JVM中还引入?jin)一些其他技术比如锁膨胀{,q些与自旋锁、偏向锁相比影响不是很大Q这里就不做介绍?/div>
通过上面的介l可以看出,synchronized的底层实C要依靠Lock-Free的队列,基本思\是自旋后dQ竞争切换后l箋竞争锁,E微牺牲?jin)公qx,但获得了(jin)高吞吐量。下面会(x)l箋介绍JVM锁中的LockQ?a style="color: #ca0000; text-decoration: none;">深入JVM?-LockQ?/div>


DLevin 2014-07-22 21:38 发表评论
]]> վ֩ģ壺 һëƬѿ| ߹ۿһƬ| ߹ۿѳ| ۺɫͼƬ | Ʒþþþþ| XX00Ƶ| ߹ۿ| ޹ƷԲ߲ | ҰƵѹۿȫ| ۺϾƷվ߹ۿ| ߲˳Ƶվ| ˳ŷþ| ѹۿëƬ| þƷ| 3344ѲŹۿƵ| ޹ƷۺϾþ20| Ʒѿ㽶| aëƬȫ| þ޹Ʒһ| 99ƷƵ߹ۿ| tsվ| ƵƷ| ĻĵӰվ| ۺϼС˵| ƵĻ| ߹ۿƬڲ| Ļ޵һ| AVַ߹ۿ| һƵǿŮ| ɫɫۺվ| ѵһƬվ| AV߲| 13ֽˮ| ޾Ʒ97þĻ| 9ᆱƷƵ| ޹ŷۺ997þ| ޾ƷҾþþþþ| պƷƬҹѹ| ˬˬƬA| þ޾ƷVA| ø߹ۿ|