在Java中,锁(Locks)是用来操控多线程对共享资源的拜访的机制,保证在同一时间内只要一个线程能够拜访特定的资源或履行特定的代码段。锁首要用于完成线程的同步。在Java中,有两种类型的锁被广泛评论:可重入锁(Reentrant Locks)和非可重入锁(Non-reentrant Locks)。这两种锁的首要差异在于它们对现已持有锁的线程再次恳求锁的处理方式。

可重入锁的基本概念

可重入锁(ReentrantLock)答应同一个线程屡次获取同一把锁。这种设计使得一个线程能够进入任何一个它现已拥有的锁同步着的代码块。

ReentrantLock 类的源码解析

ReentrantLockjava.util.concurrent.locks包中的一个类,它完成了Lock接口。ReentrantLock类运用了一个内部类Sync来办理锁的状况,SyncAbstractQueuedSynchronizer (AQS)的子类。AQS是完成同步组件的结构。

Sync 类

Sync内部类在ReentrantLock中有两种首要形态:

  • NonfairSync (非公正锁)
  • FairSync (公正锁)

两者的首要差异在于锁获取的公正性。非公正锁或许会抢占正在等候的线程,而公正锁则按等候的次序来获取锁。

abstract static class Sync extends AbstractQueuedSynchronizer {
    abstract void lock();
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
    protected final boolean tryRelease(int releases) {
        int c = getState() - releases;
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        if (c == 0) {
            free = true;
            setExclusiveOwnerThread(null);
        }
        setState(c);
        return free;
    }
}

锁的获取和开释

  • 锁的获取:当调用lock()办法时,NonfairSyncFairSync将测验经过nonfairTryAcquirefairTryAcquire办法来设置状况。假如当时状况为0(未被确定),状况将被设置为1,并将锁的所有者设置为当时线程。
  • 锁的开释:当调用unlock()办法时,tryRelease办法会被调用,减少持有的状况。假如减后的状况为0,表示锁现已彻底开释,能够被其他线程获取。

可重入性的完成

可重入性是经过查看锁的当时所有者是否为正在履行的线程来完成的。假如一个线程现已持有了锁,它能够直接再次获取锁,状况值将增加,而不是从0变到1。

小结

经过这样的设计,ReentrantLock在供给锁的基本功用(互斥)的一起,还支撑重入,使得编程模型愈加灵敏。此外,ReentrantLock供给的条件变量(Condition)支撑使得Java的多线程编程愈加强壮。相比之下,非重入锁在递归调用时简略导致死锁,因此在实践运用中其运用场景更受限。

非重入锁的基本概念

非重入锁,望文生义,是一次性锁。它要求锁的获取和开释严格一一对应,同一个线程假如现已持有锁,则不能再次获取,直到它开释了锁。假如测验再次获取,将会导致锁的恳求阻塞或者反常。

完成非重入锁的一个简略比如

下面是一个运用Java完成的非重入锁的简略示例,咱们用最基本的同步机制来展示非重入锁的作业原理:

public class SimpleNonReentrantLock {
    private boolean isLocked = false;
    private Thread lockingThread = null;
    public synchronized void lock() throws InterruptedException {
        while (isLocked && lockingThread != Thread.currentThread()) {
            wait();
        }
        isLocked = true;
        lockingThread = Thread.currentThread();
    }
    public synchronized void unlock() {
        if (Thread.currentThread() != this.lockingThread) {
            throw new IllegalMonitorStateException("Calling thread has not locked this lock");
        }
        isLocked = false;
        lockingThread = null;
        notify();
    }
}

源码解析

  1. 锁的状况办理

    • isLocked: 一个布尔标志,表示锁的状况(是否被确定)。
    • lockingThread: 记载获取了锁的线程,这是为了保证只要锁的持有者能够开释它。
  2. lock 办法

    • 运用while循环来查看锁是否现已被其他线程占用。
    • 假如isLocked为真且锁的持有者不是当时线程,则当时线程经过wait()调用等候。
    • 当锁被开释(即isLocked变为假)或当时线程是锁的持有者时,退出循环,设置isLocked为真,并记载锁的持有者为当时线程。
  3. unlock 办法

    • 首要查看调用unlock()的线程是否为锁的持有者。
    • 假如不是,抛出IllegalMonitorStateException反常。
    • 假如是,将isLocked标志设置为假,开释锁,并经过notify()唤醒或许在等候这个锁的线程。

非重入锁的特性

  • 简略性:非重入锁的逻辑比可重入锁简略,因为它不需求处理一个线程屡次获取锁的情况。
  • 防止递归:非重入锁经过防止同一线程屡次取得锁来简化死锁的危险办理,但这也意味着它不能在递归调用中运用,否则会导致线程死锁。

适用场景

因为其特性,非重入锁比较合适那些简略的同步需求场景,其间锁只需求简略的互斥而不涉及复杂的递归调用逻辑。关于需求递归调用同步办法的场景,非重入锁或许不是一个好的挑选。

总之,非重入锁供给了一种更为直接和简化的线程同步机制,但它的运用需求小心,以防止因不恰当的运用导致死锁或其他同步问题。

可重入锁的场景事例

场景描述

假定咱们正在开发一个在线商店的Spring Boot运用,其间包括一个订单处理体系。体系需求指定,订单处理进程必须是同步的,以防止同一时间对同一产品的过度出售(库存问题)。咱们将运用ReentrantLock来保证订单处理进程的线程安全。

代码示例

首要,咱们创立一个Spring Boot运用并装备一个简略的订单处理服务。咱们将运用ReentrantLock保证处理订单的办法是线程安全的。

