Java中的锁Lock,Synchronized

本文介绍了并发编程中锁的重要性,通过买票和银行取款案例阐述了无锁状态导致的资源混乱,展示了`synchronized`和`ReentrantLock`两种锁的使用方法,以及它们在保证线程安全上的效果。

目录

为什么会有锁?

无锁造成资源的混乱

加入sychronized锁后

Lock手动锁


为什么会有锁?

锁是作为并发共享数据,保证一致性的工具。例如我们的买票,当我们去创建多个线程去买票时,如果不加锁那必然会对资源造成紊乱。

接下来我们会依据这个买票的模型去探讨这个锁。

无锁造成资源的混乱

我先写一个简单的买票事件。

Ticket票类,其中定义票的个数。

public class Ticket {

    int ticket = 100;
}

User类就是我们的线程类,调用run方法去进行购票

public class User extends Thread{
    Ticket tk;

    public User(Ticket tk){
        this.tk = tk;
    }
    public void run(){
        while(tk.ticket > 0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //同步代码块
            //synchronized (tk) {
                System.out.println(this.getName() + "买到了" + tk.ticket-- + "号票!");
            //}
        }
    }
}

再去一个Main类,就是我们的测试类,去创建多个线程对象然后执行。

public class Main {
    public static void main(String[] args){
        Ticket tk = new Ticket();

        User A = new User(tk);
        User B = new User(tk);
        User C = new User(tk);

        A.start();
        B.start();
        C.start();
    }
}

我们来显示一下运行结果。

 显而易见,必定出现多个线程买到同样的票数。那这样就不符合我们的买票的机制了

这个时候我们就需要去加入sychronized锁,这样的同步代码块,这个锁的作用是只允许一个线程去调用,当一个线程加入sychronized锁时就会从Runnable状态进入Blocked状态

加入sychronized锁后

public class User extends Thread{
    Ticket tk;

    public User(Ticket tk){
        this.tk = tk;
    }
    public void run(){
        while(tk.ticket > 0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //同步代码块
            synchronized (tk) {
                System.out.println(this.getName() + "买到了" + tk.ticket-- + "号票!");
           }
        }
    }
}

注意:synchronized(同步监听器(锁)){

           同步代码块。。。

所有的对象都是一个同步监视器(锁)

在多线程中使用的同步监听器必须是同一个。

效果展示:

 这个时候,我们就能发现,每次买到的票都是不重复的。

Lock手动锁

当然我们除了加synchronized锁之外,我们也可以去手动的加锁,然后手动的去释放掉锁

我们给出代码

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

public class User implements Runnable{
    Ticket tk;

    Lock lock = new ReentrantLock();

    public User(Ticket tk){
        this.tk = tk;
    }

    @Override
    public void run() {
        while (tk.ticket > 0){
            System.out.println(Thread.currentThread().getName()+":");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //mp();
            //手动加锁
            lock.lock();
            System.out.println(Thread.currentThread().getName()+"买到了"+tk.ticket--+"号票!");

            //释放
            lock.unlock();
        }


    }
    public synchronized void mp(){
        System.out.println(Thread.currentThread().getName()+"买到了"+tk.ticket--+"号票!");
    }
}

结果也是与加synchronized锁相同的,这里只需要看改变User这个类,其他的Ticket类,Main类都是一样的。

好了,这就是简单的介绍这两种锁来实现我们的线程安全的问题。

大家也考虑一下银行取钱的问题来保证线程安全。

Java 中,`Lock` 接口 `synchronized` 关键字都用于实现线程同步,以确保多线程环境下共享资源的安全访问。它们在功能使用场景上存在显著差异,适用于不同的并发控制需求。 ### `synchronized` 的特点与使用场景 `synchronized` 是 Java 内置的关键字,它提供了隐式的机制。其主要特点包括: - **隐式**:线程在进入同步代码块或方法时自动获取,在退出时自动释放- **可重入性**:同一个线程可以多次获取同一个而不会发生死[^1]。 - **不可中断**:一个线程在等待获取 `synchronized` 时不能被中断。 - **非公平**:默认情况下,`synchronized` 使用非公平策略,即不保证等待时间最长的线程优先获取- **无法尝试获取**:不能尝试获取并指定超时时间。 `synchronized` 适用于简单的同步需求,例如对共享变量的访问进行保护,或者在并发访问较少的场景中简化代码实现。 ### `Lock` 接口的特点与使用场景 `Lock` 是 `java.util.concurrent.locks` 包中定义的一个接口,常见的实现类是 `ReentrantLock`。它提供了比 `synchronized` 更灵活的机制,主要特点包括: - **显式**:需要显式调用 `lock()` `unlock()` 方法来获取释放- **可中断**:支持线程在等待的过程中被中断。 - **尝试获取**:支持尝试获取并指定超时时间,避免无限期等待。 - **公平**:可以选择公平策略,即按照线程等待顺序来分配- **条件变量**:支持多个条件变量(`Condition`),实现更复杂的线程间协作。 `ReentrantLock` 适用于更复杂的并发控制场景,例如需要尝试获取、超时、中断响应或公平策略的情况。它还支持更精细的线程调度控制。 ### 示例代码 #### 使用 `synchronized` 的示例 ```java public class SynchronizedExample { private int count = 0; public synchronized void increment() { count++; } public synchronized int getCount() { return count; } } ``` #### 使用 `ReentrantLock` 的示例 ```java import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LockExample { private int count = 0; private Lock lock = new ReentrantLock(); public void increment() { lock.lock(); try { count++; } finally { lock.unlock(); } } public int getCount() { lock.lock(); try { return count; } finally { lock.unlock(); } } } ``` ### 适用场景对比 - **`synchronized`**:适用于简单、轻量级的同步需求,代码结构简单且不需要高级特性。 - **`Lock`**:适用于需要更复杂机制的场景,如公平、可中断等待、尝试获取等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值