• 使用锁实现同步


      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()方法后也将立即返回,并且线程将继续执行递归调用。再者,我们还可以调用其他的方法。

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

  • 相关阅读:
    E
    牛客比赛—身体训练
    前缀和例题
    欧拉函数模板
    3.30训练题
    poj1321棋盘问题
    记set学习
    B. K-th Beautiful String
    codeforces1293C
    LightOJ 1370 Bi-shoe and Phi-shoe
  • 原文地址:https://www.cnblogs.com/gaopeng527/p/4906749.html
Copyright © 2020-2023  润新知