> Java > java지도 시간 > 본문

Java를 사용하여 Lock 프레임워크를 동시 개발하는 방법에 대한 자세한 설명

高洛峰
풀어 주다: 2017-03-15 18:29:41
원래의
1450명이 탐색했습니다.

요약:

우리는 이미 synchronized가 Java의 키워드이자 JVM에서 중요한 리소스를 구현하는 Java의 내장 기능이라는 것을 알고 있습니다. 레벨 동기화 상호 배타적 액세스이지만 동기화의 세분성이 약간 크고 중단에 대한 응답과 같은 실질적인 문제를 처리할 때 많은 제한이 있습니다. 잠금은 동기화보다 더 넓은 범위의 잠금 작업을 제공하며 스레드 동기화 문제를 보다 우아한 방식으로 처리할 수 있습니다. 이 기사에서는 동기화와 잠금의 비교를 출발점으로 삼아 Java의 Lock프레임워크의 분기를 자세히 소개하고 마지막으로 잠금과 관련된 몇 가지 개념을 제공합니다.

1. 동기화의 한계와 Lock의 장점

synchronized 키워드에 의해 코드 블록이 수정되면 스레드가 해당 잠금을 획득하고 코드 블록을 실행할 때 다른 스레드만 가능합니다. 잠금을 보유하고 있는 스레드가 잠금을 해제할 때까지 기다립니다. 실제로 잠금을 보유한 스레드는 일반적으로 다음 세 가지 상황 중 하나에서 잠금을 해제합니다.

동기화 키워드 사용 시 IO 대기 또는 기타 이유로(예: sleep 메소드 호출 등으로 인해 잠금을 보유한 스레드가 차단된 경우) ) 차단되었지만 잠금이 해제되지 않으면 다른 스레드는 기다릴 수만 있고 다른 선택은 없습니다. 이는 프로그램 실행 효율성에 큰 영향을 미칩니다. 따라서 대기 중인 스레드가 무한정 대기하는 것을 방지하거나(예: 특정 시간 동안만 대기(해결책: tryLock(long

time

, TimeUnit 단위)))하거나 인터럽트에 응답할 수 있는 메커니즘이 필요합니다. ( 해결책: lockInterruptible())), 이 상황은 Lock으로 해결될 수 있습니다.

사례 2:

여러 스레드가 파일을 읽고 쓸 때 읽기 작업과 쓰기 작업이 충돌할 때 쓰기 작업도 쓰기 작업과 충돌하지만 읽기 작업은 서로 충돌한다는 것을 알고 있습니다. 기타 작업과 읽기 작업 간에 충돌이 발생하지 않습니다. 그러나 동기화 키워드를 사용하여 동기화를 수행하면 문제가 발생합니다. 즉, 여러 스레드가 읽기 작업만 수행하는 경우 하나의 스레드만 읽기 작업을 수행할 수 있고 다른 스레드는 잠금이 해제될 때까지만 기다릴 수 있습니다. 읽기 작업을 수행할 수 없습니다. 따라서 여러 스레드가 읽기 작업만 수행하는 경우 스레드 간의 충돌을 방지하기 위한 메커니즘이 필요합니다. 마찬가지로 Lock도 이 상황을 해결할 수 있습니다(해결책: ReentrantReadWriteLock).

사례 3:

Lock을 사용하여 스레드가 성공적으로 잠금을 획득했는지 알 수 있지만(해결책: ReentrantLock) 이는 동기화가 수행할 수 없는 작업입니다.

위에서 언급한 세 가지 상황은 Lock을 통해 해결할 수 있지만, 동기화 키워드는 무력합니다. 실제로 Lock은 java.util.con

current

.locks 패키지 아래의

인터페이스

입니다. Lock 구현은 동기화된 키워드보다 더 넓은 범위의 잠금 작업을 제공합니다. 스레드 동기화 문제를 처리하는 방법이 더 우아해졌습니다. 즉, Lock은 동기화보다 더 많은 기능을 제공합니다. 단, 다음 사항에 주의하시기 바랍니다.

1) 동기화는 Java의 키워드이므로 Java에 내장된 기능이며 JVM 수준에서 구현됩니다. Lock은 JDK 레벨을 기반으로 구현된 Java 인터페이스입니다. 2) 동기화된 메서드는 동기화된 경우 사용자가 수동으로 잠금을 해제할 필요가 없습니다. 메소드 또는 동기화된 코드 블록이 실행된 후 시스템은 자동으로 스레드가 잠금을 해제하도록 합니다. 잠금을 사용하면 사용자가 잠금을 수동으로 해제해야 합니다. 잠금이 적극적으로 해제되지 않으면 교착 상태가 발생할 수 있습니다.

