• 技术文章 >Java >Java基础

    Java介绍Lock锁与生产者消费者问题

    coldplay.xixicoldplay.xixi2021-02-09 17:43:21转载1417

    免费学习推荐:java基础教程

    Lock锁与生产者消费者问题

    传统Synchronized锁

    实现一个基本的售票例子:

    /*
    真正的多线程开发,公司中的开发,降低耦合性
    线程就是一个单独的资源类,没有任何附属的操作
    1.属性,方法
     * */public class SaleTicketDemo1 {
        public static void main(String[] args) {
            //并发,多个线程操作同一个资源类,把资源类丢入线程
            Ticket ticket=new Ticket();
            //Runnable借口是一个FunationalInterface函数式接口,接口可以new,jdk1.8以后,lamda表达式()->{代码}
            new Thread(()->{
                for(int i=0;i<60;i++){
                    ticket.sale();
                }
            },"A").start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for(int i=0;i<60;i++){
                        ticket.sale();
                    }
                }
            },"B").start();
            new Thread(()->{
                for(int i=0;i<60;i++){
                    ticket.sale();
                }
            },"C").start();
        }}//资源类 OOPclass Ticket{
        //属性,方法
        private int number=50;
        //卖票的方式
        //synchronized本质:队列,所
        public synchronized void sale(){
            if(number>0){
                System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,剩余"+number);
            }
        }}

    注意,这里面用到了lambda表达式,lambda表达式详细描述见Java基础-Lambda表达式

    在这里插入图片描述
    这是使用传统的synchronized实现并发,synchronized的本质就是队列,锁。就好比食堂排队。如果没有排队,就会很乱。只有给一个人服务完成了,另一个人才能接收到服务。

    Lock锁

    之前已经说道,JVM提供了synchronized关键字来实现对变量的同步访问以及用wait和notify来实现线程间通信。在jdk1.5以后,JAVA提供了Lock类来实现和synchronized一样的功能,并且还提供了Condition来显示线程间通信。
    Lock类是Java类来提供的功能,丰富的api使得Lock类的同步功能比synchronized的同步更强大。
    在java.util. Concurrent包中,里面有3个接口,Condition,lock(标准锁)。ReadWriteLock锁(读写锁)
    Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作。 它们允许更灵活的结构化,可能具有完全不同的属性,并且可以支持多个相关联的对象Condition。

    Lock l = ...; l.lock(); try { // access the resource protected by this lock } finally { l.unlock(); }

    lock()表示加锁,unlock()表示解锁
    JDK官方文档中解释
    在这里插入图片描述
    所有已知实现类:
    ReentrantLock可重入锁
    ReentrantReadWriteLock.ReadLock 读锁
    ReentrantReadWriteLock.writeLock写锁

    先说ReentrantLock实现类:
    ReentrantLock底层源码构造函数
    在这里插入图片描述
    公平锁:十分公平,可以先来后到。但是问题如果一个3s和一个3h的进程到达,3h先,那么3s等3h,实际上也不利。
    非公平锁:十分不公平,可以插队(默认)
    之后,我们会具体解释。

    怎么用,用之前加锁,用之后解锁

    //lock锁三部曲
    //1.new ReentranLock();构造
    //2.Lock.lock();加锁
    //3.finally();解锁

    public class SaleTicketDemo2 {
    	public static void main(String[] args) {
    		//并发,多个线程操作同一个资源类,把资源类丢入线程
    		Ticket ticket=new Ticket();
    		//Runnable借口是一个FunationalInterface函数式接口,接口可以new,jdk1.8以后,lamda表达式()->{代码}
    		new Thread(()->{for(int i=0;i<60;i++)ticket.sale();},"A").start();
    		new Thread(()->{for(int i=0;i<60;i++)ticket.sale();},"B").start();
    		new Thread(()->{for(int i=0;i<60;i++)ticket.sale();},"C").start();
    	}}//资源类 OOP//lock锁三部曲//1.new ReentranLock();//2.Lock.lock();加锁//3.finally();解锁class Ticket2{
    	//属性,方法
    	private int number=50;
    	//卖票的方式
    	//synchronized本质:队列,所
    	Lock lock=new ReentrantLock();
    	
    	public void sale(){
    		lock.lock();
    		try {
    			//业务代码
    			if(number>0){
    				System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,剩余"+number);
    			}
    		} catch (Exception e) {
    			// TODO: handle exception
    		}finally{
    			lock.unlock();
    		}	
    	}}

    Synchronized和lock锁的区别

    1.synchronized是内置的java关键字,lock是一个Java类
    2.synchronized无法判断获取锁的状态,lock可以判断是否获取到了锁
    3.synchronized会自动释放锁(a–),lock必须要手动释放锁!如果不释放锁,会导致死锁
    4.Synchronized线程1(获得锁,阻塞),线程2(等待,傻傻的等)
    lock.tryLock()尝试获取锁,不一定会一直等待下去
    5.Synchronized可重入锁,不可以中断的,非公平锁。Lock,可重入锁,可以判断锁,公平与非公平可以自己设置(可以自己设置)
    6.synchronized适合少量的代码同步问题,lock锁适合锁大量的同步代码
    synchornized锁对象和同步代码块方法

    传统的生产者和消费者问题

    传统的生产者和消费者是基于Object类的wait、notify方法和synchronized关键字来实现的。
    在面试的时候,手写生产者消费者代码是很常见的事情。
    面试笔试经典问题:
    单例模式+排序算法+生产者消费者+死锁
    生产者消费者问题synchronized版

    线程之间的通信问题:生产者和消费者问题 等待唤醒,通知唤醒
    线程交替执行 A B 操作同一个变量number=0
    A num+1
    B num-1

    注意:加锁的方法中,执行的思路是判断等待+业务+通知

    package testConcurrent;/*
    线程之间的通信问题:生产者和消费者问题    等待唤醒,通知唤醒
    线程交替执行 A B 操作同一个变量number=0
    A num+1
    B num-1
    
     * */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 (Exception e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				}
    			}
    		},"A").start();
    		new Thread(()->{
    			for(int i=0;i<10;i++){
    				try {
    					data.decrement();
    				} catch (Exception e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				}
    			}
    		},"B").start();	
    	}}//判断等待+业务+通知class Data{		//数字。资源类
    	private int number=0;
    	
    	//+1。多线程的情况下一定要加锁。
    	public synchronized void increment() throws InterruptedException{
    		//判断是否需要等待,如果不需要,就需要干活进行业务操作
    		if(number!=0){			//等于1的时候,需要等待,缓冲区只有1个空位置
    			//等待操作
    			this.wait();
    		}
    		number++;		//进行业务操作
    		System.out.println(Thread.currentThread().getName()+"=>"+number);
    		//通知其他线程,我+1完毕了
    		this.notify();
    	}
    	//-1
    	public synchronized void decrement() throws InterruptedException{
    		if(number==0){
    			//等待
    			this.wait();
    		}
    		number--;
    		System.out.println(Thread.currentThread().getName()+"=>"+number);
    		//通知其他线程,我-1完毕了
    		this.notify();
    	}}

    在这里插入图片描述
    如图,基本可以实现所要求的功能,但是这样还会出现问题,如果此时我再加上了两个线程,则

    		new Thread(()->{
    			for(int i=0;i<10;i++){
    				try {
    					data.increment();
    				} catch (Exception e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				}
    			}
    		},"C").start();
    		new Thread(()->{
    			for(int i=0;i<10;i++){
    				try {
    					data.decrement();
    				} catch (Exception e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				}
    			}
    		},"D").start();

    在这里插入图片描述
    这里结果中出现了2,输出结果出现了问题。为什么呢?
    为什么if判断会出现问题:
    if判断只判断一次。因为if判断了之后,就已经进入了代码的等待那一行,这时,在wait下的线程可能有多个,甚至包括生产者和消费者。有可能某个生产者执行完了之后,唤醒的是另一个生产者。

    在我们的官方文档中就给出了解释

    public final void wait(long timeout)
                    throws InterruptedException

    导致当前线程等待,直到另一个线程调用此对象的notify()方法或notifyAll()方法,或指定的时间已过。
    线程也可以唤醒,而不会被通知,中断或超时,即所谓的虚假唤醒 。 虽然这在实践中很少会发生,但应用程序必须通过测试应该使线程被唤醒的条件来防范,并且如果条件不满足则继续等待。 换句话说,等待应该总是出现在循环中,就像这样:

     synchronized (obj) {
             while (<condition does not hold>)
                 obj.wait(timeout);
             ... // Perform action appropriate to condition
         }

    注意点:防止虚假唤醒问题
    我们代码中用的是if判断,而应该用while判断

    package testConcurrent;/*
    线程之间的通信问题:生产者和消费者问题    等待唤醒,通知唤醒
    线程交替执行 A B 操作同一个变量number=0
    A num+1
    B num-1
    
     * */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 (Exception e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            },"A").start();
            new Thread(()->{
                for(int i=0;i<10;i++){
                    try {
                        data.decrement();
                    } catch (Exception e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            },"B").start();
            new Thread(()->{
                for(int i=0;i<10;i++){
                    try {
                        data.increment();
                    } catch (Exception e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            },"C").start();
            new Thread(()->{
                for(int i=0;i<10;i++){
                    try {
                        data.decrement();
                    } catch (Exception e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            },"D").start();
            
        }}//判断等待+业务+通知class Data{     //数字。资源类
        private int number=0;
        
        //+1
        public synchronized void increment() throws InterruptedException{
            //判断是否需要等待,如果不需要,就需要干活进行业务操作
            while(number!=0){           //等于1的时候,需要等待,缓冲区只有1个空位置
                //等待操作
                this.wait();
            }
            number++;       //进行业务操作
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            //通知其他线程,我+1完毕了
            this.notify();
        }
        //-1
        public synchronized void decrement() throws InterruptedException{
            while(number==0){
                //等待
                this.wait();
            }
            number--;
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            //通知其他线程,我-1完毕了
            this.notify();
        }}

    Lock版的生产者和消费者问题

    在synchronized版本中,我们使用了wait和notify来实现线程之间的同步
    在lock中,
    此时synchronized被lock替换了,那么wait和notify用什么来替换呢?
    我们在官方文档java.util.concurrent.locks 中,找到Lock类,然后在底部找到了
    Condition newCondition()
    返回一个新Condition绑定到该实例Lock实例。
    在等待条件之前,锁必须由当前线程保持。 呼叫Condition.await()将在等待之前将原子释放锁,并在等待返回之前重新获取锁。

    然后我们再来了解Condition类
    Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。
    一个Condition实例本质上绑定到一个锁。 要获得特定Condition实例的Condition实例,请使用其newCondition()方法。

    在这里插入图片描述
    我们可以看到,使用的时候new一个Condition对象。然后用await替代wait,signal替换notify
    代码实现
    //判断等待+业务+通知

    class Data2{		//数字。资源类
    	private int number=0;
    	Lock lock=new ReentrantLock();
    	Condition condition=lock.newCondition();
    	//+1
    	public void increment() throws InterruptedException{
    		try {
    			lock.lock();
    			//业务代码
    			while(number!=0){		
    				//等待
    				condition.await();
    			}
    			number++;		
    			System.out.println(Thread.currentThread().getName()+"=>"+number);
    			condition.signalAll();		//通知
    		} catch (Exception e) {
    			// TODO: handle exception
    		}finally{
    			lock.unlock();
    		}
    	}
    	//-1
    	public void decrement() throws InterruptedException{
    		try {
    			lock.lock();
    			//业务代码
    			while(number!=1){		
    				//等待
    				condition.await();
    			}
    			number--;		
    			System.out.println(Thread.currentThread().getName()+"=>"+number);
    			condition.signalAll();		//通知
    		} catch (Exception e) {
    			// TODO: handle exception
    		}finally{
    			lock.unlock();
    		}
    	}	}

    注意:主函数部分于最上面的代码一样。

    在这里插入图片描述
    这时候虽然说是正确的,但是它是一个随机分布的状态,现在我们希望它有序执行,即A执行完了执行B,B执行C,C完了执行D。即精准通知。

    Condition实现精准通知唤醒

    Condition实现精准的通知和唤醒
    我们构造三个线程,要求A执行完了执行B,B执行完了执行C,C执行完了执行D.
    代码思想:
    //加多个监视器,通过监视器来判断唤醒的是哪一个人
    //设置多个同步监视器,每个监视器监视一个线程
    //实例:生产线,下单->支付->交易->物流

    package testConcurrent;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;/*
    A执行完调用B,B执行完调用C,C执行完调用A
     * */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{    //资源类
        private Lock lock=new ReentrantLock();
        //加多个监视器,通过监视器来判断唤醒的是哪一个人
        //设置多个同步监视器,每个监视器监视一个线程
        //实例:生产线,下单->支付->交易->物流
        private Condition condition1=lock.newCondition();
        private Condition condition2=lock.newCondition();
        private Condition condition3=lock.newCondition();
        private int number=1;       //1A 2B 3C
        public void printA(){
            lock.lock();
            try {
                //业务,判断->执行->通知
                while(number!=1){
                    //等待
                    condition1.await();
                }
                System.out.println(Thread.currentThread().getName()+"=>AAAAAAA");
                //唤醒,唤醒指定的人,B
                number=2;           //精准唤醒
                condition2.signal();
                
            } catch (Exception e) {
                // TODO: handle exception
            }finally{
                lock.unlock();
            }
        }   
        public void printB(){
            lock.lock();
            try {
                //业务,判断->执行->通知
                while(number!=2){
                    //等待
                    condition2.await();
                }
                System.out.println(Thread.currentThread().getName()+"=>BBBBBBB");
                //唤醒,唤醒指定的人,C
                number=3;           //精准唤醒
                condition3.signal();
                
            } catch (Exception e) {
                // TODO: handle exception
            }finally{
                lock.unlock();
            }
        }
        public void printC(){
            lock.lock();
            try {
                //业务,判断->执行->通知
                while(number!=3){
                    //等待
                    condition3.await();
                }
                System.out.println(Thread.currentThread().getName()+"=>CCCCCCC");
                //唤醒,唤醒指定的人,A
                number=1;           //精准唤醒
                condition1.signal();
                
            } catch (Exception e) {
                // TODO: handle exception
            }finally{
                lock.unlock();
            }
        }}

    相关学习推荐:java基础

    以上就是Java介绍Lock锁与生产者消费者问题的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:CSDN,如有侵犯,请联系admin@php.cn删除
    专题推荐:Java 并发编程
    上一篇:java怎么样生成随机数 下一篇:自己动手写 PHP MVC 框架(40节精讲/巨细/新人进阶必看)

    相关文章推荐

    • java面试——高并发处理• 说说java的泛型机制是怎样的• Java并发编程,介绍常用的辅助类
    1/1

    PHP中文网