Java锁的基本用法

2年前 (2022) 程序员胖胖胖虎阿
160 0 0

文章目录

  • Java锁的基本用法
    • synchronized和lock
      • synchronized
        • 首先在没有加锁的情况下
        • 加锁的情况
      • Lock
        • 首先在没有加锁的情况下
        • 加锁的情况下
    • 线程的通信
      • synchronized 通过wait和notifyAll进行通信
      • synchronized 需求唤醒问题
      • 使用Lock定向唤醒线程

Java锁的基本用法

synchronized和lock

synchronized

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

首先在没有加锁的情况下

Java锁的基本用法
测试结果
Java锁的基本用法

加锁的情况

直接在方法上加 synchronized
Java锁的基本用法
测试结果每张票都是唯一的,这里没有问题
Java锁的基本用法

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();
    }
}

测试结果出现同一张票,这是不正常的Java锁的基本用法

加锁的情况下

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

//参数为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中
Java锁的基本用法

测试结果是正常的情况
Java锁的基本用法

线程的通信

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才能消费一个
Java锁的基本用法

synchronized 需求唤醒问题

把while改成if
Java锁的基本用法

测试结果不符合生产一个消费一个的需求
Java锁的基本用法

使用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 符合顺序执行的业务逻辑
Java锁的基本用法

版权声明:程序员胖胖胖虎阿 发表于 2022年10月21日 上午1:16。
转载请注明:Java锁的基本用法 | 胖虎的工具箱-编程导航

相关文章

暂无评论

暂无评论...