2. java.util.concurrent.locks 패키지에서 자주 사용되는 클래스와 인터페이스

java.util.concurrent에서 주로 사용되는 클래스와 인터페이스의 관계는 다음과 같습니다. locks 패키지:

1. Lock

Lock의 소스 코드를 보면 Lock이 인터페이스라는 것을 알 수 있습니다. 使用Java并发开发Lock 框架详细说明

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;  // 可以响应中断
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;  // 可以响应中断
    void unlock();
    Condition newCondition();
}
로그인 후 복사

각 잠금 인터페이스 방법을 하나씩 분석해 보겠습니다. lock(), tryLock(), tryLock(긴 시간, TimeUnit 단위) 및 lockInterruptously()는 모두 잠금을 획득하는 데 사용됩니다. unLock() 메서드는 잠금을 해제하는 데 사용됩니다. newCondition()은 스레드 간 협력을 위해 이 잠금에 바인딩된 새 Condition 인스턴스를 반환합니다.

1).lock()

在Lock中声明了四个方法来获取锁,那么这四个方法有何区别呢?首先,lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。在前面已经讲到,如果采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此,一般来说,使用Lock必须在try…catch…块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。通常使用Lock来进行同步的话,是以下面这种形式去使用的:

Lock lock = ...;
lock.lock();
try{
    //处理任务
}catch(Exception ex){

}finally{
    lock.unlock();   //释放锁
}
로그인 후 복사

2). tryLock() & tryLock(long time, TimeUnit unit)

tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true;如果获取失败(即锁已被其他线程获取),则返回false,也就是说,这个方法无论如何都会立即返回(在拿不到锁时不会一直在那等待)。

tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false,同时可以响应中断。如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。

一般情况下,通过tryLock来获取锁时是这样使用的:

Lock lock = ...;if(lock.tryLock()) {     try{         //处理任务
     }catch(Exception ex){

     }finally{         lock.unlock();   //释放锁
     } 
}else {    //如果不能获取锁,则直接做其他事情
}
로그인 후 복사

3). lockInterruptibly()

lockInterruptibly()方法比较特殊,当通过这个方法去获取锁时,如果线程 正在等待获取锁,则这个线程能够 响应中断,即中断线程的等待状态。例如,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。

由于lockInterruptibly()的声明中抛出了异常,所以lock.lockInterruptibly()必须放在try块中或者在调用lockInterruptibly()的方法外声明抛出 InterruptedException,但推荐使用后者,原因稍后阐述。因此,lockInterruptibly()一般的使用形式如下:

public void method() throws InterruptedException {    lock.lockInterruptibly();    try {  
     //.....
    }    finally {        lock.unlock();
    }  
}
로그인 후 복사

注意,当一个线程获取了锁之后,是不会被interrupt()方法中断的。因为interrupt()方法只能中断阻塞过程中的线程而不能中断正在运行过程中的线程。因此,当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,那么只有进行等待的情况下,才可以响应中断的。与 synchronized 相比,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。

2、ReentrantLock

ReentrantLock,即 可重入锁。ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法。下面通过一些实例学习如何使用 ReentrantLock。

例 1 : Lock 的正确使用

public class Test {
    private ArrayList<Integer> arrayList = new ArrayList<Integer>();

    public static void main(String[] args) {
        final Test test = new Test();

        new Thread("A") {
            public void run() {
                test.insert(Thread.currentThread());
            };
        }.start();

        new Thread("B") {
            public void run() {
                test.insert(Thread.currentThread());
            };
        }.start();
    }

    public void insert(Thread thread) {
        Lock lock = new ReentrantLock();  // 注意这个地方:lock被声明为局部变量
        lock.lock();
        try {
            System.out.println("线程" + thread.getName() + "得到了锁...");
            for (int i = 0; i < 5; i++) {
                arrayList.add(i);
            }
        } catch (Exception e) {

        } finally {
            System.out.println("线程" + thread.getName() + "释放了锁...");
            lock.unlock();
        }
    }
}/* Output: 
        线程A得到了锁...
        线程B得到了锁...
        线程A释放了锁...
        线程B释放了锁...
 *///:~
로그인 후 복사

