java.util.concurrent.locksとsynchronizedとその異同


キーワード:synchronized、java.util.concurrent.locks.ロック、同期、同時、ロック

一、【引用】

JDK1.5以前、同期を実現するにはsynchronizedが主に用いられていたが、JDK 1では.5にjavaが追加されました.util.concurrentパッケージとその2つのサブパッケージlocksとatomicで、サブパッケージlocksにはロックに関する抽象的なクラスが定義されています.本文は主にjavaを紹介する.util.concurrent.locksの使用とsynchronizedとの2つの方法で同期を実現する異同.

二、【synchronized同期】

synchronizedはJ 2 SEに詳しい多くの人がこのキーワードに慣れていないと信じています.複数のスレッド間の同期を実現するために使用されています.一般的には2つの使用方法があります.
1、メソッドにsynchronizedキーを付ける
public synchronized void f() {
//do something
}
、synchronized同期コードブロック
synchronized (mutex) {
// do something
}
この2つの方法については、「
static」の場合、staticのメンバーはクラスに属し、staitc以外のメンバーは特定のインスタンスに属するため、synchronizedを使用する際に、方法または選択した同期変数がstaticであるかどうかに注意する必要があります.次のコードです.
package test.lock;

/**
 * @author whwang
 * 2012-1-10  11:19:04
 */
public class SyncTest {
    
    private Object mutex = new Object();
    
