📜  Java的锁框架与线程同步

📅  最后修改于: 2021-09-14 01:30:55             🧑  作者: Mango

线程同步机制可以使用 Lock 框架来实现,它存在于Java.util.concurrent包中。锁框架的工作原理类似于同步块,但锁可能比 Java 的同步块更复杂。锁允许更灵活地构建同步代码。 Java 5 中引入了这种新方法来解决下面提到的同步问题。

让我们看一个 Vector 类,它有许多同步方法。当一个类中有 100 个同步方法时,在任何给定的时间点,这 100 个方法中只有一个线程可以执行。在任何给定的时间点,使用同步块只允许一个线程访问一个方法。这是一项非常昂贵的操作。锁通过允许为不同目的配置各种锁来避免这种情况。可以在一个锁下同步几个方法,在不同的锁下同步其他方法。这允许更多的并发性并提高整体性能。

例子:

Lock lock = new ReentrantLock();
lock.lock();

// Critical section
lock.unlock();

锁通过 lock() 方法获取,通过 unlock() 方法释放。在不使用 lock() 的情况下调用 unlock() 将引发异常。如前所述,Lock 接口存在于Java.util.concurrent.locks 包中,而 ReentrantLock 实现了 Lock 接口。
注意: lock() 调用的次数应始终等于 unlock() 调用的次数。

在下面的代码中,用户创建了一个名为“TestResource”的资源,它分别有两种方法和两种不同的锁。有两个作业名为“DisplayJob”和“ReadJob”。 LockTest 类创建 5 个线程来完成“DisplayJob”和 5 个线程来完成“ReadJob”。所有 10 个线程共享单个资源“TestResource”。

import java.util.Date;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
  
// Test class to test the lock example
// 5 threads are created with DisplayJob 
// and 5 thread are created with ReadJob. 
// Both these jobs use single TestResource named "test".
public class LockTest 
{
    public static void main(String[] args) 
    {
        TestResource test = new TestResource();
        Thread thread[] = new Thread[10];
          for (int i = 0; i < 5; i++)
          {
             thread[i] = new Thread(new DisplayJob(test),
             "Thread " + i);
          }
          for (int i = 5; i < 10; i++)
          {
             thread[i] = new Thread(new ReadJob(test),
             "Thread " + i);
          }
          for (int i = 0; i < 10; i++)
          {
             thread[i].start();
          }
    }
  
}
// DisplayJob class implementing Runnable interface.
// This uses TestResource object passed in the constructor.
// run method invokes displayRecord method on TestResource.
class DisplayJob implements Runnable
{
      
    private TestResource test;
    DisplayJob(TestResource tr)
    {
        test = tr;
    }
    @Override
    public void run() 
    {
        System.out.println("display job");
        test.displayRecord(new Object());
    }
}
// ReadJob class implementing Runnable interface.
// which uses TestResource object passed in the constructor.
// run method invokes readRecord method on TestResource.
class ReadJob implements Runnable
{
      
    private TestResource test;
      
    ReadJob(TestResource tr)
    {
        test = tr;
    }
    @Override
    public void run() 
    {
        System.out.println("read job");
        test.readRecord(new Object());
    }
}
// Class which has two locks and two methods.
  
class TestResource
{
      
    // displayQueueLock is created to make 
    // displayQueueLock thread safe. 
    // When T1 aquires lock on testresource(o1)
    // object displayRecord method 
    // T2 has to wait for lock to be released 
    // by T1 on testresource(o1) object 
    // displayRecord method.  But T3, can execute  
    // readRecord method with out waiting for lock 
    // to be released by t1 as 
    // readRecord method uses readQueueLock not 
    // displayQueueLock.
    private final Lock 
    displayQueueLock = new ReentrantLock();
    private final Lock 
    readQueueLock = new ReentrantLock();
  
    // displayRecord uses displayQueueLock to 
    // achieve thread safety. 
    public void displayRecord(Object document) 
    {
        final Lock displayLock = this.displayQueueLock;
        displayLock.lock();
        try
          {
             Long duration = 
                         (long) (Math.random() * 10000);
             System.out.println(Thread.currentThread().
             getName() + ": TestResource: display a Job"+
             " during " + (duration / 1000) + " seconds ::"+
             " Time - " + new Date());
             Thread.sleep(duration);
          } 
          catch (InterruptedException e)
          {
             e.printStackTrace();
          } 
          finally
          {
             System.out.printf("%s: The document has been"+
             " dispalyed\n", Thread.currentThread().getName());
             displayLock.unlock();
          }
    }
  
    // readRecord uses readQueueLock to achieve thread safety.    
    public void readRecord(Object document)
    {
        final Lock readQueueLock = this.readQueueLock;
        readQueueLock.lock();
        try
          {
             Long duration = 
                       (long) (Math.random() * 10000);
             System.out.println
             (Thread.currentThread().getName()
             + ": TestResource: reading a Job during " +
             (duration / 1000) + " seconds :: Time - " +
             new Date());
             Thread.sleep(duration);
          } 
          catch (InterruptedException e)
          {
             e.printStackTrace();
          } 
          finally
          {
             System.out.printf("%s: The document has"+
             " been read\n", Thread.currentThread().getName());
             readQueueLock.unlock();
          }
    }
}

输出

在上面的例子中,DisplayJob 不需要等待 ReadJob 线程完成任务,因为 ReadJob 和 Display 作业使用两个不同的锁。这对于“同步”是不可能的。

区别如下:

Parameters Lock Framework Synchronized
Across Methods Yes, Locks can be implemented across the methods, you can invoke lock() in method1 and invoke unlock() in method2. Not possible
try to acquire lock yes, trylock(timeout) method is supported by Lock framework, which will get the lock on the resource if it is available, else it returns false and Thread wont get blocked. Not possible with synchronized
Fair lock management Yes, Fair lock management is available in case of lock framework. It hands over the lock to long waiting thread. Even in fairness mode set to true, if trylock is coded, it is served first. Not possible with synchronized
List of waiting threads Yes, List of waiting threads can be seen using Lock framework Not possible with synchronized
Release of lock in exceptions
Lock.lock(); myMethod();Lock.unlock();

unlock() cant be executed in this code if any exception being thrown from myMethod().

Synchronized works clearly in this case. It releases the lock