线程同步机制可以使用 java.util.concurrent 包中的 Lock 框架来实现。锁框架像同步块一样工作,除了锁可以比 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的资源,它有两个方法和两个不同的锁。 有两个名为DisplayJobReadJob的作业。 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 acquires 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"+
            " dispalyedn", 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 readn", Thread.currentThread().getName());
            readQueueLock.unlock();
        }
    }
}

运行结果如下:

display job 
display job 
display job 
display job 
display job 
read job 
read job 
read job 
read job 
read job 
Thread 5: TestResource: reading a Job during 4 seconds :: Time – Wed Feb 27 15:49:53 UTC 2022 
Thread 0: TestResource: display a Job during 6 seconds :: Time – Wed Feb 27 15:49:53 UTC 2022 
Thread 5: The document has been read 
Thread 6: TestResource: reading a Job during 4 seconds :: Time – Wed Feb 27 15:49:58 UTC 2022

在上面的示例中,DisplayJob 不需要等待 ReadJob 线程完成任务,因为 ReadJob 和 Display 作业使用两个不同的锁。synchronized无法做到这一点。

区别如下:

参数 锁框架 同步
跨方法 是的,可以跨方法实现锁,可以在方法1中调用lock(),在方法2中调用unlock() 不可能
尝试获取锁 是的,锁框架支持trylock(timeout)方法,如果资源可用,它将获取资源的锁,否则返回false,线程不会被阻塞。 同步时不可能
公平锁管理 是的,在锁框架的情况下可以使用公平锁管理。 它将锁交给长时间等待的线程。 即使在将公平模式设置为 true 的情况下,如果对 trylock 进行了编码,也会首先提供它。 同步时不可能
等待线程列表 是的,使用 Lock 框架可以看到等待线程列表 同步时不可能。
释放异常中的锁定 Lock.lock(); myMethod();Lock.unlock(); 如果 myMethod() 抛出任何异常,则无法在此代码中执行 unlock() 在这种情况下,同步工作很清楚,它释放锁 。