Lock(锁)

Lock(锁):

  • 从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当
  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。
  • 锁提供了对共享资源的独占访问每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
  • ReentrantLock(可重入锁)类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
     

 API中的介绍:

  • public interface Lock
    Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作。 它们允许更灵活的结构化,可能具有完全不同的属性,并且可以支持多个相关联的对象Condition

    锁是用于通过多个线程控制对共享资源的访问的工具。 通常,锁提供对共享资源的独占访问:一次只能有一个线程可以获取锁,并且对共享资源的所有访问都要求首先获取锁。 但是,一些锁可能允许并发访问共享资源,如ReadWriteLock的读锁。

    使用synchronized方法或语句提供对与每个对象相关联的隐式监视器锁的访问,但是强制所有锁获取和释放以块结构的方式发生:当获取多个锁时,它们必须以相反的顺序被释放,并且所有的锁都必须被释放在与它们相同的词汇范围内。

    虽然synchronized方法和语句的范围机制使得使用监视器锁更容易编程,并且有助于避免涉及锁的许多常见编程错误,但是有时您需要以更灵活的方式处理锁。 例如,用于遍历并发访问的数据结构的一些算法需要使用“手动”或“链锁定”:您获取节点A的锁定,然后获取节点B,然后释放A并获取C,然后释放B并获得D等。 所述的实施方式中Lock接口通过允许获得并在不同的范围释放的锁,并允许获得并以任何顺序释放多个锁使得能够使用这样的技术。

    随着这种增加的灵活性,额外的责任。 没有块结构化锁定会删除使用synchronized方法和语句发生的锁的自动释放。 在大多数情况下,应使用以下惯用语:

      Lock l = ...;
    l.lock();
    try { 
            // access the resource protected by this lock } finally { l.unlock(); 
    } 
    当在不同范围内发生锁定和解锁时,必须注意确保在锁定时执行的所有代码由try-finally或try-catch保护,以确保在必要时释放锁定。

    Lock实现提供了使用synchronized方法和语句的附加功能,通过提供非阻塞尝试来获取锁( tryLock() ),尝试获取可被中断的锁( lockInterruptibly() ,以及尝试获取可以超时( tryLock(long, TimeUnit) )。

    一个Lock类还可以提供与隐式监视锁定的行为和语义完全不同的行为和语义,例如保证排序,非重入使用或死锁检测。 如果一个实现提供了这样的专门的语义,那么实现必须记录这些语义。

    请注意, Lock实例只是普通对象,它们本身可以用作synchronized语句中的目标。 获取Lock实例的监视器锁与调用该实例的任何lock()方法没有特定关系。 建议为避免混淆,您不要以这种方式使用Lock实例,除了在自己的实现中。

 格式:锁【lock.lock】必须紧跟try代码块,且unlock要放到finally第一行

class A{

    private final ReentrantLock lock = new ReentrantLock();

    public void fun(){
        lock.lock();
        try {
            //保证线程安全的代码
        }finally {
            lock.unlock();
        }
    }
}

synchronized 与Lock的对比:

  1. Lock是显式锁手动开启和关闭锁,别忘记关闭锁)     <-------------->    synchronized是隐式锁,出了作用域自动释放
  2. Lock只有代码块锁    <-------------->   synchronized有代码块锁和方法锁
  3. 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

优先使用顺序:

 Lock > 同步代码块(已经进入了方法体,分配了相应资源) > 同步方法(在方法体之外)

 例:三个人去抢票

package com.kuang.thread.lock;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @ClassName TestLock
 * @Description lock锁
 * @Author 麻虾
 * @Date 2021/6/1 14:55 55
 * @Version 1.0
 */

//lock锁:
    //1.定义锁:  private final ReentrantLock lock = new ReentrantLock();
    //2.获得锁: l.lock();
    //3.释放锁: l.unlock();

//锁【lock.lock】必须紧跟try代码块,且unlock要放到finally第一行


public class TestLock {
    public static void main(String[] args){

        //创建三个线程去买票
        TestLock2 testLock2 = new TestLock2();

        new Thread(testLock2,"Marry").start();
        new Thread(testLock2,"Jack").start();
        new Thread(testLock2,"Davi").start();
    }
}

class TestLock2 implements Runnable{
    //票数
    int ticketNums = 10;

    //定义Lock锁
    private final ReentrantLock l = new ReentrantLock();

    @Override
    public void run() {
        //买票
        while (true){
            //加锁
            l.lock();
            try {
                if (ticketNums > 0) {
                    //sleep放大问题的发生性
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "买到了票:" + ticketNums--);
                }else {
                    //无票退出
                    break;
                }
            }finally{
                //别忘了释放锁
                l.unlock();
            }
        }
    }
}

 

 但是执行后发现了问题,运行多次,都是一个人拿到了票,于是增加了票数看看是不是因为运行太快,发现都是一个人结束另一个人才开始。

Marry买到了票:10
Marry买到了票:9
Marry买到了票:8
Marry买到了票:7
Marry买到了票:6
Marry买到了票:5
Marry买到了票:4
Marry买到了票:3
Marry买到了票:2
Marry买到了票:1

 

