WEB开发网
开发学院软件开发Java Java 多线程同步问题的探究(三、Lock来了,大家都... 阅读

Java 多线程同步问题的探究(三、Lock来了,大家都让开【1. 认识重入锁】)

 2010-05-14 00:00:00 来源:WEB开发网   
核心提示: java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,Java 多线程同步问题的探究(三、Lock来了,大家都让开【1. 认识重入锁】)(4),而不是作为语言的特性来实现,这就为 Lock 的多种实现留下了空间,细心的读者

java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。这就为 Lock 的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。

JDK 官方文档中提到:

ReentrantLock是“一个可重入的互斥锁 Lock,它具有与使用 synchronized  方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。

ReentrantLock 将由最近成功获得锁,并且还没有释放该锁的线程所拥有。当锁没有被另一个线程所拥有时,调用 lock 的线程将成功获取该锁并返回。如果当前线程已经拥有该锁,此方法将立即返回。可以使用 isHeldByCurrentThread() 和 getHoldCount() 方法来检查此情况是否发生。 ”

简单来说,ReentrantLock有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放。这模仿了 synchronized 的语义;如果线程进入由线程已经拥有的监控器保护的 synchronized 块,就允许线程继续进行,当线程退出第二个(或者后续) synchronized 块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个 synchronized 块时,才释放锁。

ReentrantLock  类(重入锁)实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)

我们把上面的例程改造一下:

public class ThreadDemo implements Runnable {
    class Student {
        private int age = 0;
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            this.age = age;
        }
    }
    Student student = new Student();
    int count = 0;
    ReentrantLock lock1 = new ReentrantLock(false);
    ReentrantLock lock2 = new ReentrantLock(false);
    public static void main(String[] args) {
        ThreadDemo td = new ThreadDemo();
        for (int i = 1; i <= 3; i++) {
            Thread t = new Thread(td, i + "");
            t.start();
        }
    }
    public void run() {
        accessStudent();
    }
    public void accessStudent() {
        String currentThreadName = Thread.currentThread().getName();
        System.out.println(currentThreadName + " is running!");
        lock1.lock();//使用重入锁
        System.out.println(currentThreadName + " got lock1@Step1!");
        try {
            count++;
            Thread.sleep(5000);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.out.println(currentThreadName + " first Reading count:" + count);
            lock1.unlock();
            System.out.println(currentThreadName + " release lock1@Step1!");
        }
        lock2.lock();//使用另外一个不同的重入锁
        System.out.println(currentThreadName + " got lock2@Step2!");
        try {
            Random random = new Random();
            int age = random.nextInt(100);
            System.out.println("thread " + currentThreadName + " set age to:" + age);
            this.student.setAge(age);
            System.out.println("thread " + currentThreadName + " first  read age is:" + this.student.getAge());
            Thread.sleep(5000);
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            System.out.println("thread " + currentThreadName + " second read age is:" + this.student.getAge());
            lock2.unlock();
            System.out.println(currentThreadName + " release lock2@Step2!");
        }
    }
}

从上面这个程序我们看到:

对象锁的获得和释放是由手工编码完成的,所以获得锁和释放锁的时机比使用同步块具有更好的可定制性。并且通过程序的运行结果(运行结果忽略,请读者根据例程自行观察),我们可以发现,和使用同步块的版本相比,结果是相同的。

这说明两点问题:

1. 新的ReentrantLock的确实现了和同步块相同的语义功能。而对象锁的获得和释放都可以由编码人员自行掌握。

2. 使用新的ReentrantLock,免去了为同步块放置合适的对象锁所要进行的考量。

3. 使用新的ReentrantLock,最佳的实践就是结合try/finally块来进行。在try块之前使用lock方法,而在finally中使用unlock方法。

细心的读者又发现了:

在我们的例程中,创建ReentrantLock实例的时候,我们的构造函数里面传递的参数是false。那么如果传递 true又回是什么结果呢?这里面又有什么奥秘呢?

请看本节的续 ———— Fair or Unfair? It is a question...

上一页  1 2 3 4 

Tags:Java 线程 同步

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接