结果或许让人觉得诧异。第二个线程怎么会在第一个线程释放锁之前得到了锁?原因在于,在insert方法中的lock变量是局部变量,每个线程执行该方法时都会保存一个副本,那么每个线程执行到lock.lock()处获取的是不同的锁,所以就不会对临界资源形成同步互斥访问。因此,我们只需要将lock声明为成员变量即可,如下所示。

public class Test {    private ArrayList<Integer> arrayList = new ArrayList<Integer>();    private Lock lock = new ReentrantLock();  // 注意这个地方:lock被声明为成员变量
    ...
}/* Output: 
        线程A得到了锁...
        线程A释放了锁...
        线程B得到了锁...
        线程B释放了锁...
 *///:~
로그인 후 복사

例 2 : tryLock() & tryLock(long time, TimeUnit unit)

public class Test {
    private ArrayList<Integer> arrayList = new ArrayList<Integer>();
    private Lock lock = new ReentrantLock(); // 注意这个地方:lock 被声明为成员变量

    public static void main(String[] args) {
        final Test test = new Test();

        new Thread("A") {
            public void run() {
                test.insert(Thread.currentThread());
            };
        }.start();

        new Thread("B") {
            public void run() {
                test.insert(Thread.currentThread());
            };
        }.start();
    }

    public void insert(Thread thread) {
        if (lock.tryLock()) {     // 使用 tryLock()
            try {
                System.out.println("线程" + thread.getName() + "得到了锁...");
                for (int i = 0; i < 5; i++) {
                    arrayList.add(i);
                }
            } catch (Exception e) {

            } finally {
                System.out.println("线程" + thread.getName() + "释放了锁...");
                lock.unlock();
            }
        } else {
            System.out.println("线程" + thread.getName() + "获取锁失败...");
        }
    }
}/* Output: 
        线程A得到了锁...
        线程B获取锁失败...
        线程A释放了锁...
 *///:~
로그인 후 복사

与 tryLock() 不同的是,tryLock(long time, TimeUnit unit) 能够响应中断,即支持对获取锁的中断,但尝试获取一个内部锁的操作(进入一个 synchronized 块)是不能被中断的。如下所示:

public class Test {
    private Lock lock = new ReentrantLock();   
    public static void main(String[] args)  {
        Test test = new Test();
        MyThread thread1 = new MyThread(test,"A");
        MyThread thread2 = new MyThread(test,"B");
        thread1.start();
        thread2.start();

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.interrupt();
    }  

    public void insert(Thread thread) throws InterruptedException{
        if(lock.tryLock(4, TimeUnit.SECONDS)){
            try {
                System.out.println("time=" + System.currentTimeMillis() + " ,线程 " + thread.getName()+"得到了锁...");
                long now = System.currentTimeMillis();
                while (System.currentTimeMillis() - now < 5000) {
                    // 为了避免Thread.sleep()而需要捕获InterruptedException而带来的理解上的困惑,
                    // 此处用这种方法空转3秒
                }
            }finally{
                lock.unlock();
            }
        }else {
            System.out.println("线程 " + thread.getName()+"放弃了对锁的获取...");
        }
    }
}

class MyThread extends Thread {
    private Test test = null;

    public MyThread(Test test,String name) {
        super(name);
        this.test = test;
    }

    @Override
    public void run() {
        try {
            test.insert(Thread.currentThread());
        } catch (InterruptedException e) {
            System.out.println("time=" + System.currentTimeMillis() + " ,线程 " + Thread.currentThread().getName() + "被中断...");
        }
    }
}/* Output: 
        time=1486693682559, 线程A 得到了锁...
        time=1486693684560, 线程B 被中断...(响应中断,时间恰好间隔2s)
 *///:~
로그인 후 복사

例 3 : 使用 lockInterruptibly() 响应中断

public class Test {
    private Lock lock = new ReentrantLock();   
    public static void main(String[] args)  {
        Test test = new Test();
        MyThread thread1 = new MyThread(test,"A");
        MyThread thread2 = new MyThread(test,"B");
        thread1.start();
        thread2.start();

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.interrupt();
    }  