Jack买到了票:10
Jack买到了票:9
Jack买到了票:8
Jack买到了票:7
Jack买到了票:6
Jack买到了票:5
Jack买到了票:4
Jack买到了票:3
Jack买到了票:2
Jack买到了票:1
Marry买到了票:100
Marry买到了票:99
Marry买到了票:98
Marry买到了票:97
Marry买到了票:96
Marry买到了票:95
Marry买到了票:94
Marry买到了票:93
Marry买到了票:92
Marry买到了票:91
Marry买到了票:90
Marry买到了票:89
Marry买到了票:88
Marry买到了票:87
Marry买到了票:86
Marry买到了票:85
Marry买到了票:84
Marry买到了票:83
Marry买到了票:82
Marry买到了票:81
Marry买到了票:80
Marry买到了票:79
Marry买到了票:78
Marry买到了票:77
Marry买到了票:76
Marry买到了票:75
Marry买到了票:74
Marry买到了票:73
Marry买到了票:72
Marry买到了票:71
Marry买到了票:70
Marry买到了票:69
Marry买到了票:68
Marry买到了票:67
Marry买到了票:66
Marry买到了票:65
Marry买到了票:64
Marry买到了票:63
Marry买到了票:62
Marry买到了票:61
Marry买到了票:60
Marry买到了票:59
Marry买到了票:58
Marry买到了票:57
Marry买到了票:56
Marry买到了票:55
Marry买到了票:54
Marry买到了票:53
Marry买到了票:52
Marry买到了票:51
Jack买到了票:50
Jack买到了票:49
Jack买到了票:48
Jack买到了票:47
Jack买到了票:46
Jack买到了票:45
Jack买到了票:44
Jack买到了票:43
Jack买到了票:42
Jack买到了票:41
Jack买到了票:40
Jack买到了票:39
Jack买到了票:38
Jack买到了票:37
Jack买到了票:36
Jack买到了票:35
Jack买到了票:34
Jack买到了票:33
Jack买到了票:32
Jack买到了票:31
Jack买到了票:30
Davi买到了票:29
Davi买到了票:28
Davi买到了票:27
Davi买到了票:26
Davi买到了票:25
Davi买到了票:24
Davi买到了票:23
Davi买到了票:22
Davi买到了票:21
Davi买到了票:20
Davi买到了票:19
Davi买到了票:18
Davi买到了票:17
Davi买到了票:16
Davi买到了票:15
Davi买到了票:14
Davi买到了票:13
Davi买到了票:12
Davi买到了票:11
Davi买到了票:10
Davi买到了票:9
Davi买到了票:8
Davi买到了票:7
Davi买到了票:6
Davi买到了票:5
Davi买到了票:4
Davi买到了票:3
Davi买到了票:2
Davi买到了票:1

问题所在:

Thread类中的静态方法sleep(),当一个执行中的线程调用了Thread的sleep()方法后,调用线程会暂时让出时间的执行权,这期间不参与cpu的调度,但是该线程持有的锁是不让出的。时间到了会正常返回,线程处于就绪状态,然后参与cpu调度,获取到cpu资源之后就可以运行。
 

原来的代码里使用了sleep,这里要注意!!sleep是不会释放锁的,我们把sleep放在了锁里面,所以无论执行多少次,都是先输出A再输出B,或者先输出B再输出A,反正不会出现交叉输出的情况。因为A获取到这个共享对象的锁之后,执行到了sleep,sleep不会释放锁,所以后面的线程B就获取不到这个共享对象的锁,也就无法执行。

修改代码,将sleep放到锁外面:

public void run() {
        while (true){

            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            l.lock();
            try {
                if (ticketNums > 0) {
                    System.out.println(Thread.currentThread().getName() + "买到了票:" + ticketNums--);
                }else {
                    break;
                }
            }finally{
                l.unlock();
            }
        }
    }

再运行,则正常输出,线程安全:

Jack买到了票:10
Davi买到了票:9
Marry买到了票:8
Marry买到了票:7
Davi买到了票:6
Jack买到了票:5
Davi买到了票:4
Jack买到了票:3
Marry买到了票:2
Davi买到了票:1

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
在C++中,unique_lock是一个用于管理互斥的RAII(资源获取即初始化)类。它提供了一种更灵活的方式来管理互斥的加和解操作。 unique_lock的解操作非常简单,只需要调用其成员函数unlock()即可。例如: ```cpp #include <iostream> #include <mutex> std::mutex mtx; void foo() { std::unique_lock<std::mutex> lock(mtx); // 互斥已经在构造unique_lock对象时被加 // 执行一些需要保护的操作 // 解互斥 lock.unlock(); // 在解后可以执行一些不需要互斥保护的操作 // 再次加互斥 lock.lock(); // 执行一些需要保护的操作 // 解互斥 lock.unlock(); } int main() { foo(); return 0; } ``` 在上面的示例中,我们首先创建了一个std::mutex对象mtx,然后在函数foo()中创建了一个unique_lock对象lock,并将mtx作为参数传递给它。在unique_lock对象的构造函数中,互斥会被自动加。然后我们可以执行一些需要保护的操作。当我们调用lock.unlock()时,互斥会被解,这样我们就可以执行一些不需要互斥保护的操作。最后,我们可以再次调用lock.lock()来重新加互斥,并执行一些需要保护的操作。最后,当unique_lock对象超出作用域时,析构函数会自动解互斥。 需要注意的是,unique_lock对象的unlock()和lock()成员函数可以在任何时候调用,而不仅仅是在构造函数和析构函数中。这使得我们可以更灵活地控制互斥的加和解操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无霸哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值