무료 학습 권장사항: java 기본 튜토리얼
잠금 잠금 및 생산자 소비자 문제
기존 동기화 잠금
기본 티켓팅 예 구현:
/* 真正的多线程开发,公司中的开发,降低耦合性 线程就是一个单独的资源类,没有任何附属的操作 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); } }}
이 람다 표현식은 다음에서 사용됩니다. 람다 표현식에 대한 자세한 설명은 Java 기본 - 람다 표현식
을 참조하세요. 이는 동시성을 달성하기 위해 전통적인 동기화를 사용하는 것입니다. 마치 카페테리아에 줄을 서 있는 것과 같습니다. 대기열이 없으면 혼란 스러울 것입니다. 한 사람에 대한 서비스가 완료되어야 다른 사람이 서비스를 받을 수 있습니다.
앞서 언급했듯이 JVM은 변수에 대한 동기 액세스를 달성하고 대기 및 알림을 사용하여 스레드 간 통신을 달성하기 위해 동기화된 키워드를 제공합니다. jdk1.5 이후 JAVA에서는 동기화와 동일한 기능을 구현하기 위해 Lock 클래스를 제공하고 스레드 간 통신을 표시하기 위한 Condition도 제공합니다.
Lock 클래스는 Java 클래스에서 제공하는 함수로, 풍부한 API로 인해 Lock 클래스의 동기화 기능이 동기화 동기화보다 강력해집니다.
java.util.Concurrent 패키지에는 조건과 잠금(표준 잠금)의 3가지 인터페이스가 있습니다. ReadWriteLock
Lock 구현은 동기화된 메서드 및 문을 사용하여 얻을 수 있는 것보다 더 넓은 범위의 잠금 작업을 제공합니다. 보다 유연한 구조화를 허용하고 완전히 다른 속성을 가질 수 있으며 여러 관련 개체 조건을 지원할 수 있습니다. Lock l = ...; l.lock(); try { // access the resource protected by this lock } finally { l.unlock(); }
JDK 공식 문서에 설명되어 있습니다.
알려진 모든 구현 클래스:
ReentrantLock ReentrantLock
ReentrantReadWriteLock.ReadLock 읽기 잠금 ReentrantReadWriteLock . writeLock 쓰기 잠금
먼저 ReentrantLock 구현 클래스에 대해 이야기해 보겠습니다.
공정한 잠금: 매우 공정하고 선착순입니다. 그런데 문제는 3s와 3h 프로세스가 도착하면 3h가 먼저 오고, 그 다음 3s가 3h를 기다린다는 점인데, 이는 실제로 좋지 않습니다. 불공정 잠금: 매우 불공평하므로 줄을 서도 됩니다(기본값)
자세한 내용은 나중에 설명하겠습니다.
//lock trilogy
//1.new ReentranLock() 구조체 //2.Lock.lock() lock
//3. finally(); Unlockpublic 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();
}
}}
1. 동기화는 내장된 Java 키워드이고 잠금은 Java 클래스입니다
2. 동기화는 잠금 상태를 확인할 수 없습니다. 잠금이 획득되었는지 판단하세요 3. 동기화는 자동으로 잠금을 해제합니다(a–). 잠금은 수동으로 해제해야 합니다! 잠금이 해제되지 않으면 교착 상태가 발생합니다
4. 동기화된 스레드 1(잠금 획득, 차단), 스레드 2(대기, 멍청하게 대기)
lock.tryLock()이 잠금을 획득하려고 시도하지만 기다리지 않을 수 있습니다. 영원히
5. 동기화된 재진입 잠금, 무정전, 불공정 잠금. 잠금, 재진입 잠금, 잠금 판단 가능, 공정/불공정 직접 설정 가능(직접 설정 가능)
6. 동기화는 적은 양의 코드 동기화 문제에 적합하고, 잠금 잠금은 대량의 코드 동기화에 적합 동기화 코드
동기화된 잠금 객체 및 동기화된 코드 블록 방법
전통적인 생산자와 소비자는 객체 클래스의 대기, 알림 메소드 및 동기화 키워드를 기반으로 구현됩니다.
인터뷰 중에는 생산자 코드와 소비자 코드를 직접 작성하는 것이 매우 일반적입니다. 인터뷰 필기 테스트의 고전 질문:
싱글턴 모드 + 정렬 알고리즘 + 생산자 소비자 + 교착 상태
생산자 소비자 문제의 동기화 버전
스레드 A B를 교대로 실행하고 동일한 변수 번호를 연산=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{
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에 잠금 및 생산자-소비자 문제가 발생함"><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()方法。
我们可以看到,使用的时候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{ 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에 잠금 및 생산자-소비자 문제가 발생함의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!