    public void insert(Thread thread) throws InterruptedException{
        //注意,如果需要正确中断等待锁的线程,必须将获取锁放在外面,然后将 InterruptedException 抛出
        lock.lockInterruptibly(); 
        try {  
            System.out.println("线程 " + thread.getName()+"得到了锁...");
            long startTime = System.currentTimeMillis();
            for(    ;     ; ) {              // 耗时操作
                if(System.currentTimeMillis() - startTime >= Integer.MAX_VALUE)
                    break;
                //插入数据
            }
        }finally {
            System.out.println(Thread.currentThread().getName()+"执行finally...");
            lock.unlock();
            System.out.println("线程 " + thread.getName()+"释放了锁");
        } 
        System.out.println("over");
    }
}

class MyThread extends Thread {
    private Test test = null;

    public MyThread(Test test,String name) {
        super(name);
        this.test = test;
    }

    @Override
    public void run() {
        try {
            test.insert(Thread.currentThread());
        } catch (InterruptedException e) {
            System.out.println("线程 " + Thread.currentThread().getName() + "被中断...");
        }
    }
}/* Output: 
        线程 A得到了锁...
        线程 B被中断...
 *///:~
로그인 후 복사

运行上述代码之后,发现 thread2 能够被正确中断,放弃对任务的执行。特别需要注意的是,如果需要正确中断等待锁的线程,必须将获取锁放在外面(try 语句块外),然后将 InterruptedException 抛出。如果不这样做,像如下代码所示:

public class Test {
    private Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        Test test = new Test();
        MyThread thread1 = new MyThread(test, "A");
        MyThread thread2 = new MyThread(test, "B");
        thread1.start();
        thread2.start();

        try {
            Thread.sleep(5000);
            System.out.println("线程" + Thread.currentThread().getName()
                    + " 睡醒了...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.interrupt();
    }

    public void insert(Thread thread) {

        try {
            // 注意,如果将获取锁放在try语句块里,则必定会执行finally语句块中的解锁操作。若线程在获取锁时被中断,则再执行解锁操作就会导致异常,因为该线程并未获得到锁。
            lock.lockInterruptibly();
            System.out.println("线程 " + thread.getName() + "得到了锁...");
            long startTime = System.currentTimeMillis();
            for (;;) {
                if (System.currentTimeMillis() - startTime >= Integer.MAX_VALUE) // 耗时操作
                    break;
                // 插入数据
            }
        } catch (Exception e) {

        } finally {
            System.out.println(Thread.currentThread().getName()
                    + "执行finally...");
            lock.unlock();
            System.out.println("线程 " + thread.getName() + "释放了锁...");
        }
    }
}

class MyThread extends Thread {
    private Test test = null;

    public MyThread(Test test, String name) {
        super(name);
        this.test = test;
    }

    @Override
    public void run() {

        test.insert(Thread.currentThread());
        System.out.println("线程 " + Thread.currentThread().getName() + "被中断...");
    }
}/* Output: 
        线程A 得到了锁...
        线程main 睡醒了...
        B执行finally...
        Exception in thread "B" 
            java.lang.IllegalMonitorStateException
            at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(Unknown Source)
            at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(Unknown Source)
            at java.util.concurrent.locks.ReentrantLock.unlock(Unknown Source)
            at Test.insert(Test.java:39)
            at MyThread.run(Test.java:56)
 *///:~
로그인 후 복사

注意,上述代码就将锁的获取操作放在try语句块里,则必定会执行finally语句块中的解锁操作。在 准备获取锁的 线程B 被中断后,再执行解锁操作就会抛出 IllegalMonitorStateException,因为该线程并未获得到锁却执行了解锁操作。

3、ReadWriteLock

ReadWriteLock也是一个接口,在它里面只定义了两个方法:

public interface ReadWriteLock {    /**
     * Returns the lock used for reading.
     *
     * @return the lock used for reading.
     */    Lock readLock();    /**
     * Returns the lock used for writing.
     *
     * @return the lock used for writing.
     */    Lock writeLock();
}
로그인 후 복사

一个用来获取读锁,一个用来获取写锁。也就是说,将对临界资源的读写操作分成两个锁来分配给线程,从而使得多个线程可以同时进行读操作。下面的 ReentrantReadWriteLock 实现了 ReadWriteLock 接口。

4、ReentrantReadWriteLock

ReentrantReadWriteLock 里面提供了很多丰富的方法,不过最主要的有两个方法:readLock()和writeLock()用来获取读锁和写锁。下面通过几个例子来看一下ReentrantReadWriteLock具体用法。假如有多个线程要同时进行读操作的话,先看一下synchronized达到的效果:

public class Test {
    public static void main(String[] args)  {
        final Test test = new Test();

        new Thread("A"){
            public void run() {
                test.get(Thread.currentThread());
            };
        }.start();

        new Thread("B"){
            public void run() {
                test.get(Thread.currentThread());
            };
        }.start();

    }  

