首页 > 编程语言 >JUC并发编程---Lock锁

JUC并发编程---Lock锁

时间:2023-10-01 10:32:46浏览次数:35  
标签:JUC Thread lock number --- 线程 Lock new



文章目录

  • 什么是Lock
  • synchronized加锁和Lock加锁代码示例
  • synchronized
  • 使用Lock加锁
  • 公平锁和非公平锁
  • 公平锁:
  • 非公平锁:
  • Lock和Synchronized的区别
  • synchronized 版的生产者和消费者
  • Lock 版的生产者和消费者
  • 生产者和消费者出现的问题
  • Condition精准通知和唤醒线程


什么是Lock

官网介绍:
虽然synchronized方法和语句的范围机制使得使用监视器锁更容易编程,并且有助于避免涉及锁的许多常见编程错误,但是有时您
需要以更灵活的方式处理锁。例如,用于遍历并发访问的数据结构的一些算法需要使用“手动”或“链锁定”:您获取节点A的锁定,然
后获取节点B,然后释放A并获取C,然后释放B并获得D等。所述的实施方式中L0Ck接口通过允许获得并在不同的范围释放的锁,并
允许获得并以任何顺序释放多个锁使得能够使用这样的技术。
随着这种增加的灵活性,额外的责任。没有块结构化锁定会删除使用synchronized)方法和语句发生的锁的自动释放。在大多数情
况下,应使用以下惯用语:
Lock l =…1.lock();try /access the resource protected by this lock finally l.unlock();
当在不同范围内发生锁定和解锁时,必须注意确保在锁定时执行的所有代码由try-finally或try-catch保护,以确保在必要时释放锁
定。
Lock实现提供了使用synch ronized方法和语句的附加功能,通过提供非阻塞尝试来获取锁(tryLock()),尝试获取可被中断的
锁(lockInterruptibly()),以及尝试获取可以超时(tryLock(Long,TimeUnit))。
一个Lock类还可以提供与隐式监视锁定的行为和语义完全不同的行为和语义,例如保证排序,非重入使用或死锁检测。如果一个实
现提供了这样的专门的语义,那么实现必须记录这些语义。
请注意,Lock实例只是普通对象,它们本身可以用作synchronized语句中的目标。获取Lock实例的监视器锁与调用该实例的任何
Lock(防法没有特定关系。建议为避免混淆,您不要以这种方式使用L0ck实例,除了在自己的实现中。
除非另有说明,传递任何参数的nuLl值将导致NullPointerException被抛出。

Lock是一个接口,有三个实现类:ReentrantLock(可重入锁)、ReentrantReadWriteLock.ReadLock(读锁),ReentrantReadWriteLock.writeLock(写锁)

synchronized加锁和Lock加锁代码示例

这里统一用一个买票的例子,多个线程实现不同的窗口进行买票

synchronized

public class TicketSale {
    public static void main(String[] args) {
        //并发:多线程操作同一个资源类,把资源丢入线程
        Ticket1 ticket = new Ticket1();
        //@FunctionalInterface 函数式接口,jkd1.8 lambda 表达式(参数)->{代码}
        new Thread(()->{
            for (int i = 0; i < 30; i++) {
                ticket.sale();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 30; i++) {
                ticket.sale();
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 30; i++) {
                ticket.sale();
            }
        },"C").start();
    }
}
//资源类OOP
class  Ticket1{
    //属性  方法
    private int number = 30;
    //卖票的方式
    public synchronized void sale(){
        if (number>0){
            System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,剩余"+number);
        }
    }
}

使用Lock加锁

public class SaleTicketDemo1 {
    public static void main(String[] args) {
        Ticket ticket=new Ticket();
        new Thread(()->{for (int i = 0; i < 40; i++) ticket.sale();},"A").start();
        new Thread(()->{for (int i = 0; i < 40; i++) ticket.sale();},"B").start();
        new Thread(()->{for (int i = 0; i < 40; i++) ticket.sale();},"C").start();
    }

}
//资源类
class Ticket{
    //属性
    private int number=50;
    Lock lock=new ReentrantLock();

    public void sale(){
        lock.lock();//加锁
        try {
            if (number>0){
                System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,剩余:"+number);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();//解锁
        }

    }
}

公平锁和非公平锁

公平锁和非公平锁是在多线程环境下用于同步访问共享资源的机制。它们之间的区别在于线程获取锁的顺序不同。

公平锁:

公平锁是指多个线程按照申请锁的顺序来获取锁,即先来先得的原则。当一个线程释放锁后,等待时间最长的线程会获得锁。
公平锁的优点是保证了资源的公平性,避免了饥饿现象,所有线程都有机会获取到资源。
公平锁的缺点是需要维护一个有序的等待队列,增加了系统开销,降低了并发性能。

非公平锁:

非公平锁是指多个线程获取锁的顺序是不确定的,有可能新申请锁的线程会在等待队列中插队,先于等待时间更长的线程获取到锁。
非公平锁的优点是相对于公平锁,减少了等待时间,提高了吞吐量。
非公平锁的缺点是可能会导致某些线程长时间等待,产生饥饿现象,不公平性可能会造成一些线程无法获得资源。
选择使用公平锁还是非公平锁,取决于具体的业务场景和需求:

如果对资源的访问顺序要求比较高,希望保证公平性,可以选择公平锁。
如果对吞吐量要求比较高,对于资源访问的顺序没有特别的要求,可以选择非公平锁。
需要注意的是,Java中的ReentrantLock默认是非公平锁,但可以通过构造函数参数来指定为公平锁。而synchronized关键字是一种非公平锁。在使用锁的时候,需要根据具体情况选择适合的锁机制。

在ReentrantLock中,默认是非公平锁,如果需要使用公平锁可以通过传入boolean类型的参数进行转换,true为公平锁

JUC并发编程---Lock锁_开发语言

Lock和Synchronized的区别

1、Synchronized内置的ava关键字,Lock是一个ava类
2、Synchronized无法判断获取锁的状态,Lock可以判断是否获取到了锁
3、Synchronized会自动释放锁,lock必须要手动释放锁!如果不释放锁,死锁
4、Synchronized线程1(获得锁,阻塞)、线程2(等待,傻傻的等);Lock锁就不一定会等待下去(tryLock方法);
5、Synchronized可重入锁,不可以中断的,非公平;Lock,可重入锁,可以判断锁,非公平(可以自己设置);
6、Synchronized适合锁少量的代码同步问题,Lock适合锁大量的同步代码!

synchronized 版的生产者和消费者

生产者和消费者之间通过线程通信问题,生产者和消费者问题 等待唤醒 ,通知唤醒

public class A {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }
}
//等待 业务 通知
class Data{//数字  资源类
    private  int number = 0;
    //加操作
    public  synchronized void increment() throws InterruptedException {
        if(number != 0) {
            //等待
            this.wait();//在哪里睡就在哪里醒
        }
        number++;
        //通知其他线程,我+1完毕了
        System.out.println(Thread.currentThread().getName()+"-->"+number);
        this.notifyAll();
    }
    //减操作
    public synchronized void decrement() throws InterruptedException {
        if(number == 0) {
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"-->"+number);
        this.notifyAll();
    }
}

上面这段代码是4个线程进行交替。4个线程出现的问题在下文会有讲解。正常可以使用2个线程线程交替

Lock 版的生产者和消费者

public class B {
    public static void main(String[] args) {
        Data2 data = new Data2();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }
}
//等待 业务 通知
class Data2 {//数字  资源类
    private int number = 0;
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    //    condition.await();//等待
//    condition.signalAll();//唤醒全部
    public void increment() throws InterruptedException {
        lock.lock();
        try {
            //业务代码
            if(number != 0) {
                //等待
                condition.await();
            }
            number++;
            //通知其他线程,我+1完毕了
            System.out.println(Thread.currentThread().getName() + "-->" + number);
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void decrement() throws InterruptedException {
        lock.lock();
        try {
            if(number == 0) {
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + "-->" + number);
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

生产者和消费者出现的问题

在通过上面的代码中,如果两个线程进行的话,可以实现生产者和消费者进行0、1的交替通信,那么如果再增加两个线程的话,就有可能出现问题,并不是0、1进行交替,而是可能出现8、9这样的数值。如图:

JUC并发编程---Lock锁_开发语言_02

那这种情况就是虚假唤醒,也就是在不该醒的地方,线程醒了并执行了后续的操作。

虚假唤醒出现的原因:

JUC并发编程---Lock锁_算法_03


如上图:当A线程进入阻塞的时候,其他三个线程就会竞争资源,这个使用可能c会获取资源,那么当它执行加操作的时候,会发现不符合条件就会进行阻塞,那么A、B、D进行争抢资源,当到A之后,发现不符合还是会进入阻塞,重复上面的操作,那么可能C抢到资源,这里注意,在刚刚的步骤中,C已经进行了一次阻塞,这里使用的wait(),那么wait是在哪里睡就在哪里醒,这里c就醒了,并执行了后续的加操作。所以才会出现加到7、8这种数值的情况

那么如果解决呢?可以把if判断更换为while判断。

Condition精准通知和唤醒线程

Condition因素出Object监视器方法(wait notify和notifyAll)成不同的对象,以得到具有多个等待集的每个对象,通过
将它们与使用任意的组合的效果Lock个实现。'Lock替换synchronized方法和语句的使用
Condition取代了对象监视器方法的使用。
条件(也称为条件队/域条件变量)为一个线程暂停执行(“等待”)提供了一种方法,直到另一个线程通知某些状态现在可能为真。
因为访问此共享状态信息发生在不同的线程中,所以它必须被保护,因此某种形式的锁与该条件相关联。等待条件的关键属性是它原

在这里插入代码片
public class C {
 public static void main(String[] args) {
 Data3 data = new Data3();
 new Thread(() -> {
 for (int i = 0; i < 10; i++) {
 data.printA();
 }
 }, “A”).start();
 new Thread(() -> {
 for (int i = 0; i < 10; i++) {
 data.printB();
 }
 }, “B”).start();
 new Thread(() -> {
 for (int i = 0; i < 10; i++) {
 data.printC();
 }
 }, “C”).start();
 }
 }
 class Data3 {// 资源类 Lock
 private Lock lock = new ReentrantLock();
 private Condition condition1 = lock.newCondition();//监视器
 private Condition condition2 = lock.newCondition();
 private Condition condition3 = lock.newCondition();
 private int number = 1;//number为1的时候A执行,2的时候B执行,3的时候C执行
 public void printA() {
 lock.lock();
 try {
 //业务,判断->执行->通知
 while (number != 1) {
 //等待
 condition1.await();
 }
 System.out.println(Thread.currentThread().getName() + “—>A”);
 //唤醒指定的人:B
 number = 2;
 condition2.signal();
 } catch (Exception e) {
 e.printStackTrace();
 } finally {
 lock.unlock();
 }
 }
 public void printB() {
 lock.lock();
 try {
 //业务,判断->执行->通知
 while (number != 2) {
 //等待
 condition2.await();
 }
 System.out.println(Thread.currentThread().getName() + “—>B”);
 //唤醒指定的人:C
 number = 3;
 condition3.signal();
 } catch (Exception e) {
 e.printStackTrace();
 } finally {
 lock.unlock();
 }
 }
 public void printC() {
 lock.lock();
 try {
 //业务,判断->执行->通知
 while (number!=3){
 //等待
 condition3.await();
 }
 System.out.println(Thread.currentThread().getName()+“—>C”);
 number=1;
 condition1.signal();
 } catch (Exception e) {
 e.printStackTrace();
 } finally {
 lock.unlock();
 }
 }
 }


上面代码是通过设置线程执行完之后,后面需要执行那个线程的


标签:JUC,Thread,lock,number,---,线程,Lock,new
From: https://blog.51cto.com/u_15902978/7672394

相关文章

  • git中的cherry-pick和merge有些区别以及cherry-pick怎么用
    git中的cherry-pick和merge在使用场景上有些区别:cherry-pick用于将另一个分支的某一次或几次commit应用到当前分支。它可以选择性地拉取代码修改。merge用于将两个分支合并成一个新分支。它会把整个分支上的所有修改都合并过来。具体区别:cherry-pick通常用于将bug修复从发布分支......
  • 01-螺旋矩阵(力扣题号59
    我的想法:两重循环,控制换行,打印对应递增数字问题:只能打印出第一行,虽然可以换行但是打印的数字不对正确思路:创建二维矩阵;给二维矩阵赋值;打印二维矩阵代码//题目:/**学习到:*-------写代码遇到的问题*1.vector容器初始化:*2.函数返回类型的确定:该函数(generateMatr......
  • 金融量化项目案例 -- 股票分析
    博客地址:https://www.cnblogs.com/zylyehuo/股票分析使用tushare包获取某股票的历史行情数据。输出该股票所有收盘比开盘上涨3%以上的日期。输出该股票所有开盘比前日收盘跌幅超过2%的日期。tushare财经数据接口包!pipinstall-ihttps://pypi.tuna.tsinghua.edu.cn......
  • kube-ovn vpc没有cidr
    kube-ovn中vpc没有cidr,不像传统vpc那样有大网段,关联的subnetcidr可以任意。kind:VpcapiVersion:kubeovn.io/v1metadata:name:vpc0---kind:SubnetapiVersion:kubeovn.io/v1metadata:name:s1spec:vpc:vpc0cidrBlock:10.0.1.0/24protocol:IPv4---......
  • 苍穹外卖-第八章来单提醒
    1.SpringTask1.1介绍SpringTask是Spring框架提供的任务调度工具,可以按照约定的时间自动执行某个代码逻辑。定位:定时任务框架作用:定时自动执行某段Java代码为什么要在Java程序中使用SpringTask?应用场景:信用卡每月还款提醒银行贷款每月还款提醒火车票售票系统处理未......
  • 苍穹外卖-第六章购物车
    1.缓存菜品1.1问题说明用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大。结果:系统响应慢、用户体验差1.2实现思路通过Redis来缓存菜品数据,减少数据库查询操作。缓存逻辑分析:每个分类下的菜品保存一份缓存数据数据库中......
  • Go每日一库之166:go-version(语义化版本)
    今天给大家推荐的是一个版本比较工具。该工具基于语义化标准的版本号进行比较、约束以及校验。以下是go-version的基本情况:安装通过goget进行安装:gogetgithub.com/hashicorp/go-version解析和比较版本号v1,err:=version.NewVersion("1.2")给版本号增加约束并校验v1......
  • Go每日一库之165:go-callvis(可视化调用链)
    本文介绍一款工具go-callvis,它能够将Go代码的调用关系可视化出来,并提供了可交互式的web服务。go-callvis使用依赖Go1.17+Graphviz(可选,当工具指定了-graphviz时需要)工具安装goget-ugithub.com/ofabry/go-callvis#orgitclonehttps://github.com/ofabry/......
  • Go每日一库之155:go-spew(输出 Go 数据结构)
    对于应用的调试,我们经常会使用fmt.Println来输出关键变量的数据。或者使用log库,将数据以log的形式输出。对于基础数据类型,上面两种方法都可以比较方便地满足需求。对于一些结构体类型数据,通常我们可以先将其序列化后再输出。如果结构体中包含不可序列化的字段,比如func类型......
  • 苍穹外卖-第七章订单支付
    1.导入地址簿功能代码1.1需求分析和设计1.1.1产品原型地址簿,指的是消费者用户的地址信息,用户登录成功后可以维护自己的地址信息。同一个用户可以有多个地址信息,但是只能有一个默认地址。对于地址簿管理,我们需要实现以下几个功能:查询地址列表新增地址修改地址删除地址......