首頁 > Java > Java基礎 > Java介紹Lock鎖與生產者消費者問題

Java介紹Lock鎖與生產者消費者問題

coldplay.xixi
發布: 2021-02-09 17:43:21
轉載
2762 人瀏覽過

Java介紹Lock鎖與生產者消費者問題

免費學習推薦:java基礎教學

  • #Lock鎖定與生產者消費者問題
  • 傳統Synchronized鎖定
  • Lock鎖定
Synchronized與lock鎖定的差異

傳統的生產者和消費者問題

Lock版的生產者和消費者問題

Condition實現精確通知喚醒

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{
            for(int i=0;i0){
            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鎖定(讀寫鎖定)

Java介紹Lock鎖與生產者消費者問題Lock實作提供比使用synchronized方法和語句可以獲得的更廣泛的鎖定操作。它們允許更靈活的結構化,可能具有完全不同的屬性,並且可以支援多個相關聯的物件Condition。

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

lock()表示加鎖,unlock()表示解鎖
JDK官方文件中解釋

所有已知實作類別:

Java介紹Lock鎖與生產者消費者問題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{for(int i=0;i{for(int i=0;i0){
				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=0Java介紹Lock鎖與生產者消費者問題 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{
			for(int i=0;i"+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{
			for(int i=0;i<p><img src="https://img.php.cn/upload/article/000/000/052/4011c6e510a595c0afc39326e031cf33-4.png" alt="Java介紹Lock鎖與生產者消費者問題"><br> 这里结果中出现了2,输出结果出现了问题。为什么呢?<br> 为什么if判断会出现问题:<br> if判断只判断一次。因为if判断了之后,就已经进入了代码的等待那一行,这时,在wait下的线程可能有多个,甚至包括生产者和消费者。有可能某个生产者执行完了之后,唤醒的是另一个生产者。</p><p>在我们的官方文档中就给出了解释</p><pre class="brush:php;toolbar:false">public final void wait(long timeout)
                throws InterruptedException
登入後複製

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

 synchronized (obj) {
         while (<condition>)
             obj.wait(timeout);
         ... // Perform action appropriate to condition
     }</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{
            for(int i=0;i{
            for(int i=0;i{
            for(int i=0;i"+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()方法。

Java介紹Lock鎖與生產者消費者問題
我们可以看到,使用的时候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();
		}
	}	}
登入後複製

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

Java介紹Lock鎖與生產者消費者問題
这时候虽然说是正确的,但是它是一个随机分布的状态,现在我们希望它有序执行,即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{
            for(int i=0;i{
            for(int i=0;i支付->交易->物流
    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.net
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板