搜索
首页JavaJava基础Java介绍Lock锁与生产者消费者问题

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

Feb 09, 2021 pm 05:43 PM
java并发编程

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

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

Lock锁与生产者消费者问题

  • 传统Synchronized锁
  • Lock锁
  • Synchronized和lock锁的区别
  • 传统的生产者和消费者问题
  • Lock版的生产者和消费者问题
  • Condition实现精准通知唤醒

传统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删除

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
1 个月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
1 个月前By尊渡假赌尊渡假赌尊渡假赌
威尔R.E.P.O.有交叉游戏吗?
1 个月前By尊渡假赌尊渡假赌尊渡假赌

热工具

MinGW - 适用于 Windows 的极简 GNU

MinGW - 适用于 Windows 的极简 GNU

这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。

SublimeText3 英文版

SublimeText3 英文版

推荐:为Win版本,支持代码提示!

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

将Eclipse与SAP NetWeaver应用服务器集成。

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )专业的PHP集成开发工具