使用锁实现同步

  Java提供了同步代码块的另一种机制,它是一种比synchronized关键字更强大也更灵活的机制。这种机制基于Lock接口及其实现类(例如ReentrantLock),提供了更多的好处。

  • 支持更灵活的同步代码块结构。使用synchronized关键字时,只能在同一个syanchronized块结构中获取和释放控制。Lock接口允许实现更复杂的临界区结构(即控制的获取和释放不出现在同一个块结构中)。
  • 相比synchronized关键字,Lock接口提供了更多的功能。其中一个新的功能是tryLock()方法的实现。这个方法试图获取锁,如果锁已经被其他线程获取,它将返回false并继续往下执行代码。使用synchronized关键字时,如果线程A试图执行一个同步代码块,而线程B已在执行这个同步代码块,则线程A就会被挂起直到线程B运行完这个同步代码块。使用锁的tryLock()方法,通过返回值将得知是否有其他线程正在使用这个锁保护的代码块。
  • Lock接口允许分离读和写操作,允许多个读线程和只有一个写线程。
  • 相比synchronized关键字,Lock接口具有更好的性能。

  下面我们将学习如何使用锁来同步代码,并且使用Lock接口和它的实现类——ReentrantLock类来创建一个临界区。这个范例将模拟打印队列。

 1. 创建一个打印队列类PrintQueue。

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

public class PrintQueue {
    //声明一个锁对象,并且用ReentrantLock类初始化
    private final Lock queueLock = new ReentrantLock();
    //实现打印方法
    public void printJob(Object doucument){
        queueLock.lock();
        Long duration = (long) (Math.random()*10000);
        System.out.println(Thread.currentThread().getName()+": PrintQueue: Printing a Job during "+(duration/1000)+" seconds");
        try {
            Thread.sleep(duration);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            queueLock.unlock();
        }
    }
    
}

2. 创建打印工作类Job并且实现Runnable接口。

public class Job implements Runnable {
    private PrintQueue printQueue;
    public Job(PrintQueue printQueue){
        this.printQueue = printQueue;
    }
    @Override
    public void run() {
        System.out.printf("%s: Going to print a document
", Thread.currentThread().getName());
        printQueue.printJob(new Object());
        System.out.printf("%s: The Document has been printed
", Thread.currentThread().getName());
    }

}

3. 创建范例的主类Main

public class Main {

    public static void main(String[] args) {
        //创建一个共享的打印队列对象
        PrintQueue printQueue = new PrintQueue();
        //创建10个打印工作Job对象
        Thread threads[] = new Thread[10];
        for(int i=0;i<10;i++){
            threads[i] = new Thread(new Job(printQueue), "Thread"+i);
        }
        //启动10个线程
        for(int i=0;i<10;i++){
            threads[i].start();
        }
    }
}

4. 程序执行结果如下所示

Thread0: Going to print a document
Thread8: Going to print a document
Thread6: Going to print a document
Thread8: PrintQueue: Printing a Job during 5 seconds
Thread4: Going to print a document
Thread2: Going to print a document
Thread9: Going to print a document
Thread7: Going to print a document
Thread5: Going to print a document
Thread1: Going to print a document
Thread3: Going to print a document
Thread8: The Document has been printed
Thread6: PrintQueue: Printing a Job during 2 seconds
Thread6: The Document has been printed
Thread4: PrintQueue: Printing a Job during 4 seconds
Thread4: The Document has been printed
Thread0: PrintQueue: Printing a Job during 1 seconds
Thread0: The Document has been printed
Thread2: PrintQueue: Printing a Job during 6 seconds
Thread2: The Document has been printed
Thread9: PrintQueue: Printing a Job during 6 seconds
Thread9: The Document has been printed
Thread7: PrintQueue: Printing a Job during 1 seconds
Thread5: PrintQueue: Printing a Job during 5 seconds
Thread7: The Document has been printed
Thread5: The Document has been printed
Thread1: PrintQueue: Printing a Job during 2 seconds
Thread1: The Document has been printed
Thread3: PrintQueue: Printing a Job during 0 seconds
Thread3: The Document has been printed

  这个范例中,在printJob()这个临界区的开始,必须通过lock()方法获得对锁的控制。当线程A访问这个方法时,如果没有其他线程获取对这个锁的控制,lock()方法将让线程A获得锁并且允许它立即执行临界区代码。否则,如果其他线程B正在执行这个锁保护的临界区代码,lock()方法将让线程A休眠直到线程B执行完临界区的代码。

  在线程离开临界区的时候,我们必须使用unlock()方法来释放它持有的锁,以便其它线程可以访问临界区。如果在离开临界区的时候没有调用unlock()方法,其它线程将永远等待,从而导致了死锁(Deadlock)情景。如果在临界区使用了try-catch块,不要忘记将unlock()方法放入finally部分,以便避免死锁。

  ReentrantLock类也允许使用递归调用。如果一个线程获取了锁并且进行了递归调用,它将继续持有这个锁,因此调用lock()方法后也将立即返回,并且线程将继续执行递归调用。再者,我们还可以调用其他的方法。

  必须很小心的使用锁,以避免死锁的发生。

原文地址:https://www.cnblogs.com/gaopeng527/p/4906749.html