1. Maven依靠装备(pom.xml)

保证您的pom.xml文件中包括Spring Boot依靠,如下所示:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

2. 订单处理服务(OrderService.java)

package com.example.demo.service;
import org.springframework.stereotype.Service;
import java.util.concurrent.locks.ReentrantLock;
@Service
public class OrderService {
    private final ReentrantLock lock = new ReentrantLock();
    public void processOrder(String productId, int quantity) {
        lock.lock();  // Block until the lock is available
        try {
            // Simulate order processing
            System.out.println("Processing order for product " + productId + ", quantity " + quantity);
            Thread.sleep(1000); // This sleep simulates processing time
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            System.out.println("Failed to process order for product " + productId);
        } finally {
            lock.unlock();  // Always ensure locks are released
        }
    }
}

3. REST Controller(OrderController.java)

package com.example.demo.controller;
import com.example.demo.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
    @Autowired
    private OrderService orderService;
    @GetMapping("/order/{productId}/{quantity}")
    public String placeOrder(@PathVariable String productId, @PathVariable int quantity) {
        orderService.processOrder(productId, quantity);
        return "Order placed for product " + productId + ", quantity " + quantity;
    }
}

4. 主运用类(DemoApplication.java)

package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

运转和测验

在您的IDE或指令行中运转上述Spring Boot运用。您能够运用任何HTTP客户端(如curl或Postman)来测验订单处理API。例如:

curl http://localhost:8080/order/1234/1

此指令应触发订单处理服务,因为加了锁,即使多个恳求一起到达,每个恳求也会被依次安全处理。

小结

经过以上示例,咱们能够看到在Spring Boot中运用ReentrantLock来同步处理特定的办法是十分直接的。ReentrantLock供给的显式确定机制比synchronized关键字更灵敏,特别是在需求可装备锁行为(如公正性)或其他高档功用时。这种方式适用于需求高度操控并发拜访的复杂运用场景。

非重入锁的场景事例

场景描述

假定咱们在开发一个在线电影票务体系,用户能够在线选座并购买电影票。为了简化问题,咱们假定在挑选座位后,用户必须付出,体系需求保证在付出进程中,座位挑选状况不会被同一用户的其他恳求影响(例如,翻开多个浏览器标签页测验并行付出)。咱们将运用非重入锁来完成这一需求。

代码示例

下面的代码示例将演示如何在Spring Boot运用中完成并运用一个简略的非重入锁。

1. Maven依靠装备(pom.xml)

保证您的pom.xml文件中包括Spring Boot依靠:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

2. 非重入锁完成(SimpleNonReentrantLock.java)

package com.example.demo.lock;
public class SimpleNonReentrantLock {
    private boolean isLocked = false;
    public synchronized void lock() {
        if (isLocked) {
            throw new IllegalStateException("Lock is already acquired and is not reentrant.");
        }
        isLocked = true;
    }
    public synchronized void unlock() {
        isLocked = false;
        notify();
    }
}

3. 付出服务(PaymentService.java)

package com.example.demo.service;
import com.example.demo.lock.SimpleNonReentrantLock;
import org.springframework.stereotype.Service;
@Service
public class PaymentService {
    private final SimpleNonReentrantLock lock = new SimpleNonReentrantLock();
    public void processPayment(String seatId) {
        lock.lock();
        try {
            // Simulate payment processing
            System.out.println("Processing payment for seat " + seatId);
            Thread.sleep(1000); // This sleep simulates processing time
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            System.out.println("Payment processing was interrupted for seat " + seatId);
        } finally {
            lock.unlock();
        }
    }
}

4. REST Controller(PaymentController.java)

package com.example.demo.controller;
import com.example.demo.service.PaymentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PaymentController {
    @Autowired
    private PaymentService paymentService;
    @GetMapping("/pay/{seatId}")
    public String makePayment(@PathVariable String seatId) {
        paymentService.processPayment(seatId);
        return "Payment initiated for seat " + seatId;
    }
}

5. 主运用类(DemoApplication.java)

package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

运转和测验

运转您的Spring Boot运用,并经过以下指令测验付出API:

curl http://localhost:8080/pay/1A

假如测验从相同或不同的终端屡次快速履行该指令,将因锁的非重入性而失败,后续测验将会抛出反常。

小结

经过上述示例,咱们演示了如何在Spring Boot中运用自界说的非重入锁来操控对特定资源的拜访,这种锁是经过抛出反常来防止同一线程的屡次入锁,适用于简略的同步需求场景。这个比如简化了非重入锁的完成并显现了其在实践运用中的一种或许用处。

总结

在Java中,锁是用于操控多线程对共享资源拜访的关键工具,可分为可重入锁和非可重入锁两种类型。以下是关于这两种锁类型的要点总结:

可重入锁 (Reentrant Lock)

  • 界说: 答应同一个线程屡次取得同一把锁,从而防止死锁,增加灵敏性。
  • 完成: 经过java.util.concurrent.locks.ReentrantLock,支撑公正与非公正两种策略。
  • 运用场景: 适用于锁的持有者需求屡次进入锁维护的代码块的情况,如递归调用。

非可重入锁 (Non-reentrant Lock)

  • 界说: 一旦一个线程获取了锁,即使是同一个线程也不能再次获取此锁,直到它被开释。
  • 完成: 经过自界说简略锁机制完成,不在Java标准库中清晰供给。
  • 运用场景: 适用于需求简略确定逻辑的场景,保证操作的原子性,防止复杂的递归调用问题。