    public synchronized void f1() {
        synchronized (mutex) {
            System.err.println("nonstatic method f1....");
            try {
                Thread.sleep(2 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    public static void main(String[] args) {
        SycnThread thread1 = new SycnThread(new SyncTest());
        SycnThread thread2 = new SycnThread(new SyncTest());
        SycnThread thread3 = new SycnThread(new SyncTest());
        thread1.start();
        thread2.start();
        thread3.start();
    }
    
}

class SycnThread extends Thread {
    
    private SyncTest st;
    
    public SycnThread(SyncTest st) {
        this.st = st;
    }
    
    @Override
    public void run() {
        while (true) {
            st.f1();
        }
    }
}

mainメソッドでは、thread 1、2、3つのスレッドが作成され、いずれもSyncTestのf 1()メソッドが呼び出され、メソッドf 1()はmutex(非static)を使用して同期変数を作成します.この3つのスレッドのメソッドf 1への同期を実現することを意図している場合、実行の結果は、mutexが非staticのメンバー変数であり、つまりnew SyncTest()ごとに、この3つのスレッドを同期させることができないため、期待を裏切ることになります.それらのmutex変数はすべて異なります.このように、上記のプログラムでは、各スレッドがそれぞれ異なる変数として1つのmutexを使用していることを意味し、上記の3つのスレッドを同期させる場合は、mutexをstaticに変更したり、SycnThreadを作成する際に、転送されたSyncTestを同じオブジェクトにすればよい.
また、String定数やグローバル変数を使用する場合、Javaスレッド同期の小さなトラップに注意しなければなりません.落ちたことがありますか.

三、【java.util.concurrent.locks下のロックで同期を実現】

JDK 1より.5 Javaはjavaを提供していると思っています.util.concurrentというパッケージは、そのサブパッケージlocksにおいて、ロックに関する抽象的なクラスを提供しています.主に2種類のロックReentrantLockReentrantReadWriteLockがあり、他の2つのクラスは、AbstractQueuedSynchronizerのような「補助」クラスであり、特定のロックを実現するための抽象クラスであり、ReentrantLockとReentrantReadWriteLockの内部には、この抽象クラスを継承した内部クラスがあり、特定のロックを実現するための機能がある.以下主に説明します:ReentrantLockとReentrantReadWriteLock

1、再入可能なロックReentrantLock

ReentrantLockロックを使用する最も簡単な例:
Lock lock = new ReentrantLock();
try {
    lock.lcok();
    // do something
} finally {
    lock.unlock();
}

上記のコードでは、まずlockを作成し、lock()メソッドを呼び出し、ロックをオンにし、最後にunlock()を呼び出してロックを解除します.注意すべき時、一般的にロックを使用する時、上のスタイルでコード、すなわちlockを書くべきである.unlock()はfinallyブロックに置くことが望ましい.これにより、do somethingの実行中に異常が発生した後、ロックが永遠に解放されないことを防止することができる.
ここまで、ロックとsynchronizedの違いはまだ発見されていません.ロックとsynchronizedの違いは主にロックがsynchronizedよりずっと柔軟であることに現れていますが、これらの柔軟性は主に以下のいくつかの方法に現れています://lock()
tryLock() tryLock(long timeout, TimeUnit timeUnit) lockInterruptibly()
//unlock()
A、
trylock()メソッド:ロックを取得したらすぐにtrueに戻り、他のスレッドがロックを持っている場合はfalseに戻ります.
B、
tryLock(long timeout,TimeUnit timeUnit)メソッド:ロックを取得するとすぐにtrueに戻り、他のスレッドがロックを持っている場合、パラメータが与えられた時間を待つ.待機中、ロックを取得するとtrueに戻り、待機タイムアウトが発生するとfalseに戻る.
synchronizedより柔軟で体現されているのではないでしょうか.適切ではないスコアを打っています.あなたは今仕事で忙しくて、突然内急を感じて、トイレに走って、入り口に行って「清掃中、使用を一時停止」するカードを見つけました.仕方がなくて、仕事はまた忙しくて、だからあなたは先にトイレに行って帰って仕事を忙しくすることを放弃するしかなくて、このように缲り返して、ついにあなたは入ることができることを発见して、そこで......
このようなシーンをsynchronizedでどうやって実現しますか?仕方ありません.synchronizedでは、トイレが一時的に入れないことに気づいたら、おとなしく入り口で待つしかありません.trylock()を使うには、まずトイレに行ってみて、しばらく入ることができないことに気づきました(trylockはfalseに戻ります)、トイレに入ることができるまで仕事を続けます(trylockはtrueに戻ります).さらに、あなたはとても急いでいて、入り口で20秒待ってみることができて、仕事を忙しくすることはできません(trylock(20,TimeUnit.SECONDS);).
C、
lockInterruptibly()メソッドlockInterruptibly()メソッドの実行は、次のとおりです.
ロックが別のスレッドに保持されていない場合は、ロックを取得してすぐに戻り、ロックの保持カウントを1に設定します.
現在のスレッドがこのロックを保持している場合、保持カウントは1に加算され、メソッドはすぐに返されます.
ロックが別のスレッドによって保持されている場合、現在のスレッドはスレッドスケジューリングの目的で無効になり、次のいずれかが発生するまでスレッドはスリープ状態になります.
a、ロックは現在のスレッドによって取得される.
b、または他のスレッドが現在のスレッドを中断する.
現在のスレッドがロックを取得した場合、ロック保持カウントは1に設定されます.
現在のスレッドの場合:
a、この方法に入る時すでにこのスレッドの中断状態を設定した.
b、またはロックの取得を待つ間に中断される.
InterruptedExceptionが放出され、現在のスレッドの割り込み状態がクリアされます.
すなわちlockInterruptibly()メソッドは、待機中に他のスレッドから呼び出すことを可能にする.interruptメソッドは、待機を中断して直接戻り、ロックを取得せずにInterruptedExceptionを放出します.
D、lockInterruptibly()メソッドソースコード紹介
public void lockInterruptibly() throws InterruptedException {
    sync.acquireInterruptibly(1);
}

a、まずlockInterruptiblyは内部クラスsyncのacquireInterruptibly(1)メソッドを呼び出し、このsyncは前述のAbstractQueuedSynchronizerのサブクラスである
abstract static class Sync extends AbstractQueuedSynchronizer {
    // ....
}
public final void acquireInterruptibly(int arg)
            throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (!tryAcquire(arg))
        doAcquireInterruptibly(arg);
}

b.syncのacquireInterruptiblyメソッドでは、まず現在のフィールドが中断されているかどうかを確認し、中断されている場合はInterruptedException異常を投げ出し、そうでない場合はsyncを呼び出すdoAcquireInterruptiblyメソッドを呼び出す.
private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
        //  InterruptedException 
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

c、syncの方法doAcquireInterruptiblyでは、割り込みを検出するとループを直接終了(ロックの取得を待つのではなく)し、InterruptedException異常を直接投げ出し、最後にfinallyでcancelAcquireを呼び出してロック解除操作を行うことが肝心である.
E、これらの方法のほかに、ReentrantLockは多くの実用的な方法を提供しています.ここでは一つ一つ説明しません.
ロックにはもう一つ
特に注意すべき点は、次のコードを見てください.
Lock lock = new ReentrantLock();
// ....
try {
    lock.lock();
    lock.lock();
    // do something...
} finally {
    lock.unlock();
}
// ....

上のコードがlock()メソッドを呼び出す回数とunlock()メソッドを呼び出す回数が異なることがわかりますが、このようなコードの実行が完了すると、他のスレッドはすでにこのlockロックを取得することができますか?
package test.mult;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @ClassName: Test
 * @author whwang
 * @date 2012-1-11  02:04:04
 */
public class Test {

    private String name;
    
    public Test(String name) {
        this.name = name;
    }
    
    public static void main(String[] args) {
        //  " " 
        Lock lock = new ReentrantLock(true);
        MyThread t1 = new MyThread(lock, new Test("test1"));
        MyThread t2 = new MyThread(lock, new Test("test2"));
        t1.start();
        t2.start();
    }

    private static class MyThread extends Thread {
        Lock lock = null;
        Test test = null;

        public MyThread(Lock lock, Test test) {
            this.lock = lock;
            this.test = test;
        }

        @Override
        public void run() {
            while (true) {
                try {
                    //  lock
                    lock.lock();
                    lock.lock();
                    System.err.println(test.name + " locked...");
                    try {
                        Thread.sleep(3 * 1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } finally {
                    lock.unlock();
                }
            }
        }

    }
}

実行結果:
test1 locked...
test1 locked...
test1 locked...
test1 locked...
test1 locked...
test1 locked...

常にtest 1というクラスのスレッドを持ってこそロックを取得することができ、実は最初のロックを取得したスレッドで、彼は永遠にロックを持って放さない.
だからロックを使う時、ロックとunlockは必ず
ペアリング.

2、再入力可能な読み書きロックReentrantReadWriteLock

このロックの使い方はReentrantLockと基本的に同じであるが、ReentrantReadWriteLockは特殊なルール(読み書きロック)を実現し、ReentrantReadWriteLockには2つの内部クラスReentrantReadWriteLockがある.ReadLockとReentrantReadWriteLock.WriteLock(実際には2つの内部クラスだけでなく、AbstractQueuedSynchronizerを実現するSyncなど)は、ReentrantReadWriteLockのreadLock()とwriteLock()をそれぞれ使用して返すことができ、この読み書きロックのルールは、writerがない限り、読み取りロックは複数のreaderスレッドで同時に保持され、書き込みロックは独占的である.簡単な例で説明します.
package test.mult;

import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
* @ClassName: ReadWriteLockTest
* @author whwang
* @date 2012-1-11  02:20:59
 */
public class ReadWriteLockTest {

    static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);

    public static void main(String[] args) {
        //  reader -  

        //  writer -  

        //  reader , writer  -  

        //  writer , reader  -  

        MyThread t1 = new MyThread(0, "t1");
        MyThread t2 = new MyThread(0, "t2");
        MyThread t3 = new MyThread(1, "t3");
        t1.start();
        t2.start();
        t3.start();
    }

    private static class MyThread extends Thread {

        private int type;

        private String threadName;

        public MyThread(int type, String threadName) {
            this.threadName = threadName;
            this.type = type;
        }

        @Override
        public void run() {
            while (true) {
                if (type == 0) {
                    // read
                    ReentrantReadWriteLock.ReadLock readLock = null;
                    try {
                        readLock = lock.readLock();
                        readLock.lock();
                        System.err.println("to read...." + threadName);
                        try {
                            Thread.sleep(5 * 1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } finally {
                        readLock.unlock();
                    }
                } else {
                    // write
                    ReentrantReadWriteLock.WriteLock writeLock = null;
                    try {
                        writeLock = lock.writeLock();
                        writeLock.lock();
                        System.err.println("to write...." + threadName);
                        try {
                            Thread.sleep(5 * 1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } finally {
                        writeLock.unlock();
                    }
                }
            }
        }
    }
}

3、AbstractQueuedSynchronizer:特殊なルールのロックを自分で実現する必要がある場合は、クラスを拡張することで実現できます.
参照ドキュメント:
http://wenku.baidu.com/view/41480552f01dc281e53af090.html
http://tutorials.jenkov.com/java-concurrency/index.html