Java锁的基本用法

Java锁的基本用法

synchronized和lock

synchronized

synchronized是方法锁,可自动加锁和释放锁,因为其是方法级锁,也就是说将锁定整个方法的内容,所以对于业务逻辑较为复杂的方法,不建议用synchronized
需求:模拟三个人卖十张票,十张票代表不同的十个座位,正常情况下不能出现卖出两张一样的票

首先在没有加锁的情况下

在这里插入图片描述
测试结果
在这里插入图片描述

加锁的情况

直接在方法上加 synchronized
在这里插入图片描述
测试结果每张票都是唯一的,这里没有问题
在这里插入图片描述

Lock

需求:模拟三个人卖十张票,十张票代表不同的十个座位,正常情况下不能出现卖出两张一样的票

首先在没有加锁的情况下
package com.zhou.juc.sellingtickets;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @author DELL
 * @version 1.0
 * @Description
 * @date 2022/5/17 17:16
 */
public class LockTicket implements Runnable{
    private int number=10;
    //可重入锁
    //final ReentrantLock rLock=new ReentrantLock();
    //加锁的情况下
    public void sellingTickets(){
        //上锁
        //rLock.lock();
        if(number>0) {
            System.out.println(Thread.currentThread().getName()+"  卖第"+(number--)+ "张票  当前剩于:" + number);
            try {
                //因为cpu处理效率较快,为了三个线程都能访问到这个方法,所以在此休息一下
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //解锁,这是必须的
        //rLock.unlock();
    }

    @Override
    public void run() {
        for (int i=0;i<10;i++)
            sellingTickets();
    }
}
class LockSellTickets{
    public static void main(String[]args){
        LockTicket ticket=new LockTicket();
        //同时开三个线程进行卖票
        new Thread(ticket,"A").start();
        new Thread(ticket,"B").start();
        new Thread(ticket,"C").start();
    }
}

测试结果出现同一张票,这是不正常的在这里插入图片描述

加锁的情况下

非公平锁和公平锁
非公平锁就是谁拿到锁谁就用,这里会造成一种情况就是有可能一个线程会把所有的任务给做了,其它线程没事可做,但是这种方式效率高
公平锁就是尽可能的给各线程分配相同量的任务,因为需要进行公平处理,所以会牺牲一定效率

//参数为true为公平锁
//final ReentrantLock rLock=new ReentrantLock(true);
//默认无参为非公平锁
final ReentrantLock rLock=new ReentrantLock();
public class LockTicket implements Runnable{
    private int number=10;
    //参数为true为公平锁
    //final ReentrantLock rLock=new ReentrantLock(true);
    //默认无参为非公平锁
    final ReentrantLock rLock=new ReentrantLock();
    //加锁的情况下
    public void sellingTickets(){
        //上锁
        rLock.lock();
        try {

            if (number > 0) {                System.out.println(Thread.currentThread().getName() + "  卖第" + (number--)+ "张票  当前剩于:" + number);
                try {
                    //因为cpu处理效率较快,为了三个线程都能访问到这个方法,所以在此休息一下
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        finally {
            //解锁,这是必须的
            rLock.unlock();
        }
    }

    @Override
    public void run() {
        for (int i=0;i<10;i++)
            sellingTickets();
    }
}
class LockSellTickets{
    public static void main(String[]args){
        LockTicket ticket=new LockTicket();
        //同时开三个线程进行卖票
        new Thread(ticket,"A").start();
        new Thread(ticket,"B").start();
        new Thread(ticket,"C").start();
    }
}

与没有加锁的区别在于这里加了可重入锁,为了避免出现异常而没有释放锁情况(死锁),需要把解锁放到finally中
在这里插入图片描述

测试结果是正常的情况
在这里插入图片描述

线程的通信

synchronized 通过wait和notifyAll进行通信

需求:一个生产者生产一个商品,两个消费者进行消费,并且生产者只能等消费者消费完之后才能生产,消费者只能等生产者生产完后才能消费

package com.zhou.juc.ThreadCommunication;

/**
 * @author DELL
 * @version 1.0
 * @Description
 * @date 2022/5/17 16:17
 */
public class SynTicket /*implements Runnable*/{

    private int number=0;
    //生产一个商品
    public synchronized void incr() throws InterruptedException {
        //判断商品是否被消费,如果没有就等待(这里一定要用while,用if会出现虚假唤醒的情况)
        while (number!=0) {
        //在这里进行等待,如果有多个线程调用incr,在此有可能被其它调用incr的线程进行唤醒
        //因为已经进入了判断,所以此时number有可能已经大于0,所以上面必须用while,而不能用if
            this.wait();
        }
        //生产一个商品
        System.out.println(Thread.currentThread().getName()+"生产一个商品前:"+number+"  生产一个商品后:"+ (++number));
        //通知其它线程可以干活了,这里是指所有线程都可以执行自己的任务了
        this.notifyAll();
    }
    //消费一个商品
    public synchronized void decr() throws InterruptedException {
        //判断商品是否被生产,如果没有就等待(这里一定要用while,用if会出现虚假唤醒的情况)
        while (number!=1) {
        //在这里进行等待,这里有B、C两个线程调用,如果B在此等待,有可能将被C唤醒
        //因为已经进入了判断,所以此时number有可能已经小于0,所以上面必须用while,而不能用if
            this.wait();
        }
        //消费一个商品
        System.out.println(Thread.currentThread().getName()+"消费一个商品前:"+number+"  消费一个商品后:"+ (--number));
        //通知其它线程可以干活了,这里是指所有线程都可以执行自己的任务了
        this.notifyAll();
    }

    //第一种方法
    /*@Override
    public void run() {
        for (int i=0;i<10;i++) {
            try {
                if("A".equals(Thread.currentThread().getName()))
                    incr();
                else
                    decr();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }*/
}

class SellTickets{
    public static void main(String[]args){
        SynTicket ticket=new SynTicket();
        //同时开三个线程进行,A为生产者,B、C为消费者

        //第一种方法
        /*new Thread(ticket,"A").start();
        new Thread(ticket,"B").start();
        new Thread(ticket,"C").start();*/

        //第二种方法
        incrOrDecr("A",ticket);
        incrOrDecr("B",ticket);
        incrOrDecr("C",ticket);

    }

    private static void incrOrDecr(String name, SynTicket ticket){
        new Thread(()->{
            for (int i=0;i<10;i++) {
                try {
                    if("A".equals(name))
                    ticket.incr();
                    else
                        ticket.decr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },name).start();
    }
}

测试结果A生产一个,B、C才能消费一个
在这里插入图片描述

synchronized 需求唤醒问题

把while改成if
在这里插入图片描述

测试结果不符合生产一个消费一个的需求
在这里插入图片描述

使用Lock定向唤醒线程

需求:
1、A生产五件商品,B先消费三件,C再消费两件
2、只有等C消费完后,A再生产,整个流程循环五次

代码中用到了Condition接口,Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition的await()、signal()这种方式实现使线程间协作更加安全和高效。因此通常来说比较推荐使用Condition,阻塞队列实际上是使用了Condition来模拟线程间协作。

package com.zhou.juc.ThreadCommunication;

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

/**
 * @author DELL
 * @version 1.0
 * @Description
 * @date 2022/5/18 10:41
 */
public class LockDirectonalAwaken {
    //标志量1,默认A先执行
    int flag=1;
    //商品数量
    int number=0;
    //定义一把锁
    ReentrantLock rLock=new ReentrantLock();

    Condition conditionA=rLock.newCondition();
    Condition conditionB=rLock.newCondition();
    Condition conditionC=rLock.newCondition();


    public void doA() throws InterruptedException {
        rLock.lock();

        try{
            while(flag!=1){
                //A进行等待
                conditionA.await();
            }
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName()+"生产一个商品前:"+number+"  生产一个商品后:"+ (++number));
            }
            //标志量改成B的
            flag=2;
            //定向唤醒B
            conditionB.signal();

        }finally{
            rLock.unlock();
        }
    }

    public void doB() throws InterruptedException {
        rLock.lock();
        try{
            while(flag!=2){
                //B进行等待
                conditionB.await();
            }
            for (int i = 0; i < 3; i++) {
                System.out.println(Thread.currentThread().getName()+"消费一个商品前:"+number+"  消费一个商品后:"+ (--number));
            }
            //标志量改成C的
            flag=3;
            //定向唤醒C
            conditionC.signal();

        }finally{
            rLock.unlock();
        }
    }

    public void doC() throws InterruptedException {
        rLock.lock();
        try{
            while(flag!=3){
                //C进行等待
                conditionC.await();
            }
            for (int i = 0; i < 2; i++) {
                System.out.println(Thread.currentThread().getName()+"消费一个商品前:"+number+"  消费一个商品后:"+ (--number));
            }
            //标志量改成A的
            flag=1;
            //定向唤醒A
            conditionA.signal();
        }finally{
            rLock.unlock();
        }
    }
}

class DirectonalAwaken{
    public static void main(String[]args){
        LockDirectonalAwaken ldAwaken=new LockDirectonalAwaken();
        incrOrDecr("A",ldAwaken);
        incrOrDecr("B",ldAwaken);
        incrOrDecr("C",ldAwaken);
    }
    private static void incrOrDecr(String name, LockDirectonalAwaken ldAwaken){
        new Thread(()->{
            for (int i = 0; i < 5; i++) {

                try {
                    switch (name){
                        case "A":
                            ldAwaken.doA();
                            break;
                        case "B":
                            ldAwaken.doB();
                            break;
                        case "C":
                            ldAwaken.doC();
                            break;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        },name).start();
    }
}

测试结果 A+5 B-3 C-2 A+5 B-3 C-2 符合顺序执行的业务逻辑
在这里插入图片描述

  • 6
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值