    public synchronized void get(Thread thread) {
        long start = System.currentTimeMillis();
        System.out.println("线程"+ thread.getName()+"开始读操作...");
        while(System.currentTimeMillis() - start <= 1) {
            System.out.println("线程"+ thread.getName()+"正在进行读操作...");
        }
        System.out.println("线程"+ thread.getName()+"读操作完毕...");
    }
}/* Output: 
        线程A开始读操作...
        线程A正在进行读操作...
        ...
        线程A正在进行读操作...
        线程A读操作完毕...
        线程B开始读操作...
        线程B正在进行读操作...
        ...
        线程B正在进行读操作...
        线程B读操作完毕...
 *///:~
로그인 후 복사

这段程序的输出结果会是,直到线程A执行完读操作之后,才会打印线程B执行读操作的信息。而改成使用读写锁的话:

public class Test {
    private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

    public static void main(String[] args) {
        final Test test = new Test();

        new Thread("A") {
            public void run() {
                test.get(Thread.currentThread());
            };
        }.start();

        new Thread("B") {
            public void run() {
                test.get(Thread.currentThread());
            };
        }.start();
    }

    public void get(Thread thread) {
        rwl.readLock().lock(); // 在外面获取锁
        try {
            long start = System.currentTimeMillis();
            System.out.println("线程" + thread.getName() + "开始读操作...");
            while (System.currentTimeMillis() - start <= 1) {
                System.out.println("线程" + thread.getName() + "正在进行读操作...");
            }
            System.out.println("线程" + thread.getName() + "读操作完毕...");
        } finally {
            rwl.readLock().unlock();
        }
    }
}/* Output: 
        线程A开始读操作...
        线程B开始读操作...
        线程A正在进行读操作...
        线程A正在进行读操作...
        线程B正在进行读操作...
        ...
        线程A读操作完毕...
        线程B读操作完毕...
 *///:~
로그인 후 복사

我们可以看到,线程A和线程B在同时进行读操作,这样就大大提升了读操作的效率。不过要注意的是,如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁。如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程也会一直等待释放写锁。

5、Lock和synchronized的选择

总的来说,Lock和synchronized有以下几点不同:

  • (1) Lock是一个接口,是JDK层面的实现;而synchronized是Java中的关键字,是Java的内置特性,是JVM层面的实现;

  • (2) synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;

  • (3) Lock 可以让等待锁的线程响应中断,而使用synchronized时,等待的线程会一直等待下去,不能够响应中断;

  • (4) 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到;

  • (5) Lock可以提高多个线程进行读操作的效率。

在性能上来说,如果竞争资源不激烈,两者的性能是差不多的。而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

三. 锁的相关概念介绍

1、可重入锁

如果锁具备可重入性,则称作为 可重入锁 。像 synchronized和ReentrantLock都是可重入锁,可重入性在我看来实际上表明了 锁的分配机制:基于线程的分配,而不是基于方法调用的分配。举个简单的例子,当一个线程执行到某个synchronized方法时,比如说method1,而在method1中会调用另外一个synchronized方法method2,此时线程不必重新去申请锁,而是可以直接执行方法method2。

class MyClass {    public synchronized void method1() {
        method2();
    }    public synchronized void method2() {

    }
}
로그인 후 복사

上述代码中的两个方法method1和method2都用synchronized修饰了。假如某一时刻,线程A执行到了method1,此时线程A获取了这个对象的锁,而由于method2也是synchronized方法,假如synchronized不具备可重入性,此时线程A需要重新申请锁。但是,这就会造成死锁,因为线程A已经持有了该对象的锁,而又在申请获取该对象的锁,这样就会线程A一直等待永远不会获取到的锁。而由于synchronized和Lock都具备可重入性,所以不会发生上述现象。

2、可中断锁

顾名思义,可中断锁就是可以响应中断的锁。在Java中,synchronized就不是可中断锁,而Lock是可中断锁。
如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。在前面演示tryLock(long time, TimeUnit unit)和lockInterruptibly()的用法时已经体现了Lock的可中断性。

3、公平锁

公平锁即 尽量 以请求锁的顺序来获取锁。比如,同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。而非公平锁则无法保证锁的获取是按照请求锁的顺序进行的,这样就可能导致某个或者一些线程永远获取不到锁。

在Java中,synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。而对于ReentrantLock 和 ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。

看下面两个例子:

Case : 公平锁

public class RunFair {    public static void main(String[] args) throws InterruptedException {
        final Service service = new Service(true);     // 公平锁,设为 true
        Runnable runnable = new Runnable() {
            @Override            public void run() {
                System.out.println("★线程" + Thread.currentThread().getName()
                        + "运行了");
                service.serviceMethod();
            }
        };

        Thread[] threadArray = new Thread[10];        for (int i = 0; i < 10; i++) 
            threadArray[i] = new Thread(runnable);        for (int i = 0; i < 10; i++) 
            threadArray[i].start(); 
    }
}class Service {    private ReentrantLock lock;    public Service(boolean isFair) {
        super();        lock = new ReentrantLock(isFair);
    }    public void serviceMethod() {        try {            lock.lock();
            System.out.println("ThreadName=" + Thread.currentThread().getName()
                    + "获得锁定");
        } finally {            lock.unlock();
        }
    }
}/* Output: 
        ★线程Thread-0运行了
        ★线程Thread-1运行了
        ThreadName=Thread-1获得锁定
        ThreadName=Thread-0获得锁定
        ★线程Thread-2运行了
        ThreadName=Thread-2获得锁定
        ★线程Thread-3运行了
        ★线程Thread-4运行了
        ThreadName=Thread-4获得锁定
        ★线程Thread-5运行了
        ThreadName=Thread-5获得锁定
        ThreadName=Thread-3获得锁定
        ★线程Thread-6运行了
        ★线程Thread-7运行了
        ThreadName=Thread-6获得锁定
        ★线程Thread-8运行了
        ★线程Thread-9运行了
        ThreadName=Thread-7获得锁定
        ThreadName=Thread-8获得锁定
        ThreadName=Thread-9获得锁定
*///:~
로그인 후 복사

Case: 非公平锁

public class RunFair {    public static void main(String[] args) throws InterruptedException {        final Service service = new Service(false);  // 非公平锁,设为 false
        ...
}/* Output: 
        ★线程Thread-0运行了
        ThreadName=Thread-0获得锁定
        ★线程Thread-2运行了
        ThreadName=Thread-2获得锁定
        ★线程Thread-6运行了
        ★线程Thread-1运行了
        ThreadName=Thread-6获得锁定
        ★线程Thread-3运行了
        ThreadName=Thread-3获得锁定
        ★线程Thread-7运行了
        ThreadName=Thread-7获得锁定
        ★线程Thread-4运行了
        ThreadName=Thread-4获得锁定
        ★线程Thread-5运行了
        ThreadName=Thread-5获得锁定
        ★线程Thread-8运行了
        ThreadName=Thread-8获得锁定
        ★线程Thread-9运行了
        ThreadName=Thread-9获得锁定
        ThreadName=Thread-1获得锁定
*///:~
로그인 후 복사

根据上面代码演示结果我们可以看出(线程数越多越明显),在公平锁案例下,多个线程在等待一个锁时,一般而言,等待时间最久的线程(最先请求的线程)会获得该锁。而在非公平锁例下,则无法保证锁的获取是按照请求锁的顺序进行的。

另外, 在ReentrantLock类中定义了很多方法,举几个例子:

  • isFair() //判断锁是否是公平锁

  • isLocked() //判断锁是否被任何线程获取了

  • isHeldByCurrentThread() //判断锁是否被当前线程获取了

  • hasQueuedThreads() //判断是否有线程在等待该锁

  • getHoldCount() //查询当前线程占有lock锁的次数

  • getQueueLength() // 获取正在等待此锁的线程数

  • getWaitQueueLength(Condition condition) // 获取正在等待此锁相关条件condition的线程数在ReentrantReadWriteLock中也有类似的方法,同样也可以设置为公平锁和非公平锁。不过要记住,ReentrantReadWriteLock并未实现Lock接口,它实现的是ReadWriteLock接口。

4.读写锁

读写锁将对临界资源的访问分成了两个锁,一个读锁和一个写锁。正因为有了读写锁,才使得多个线程之间的读操作不会发生冲突。ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。可以通过readLock()获取读锁,通过writeLock()获取写锁。上一节已经演示过了读写锁的使用方法,在此不再赘述。



위 내용은 Java를 사용하여 Lock 프레임워크를 동시 개발하는 방법에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
최신 이슈
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