【Java难点】多线程-高级

悲观锁和乐观锁

悲观锁

synchronized关键字和Lock的实现类都是悲观锁。

它很悲观,认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会一不做二不休的先加锁,确保数据不会被别的线程修改。

适合写操作多的场景,先加锁可以保证写操作时数据正确。

实例:

image-20240428003636339

乐观锁

它很乐观,认为自己在使用数据时不会有别的线程修改数据或资源,所以不会添加锁。

在Java中是通过使用无锁编程来实现,只是在更新数据的时候去判断,之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果这个数据己经被其它线程更新,则根据不同的实现方式执行不同的操作,比如放弃修改、重试抢锁等等。

乐观锁的实现方式:

  • 采用Version版本号机制

  • 采用CAS算法实现

    实例:

image-20240428003707495

锁案例演示

案例1
import java.util.concurrent.TimeUnit;public class JUC04 {public static void main(String[] args) {Phone phone = new Phone();new Thread(()->{phone.sendEmail();},"a").start();new Thread(()->{phone.sendSMS();},"b").start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}
}/*** 资源类*/
class Phone{public synchronized void sendEmail(){System.out.println("--------sendEmail");}public synchronized void sendSMS(){System.out.println("--------sendSMS");}
}

请问先打印邮件还是短信?

image-20240428005339903

案例2
import java.util.concurrent.TimeUnit;public class JUC04 {public static void main(String[] args) {Phone phone = new Phone();new Thread(()->{phone.sendEmail();},"a").start();new Thread(()->{phone.sendSMS();},"b").start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}
}/*** 资源类*/
class Phone{public synchronized void sendEmail(){try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("--------sendEmail");}public synchronized void sendSMS(){System.out.println("--------sendSMS");}
}

请问先打印邮件还是短信?

image-20240428005646180

案例3
import java.util.concurrent.TimeUnit;public class JUC04 {public static void main(String[] args) {Phone phone = new Phone();new Thread(()->{phone.sendEmail();},"a").start();new Thread(()->{phone.hello();},"b").start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}
}/*** 资源类*/
class Phone{public synchronized void sendEmail(){try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("--------sendEmail");}public void hello(){System.out.println("--------hello");}
}

请问先打印邮件还是hello?

image-20240428010005046

案例4
import java.util.concurrent.TimeUnit;public class JUC04 {public static void main(String[] args) {Phone phone = new Phone();Phone phone2 = new Phone();new Thread(()->{phone.sendEmail();},"a").start();new Thread(()->{phone2.sendSMS();},"b").start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}
}/*** 资源类*/
class Phone{public synchronized void sendEmail(){try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("--------sendEmail");}public synchronized void sendSMS(){System.out.println("--------sendSMS");}
}

有两部手机,请问先打印邮件还是短信?

image-20240428010211612

案例5
import java.util.concurrent.TimeUnit;public class JUC04 {public static void main(String[] args) {Phone phone = new Phone();new Thread(()->{phone.sendEmail();},"a").start();new Thread(()->{phone.sendSMS();},"b").start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}
}/*** 资源类*/
class Phone{public static synchronized void sendEmail(){try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("--------sendEmail");}public static synchronized void sendSMS(){System.out.println("--------sendSMS");}
}

有两个静态同步方法,有一部手机,请问先打印邮件还是短信?

image-20240428010526197

案例6
import java.util.concurrent.TimeUnit;public class JUC04 {public static void main(String[] args) {Phone phone = new Phone();Phone phone2 = new Phone();new Thread(()->{phone.sendEmail();},"a").start();new Thread(()->{phone2.sendSMS();},"b").start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}
}/*** 资源类*/
class Phone{public static synchronized void sendEmail(){try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("--------sendEmail");}public static synchronized void sendSMS(){System.out.println("--------sendSMS");}
}

有两个静态同步方法,有两部手机,请问先打印邮件还是短信?

image-20240428010724241

案例7
 import java.util.concurrent.TimeUnit;public class JUC04 {public static void main(String[] args) {Phone phone = new Phone();new Thread(()->{phone.sendEmail();},"a").start();new Thread(()->{phone.sendSMS();},"b").start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}
}/*** 资源类*/
class Phone{public static synchronized void sendEmail(){try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}  System.out.println("--------sendEmail");}public static void sendSMS(){System.out.println("--------sendSMS");}
}

有一个静态同步方法,一个普通静态方法,有一部手机,请问先打印邮件还是短信?

image-20240428011114157

案例8
import java.util.concurrent.TimeUnit;public class JUC04 {public static void main(String[] args) {Phone phone = new Phone();Phone phone2 = new Phone();new Thread(()->{phone.sendEmail();},"a").start();new Thread(()->{phone2.sendSMS();},"b").start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}
}/*** 资源类*/
class Phone{public static synchronized void sendEmail(){try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("--------sendEmail");}public static void sendSMS(){System.out.println("--------sendSMS");}
}

有一个静态同步方法,一个普通静态方法,有两部手机,请问先打印邮件还是短信?

image-20240428011251980

笔记总结

案例1和案例2

一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一的一个线程去访问这些synchronized方法,锁的是当前对象this,被锁定后,其它的线程都不能进入到当前对象的其它的 synchronized方法。

案例3和案例4

普通方法和同步锁无关,换成两个对象后,不是同一把锁了

案例5和案例6

对于普通同步方法,锁的是当前实例对象,通常指this,具体的一部手机,所有的普通同步方法用的都是同一把锁->实例对象本身;

对于静态同步方法,锁的是当前类的class 对象,如Phone.class;

对于同步代码块,锁的是 synchronized 扩号内的对象。

案例7和案例8

当一个线程试图访问同步代码块时,它首先必须得到锁,正常退出或抛出异常时必须释放锁。

所有的普通同步方法用的都是同一把锁-实例对象本身,就是new出来的具体实例对象本身,本类this
也就是说如果一个实例对象的普通同步方法获取锁后,该实例对象的其他普通同步方法必须等待获取锁的方法释放锁后才能再次获取锁。

所有的静态同步方法用的也是同一把锁-类对象(类名.class)本身,具体实例对象this 和类对象本身,这两把锁是两个不同的对象,所以静态同步方法与普通同步方法之间是不会有竞态条件的,但是一但一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁。

synchronized字节码分析

synchronized代码块
public class JUC05 {Object o=new Object();public void m1(){synchronized (o){System.out.println("----hello synchronized code block");}}public static void main(String[] args) {}
}

运行main方法,会在src的同级目录下生成一个target目录,进入target下的classes目录,找到JUC05.class,右键->打开于->终端,在终端中输入javap -c JUC05 ,对 JUC05.class进行反编译,得到如下信息:

image-20240428230628261

synchronized代码块使用的是monitorentermonitorexit指令来持有锁和释放锁。

问: 一定是1个monitorenter对应2个monitorexit吗?

答: 一般情况下,1个monitorenter对应2个monitorexit,但是也存在极端的情况,1个monitorenter对应1个monitorexit

image-20240428230221143

synchronized同步方法

public class JUC05 {public synchronized void m2(){System.out.println("----hello synchronized m2");}public static void main(String[] args) {}
}

运行main方法,会在src的同级目录下生成一个target目录,进入target下的classes目录,找到JUC05.class,右键->打开于->终端,在终端中输入javap -v JUC05 ,对 JUC05.class进行反编译,得到如下信息:

image-20240428231242956

调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置。如果设置了,执行线程会先持有montor锁, 然后再执行方法,最后在方法完成(无论是正常完成还是非正常完成)时释放monitor

synchronized静态同步方法

public class JUC05 {public synchronized void m2(){System.out.println("----hello synchronized m2");}public static synchronized void m3(){System.out.println("----hello static synchronized m3");}public static void main(String[] args) {}
}

运行main方法,会在src的同级目录下生成一个target目录,进入target下的classes目录,找到JUC05.class,右键->打开于->终端,在终端中输入javap -v JUC05 ,对 JUC05.class进行反编译,得到如下信息:

image-20240428231557432

ACC_STATIC, ACC_SYNCHRONIZED访问标识来区分该方法是否静态同步方法

synchronized底层原语分析

问: 为什么任何一个对象都可以成为一个锁?

答:

公平锁和非公平锁

公平锁

image-20240428235631627

image-20240429002250063

非公平锁

image-20240428235654107

image-20240429002237433

: 为什么会有公平锁和非公平锁的设计?为什么默认非公平?

  1. 恢复挂起的线程到真正锁的获取还是有时间差的,从开发人员来看这个时间微乎其微,但是从CPU的角度来看,这个时间差存在的还是很明显的。所以非公平锁能更充分的利用CPU 的时间片,尽量减少 CPU 空闲状态时间。

  2. 使用多线程很重要的考量点是线程切换的开销,当采用非公平锁时,当1个线程请求锁获取同步状态,然后释放同步状态,所以刚释放锁的线程在此刻再次获取同步状态的概率就变得非常大,所以就减少了线程的开销。

问: 什么时候用非公平锁,什么时候用公平锁?

:如果为了更高的吞吐量,很显然非公平锁是比较合适的,因为节省很多线程切换时间,吞吐量自然就上去了;否则那就用公平锁,大家公平使用。

可重入锁(递归锁)

定义

可:可以;重:再次;入:进入;锁:同步锁。

image-20240429220921948

image-20240429001200863

重入锁的种类

隐式锁

synchronized关键字使用的锁就是隐式锁,默认是可重入锁。

  • 同步块实例
public class JUC05 {public static void main(String[] args) {final Object object=new Object();new Thread(()->{synchronized (object){System.out.println(Thread.currentThread().getName()+"\t --------外层调用");synchronized (object){System.out.println(Thread.currentThread().getName()+"\t --------中层调用");synchronized (object){System.out.println(Thread.currentThread().getName()+"\t --------内层调用");}}}},"t1").start();}
}

image-20240429221547347

  • 同步方法实例
public class JUC05 {public synchronized void m1(){System.out.println(Thread.currentThread().getName() + "\t --------come in");m2();System.out.println(Thread.currentThread().getName() + "\t --------end");}public synchronized void m2(){System.out.println(Thread.currentThread().getName() + "\t --------come in");m3();}public synchronized void m3(){System.out.println(Thread.currentThread().getName() + "\t --------come in");}public static void main(String[] args) {JUC05 juc05 = new JUC05();new Thread(() -> {juc05.m1();}, "t1").start();}
}

image-20240429222226712

synchronized的重入的实现原理:

image-20240429223205523

显示锁

Lock就是显示锁,需要手动上锁和释放锁,且Lock是可重入锁。如ReentrantLock。

import java.util.concurrent.locks.ReentrantLock;public class JUC05 {public static void main(String[] args) {ReentrantLock lock = new ReentrantLock();new Thread(()->{lock.lock();try{System.out.println(Thread.currentThread().getName() + "\t --------come in外层调用");lock.lock();try{System.out.println(Thread.currentThread().getName() + "\t --------come in内层调用");}finally {lock.unlock();}}finally {lock.unlock();}},"t1").start();}
}

image-20240429223806232

死锁及排查

死锁实例
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;public class JUC05 {public static void main(String[] args) {final Object objectA=new Object();final Object objectB=new Object();new Thread(()->{synchronized (objectA){System.out.println(Thread.currentThread().getName()+"\t 自己持有A锁,希望获得B锁");try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}synchronized (objectB){System.out.println(Thread.currentThread().getName()+"\t 成功获得B锁");}}},"A").start();new Thread(()->{synchronized (objectB){System.out.println(Thread.currentThread().getName()+"\t 自己持有B锁,希望获得A锁");try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}synchronized (objectA){System.out.println(Thread.currentThread().getName()+"\t 成功获得A锁");}}},"B").start();}
}

image-20240429225257004

排查死锁
  • 命令行的方式
  1. jps -l 列出JVM当前进程的信息

image-20240429225830293

  1. jstack 51282列出JVM中进程号为51282的栈信息

image-20240429230138475

  • 图像化界面的方式
  1. 命令行输入jconsole

image-20240429230323586

image-20240429230355996

image-20240429230547796

线程中断机制

什么是中断机制?

image-20240506224240131

中断的三大方法
  • public void interrupt()

实例方法,仅仅是设置线程的中断状态为true,发起协商,而不会立刻停止线程。

源码:

image-20240507222729949

image-20240507222745976

注意:

当对一个线程,调用 interrupt() 时:

  1. 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而己。被设置中断标志的线程将继续正常运行,不受影响。所以,interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行。
  2. 如果线程处于被阻塞状态(例如处于sleep, wait, join等状态),在别的线程中调用当前线程对象的interrupt()方法,那么线程将立即退出被阻塞状态,并且该线程的中断状态将被清除(置为false),并且抛出一个InterruptedException异常,需要在异常处理中再次调用interrupt()方法,防止发生程序不能中断的现象。
  3. 当线程运行结束后,不管 该线程之前的中断标志是什么,调用isInterrupted()方法,都会返回false
  • public boolean isInterrupted()

实例方法,Thread.isInterrupted()判断当前线程是否被中断(通过检查中断标志位)

源码:

image-20240507223636212

  • public static boolean interrupted()

静态方法,判断线程是否被中断并清除当前中断状态。这个方法做了两件事:

  1. 返回当前线程的中断状态,测试当前线程是否已被中断;
  2. 将当前线程的中断状态清零并重新设为false,清除线程的中断状态。如果连续两次调用此方法,则第二次调用将返回false,因为连续调用两次的结果可能不一样。

源码对比:

image-20240507232620407

如何中断一个运行中的线程?
  1. 通过一个volatile变量实现
import java.util.concurrent.TimeUnit;public class JUC06 {static volatile boolean isStop=false;public static void main(String[] args) {new Thread(()->{while(true){if(isStop){System.out.println(Thread.currentThread().getName()+"\t isStop被修改为true,程序停止");break;}System.out.println("----------hello");}},"t1").start();try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}new Thread(()->{isStop=true;},"t2").start();}
}
  1. 通过AtomicBoolean实现
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;public class JUC06 {static AtomicBoolean atomicBoolean = new AtomicBoolean(false);public static void main(String[] args) {new Thread(()->{while(true){if(atomicBoolean.get()){System.out.println(Thread.currentThread().getName()+"\t atomicBoolean被修改为true,程序停止");break;}System.out.println("----------hello atomicBoolean");}},"t1").start();try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}new Thread(()->{atomicBoolean.set(true);},"t2").start();}
}
  1. 通过Thread类自带的终端api实例方法实现
import java.util.concurrent.TimeUnit;public class JUC06 {public static void main(String[] args) {Thread t1 = new Thread(() -> {while (true) {if (Thread.currentThread().isInterrupted()) {System.out.println(Thread.currentThread().getName() + "\t 中断标志位被修改为true,程序停止");break;}System.out.println("----------hello interrupt api");}}, "t1");t1.start();try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}//t2同t1发出协商,将t1的中断标志位设为true,希塑t1停下来new Thread(()->{t1.interrupt();},"t2").start();}
} 

LockSupport

定义

image-20240508224429837

常用方法
  • public static void unpark(Thread thread)

会给thread线程发放许可证permit,会自动唤醒park阻塞的线程,即之前阻塞中的LockSupport.park()方法会立即取下阻塞。

  • public static void park()

如果当前线程没有许可证permit,则调用park方法会使该线程阻塞,直到别的线程给当前线程发放许可证permit,park方法才会被唤醒。park方法会消耗一张许可证

线程阻塞和唤醒的方式
  1. 使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程
import java.util.concurrent.TimeUnit;public class JUC07 {public static void main(String[] args) {Object objectLock = new Object();new Thread(()->{synchronized (objectLock){System.out.println(Thread.currentThread().getName()+"\t -----come in");try {objectLock.wait(); //wait方法会释放锁objectLock} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"\t -----被唤醒了");}},"t1").start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}new Thread(()->{synchronized (objectLock){objectLock.notify();System.out.println(Thread.currentThread().getName()+"\t -----发出通知");}},"t2").start();}
}

image-20240508214252890

注意:

  • wait方法和notify方法必须在synchronized中调用,否则会报错:

image-20240508214236494

  • 将notify方法放在wait方法前面,将无法唤醒
  1. 使用JUC包中Condition的await()方法让线程等待,使用signal()方法唤醒线程
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class JUC07 {public static void main(String[] args) {Lock lock = new ReentrantLock();Condition condition = lock.newCondition();new Thread(()->{lock.lock();try {System.out.println(Thread.currentThread().getName()+"\t -----come in");condition.await();//await方法会释放锁lockSystem.out.println(Thread.currentThread().getName()+"\t -----被唤醒");} catch (InterruptedException e) {e.printStackTrace();}finally {lock.unlock();}},"t1").start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}new Thread(()->{lock.lock();try {condition.signal();System.out.println(Thread.currentThread().getName()+"\t -----发出通知");}finally {lock.unlock();}},"t2").start();}
}

image-20240508215558578

注意:

  • await方法和signal方法必须在持有锁后调用,否则会报错
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class JUC07 {public static void main(String[] args) {Lock lock = new ReentrantLock();Condition condition = lock.newCondition();new Thread(()->{
//            lock.lock();try {System.out.println(Thread.currentThread().getName()+"\t -----come in");condition.await();System.out.println(Thread.currentThread().getName()+"\t -----被唤醒");} catch (InterruptedException e) {e.printStackTrace();}finally {
//                lock.unlock();}},"t1").start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}new Thread(()->{
//            lock.lock();try {condition.signal();System.out.println(Thread.currentThread().getName()+"\t -----发出通知");}finally {
//                lock.unlock();}},"t2").start();}
}

image-20240508215443707

  • 将signal方法放在await方法前面,将无法唤醒

image-20240508215909946

总结

上述两种方式存在如下限制,

  • wait和notify、await和signal必须要在线程获得锁后调用,即必须在锁块(synchronized或lock)中

  • 必须要先等待,后唤醒,线程才能够被唤醒。

所以引出了第三种方式(LockSupport类中的park等待和unpark唤醒)。

  1. LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程
  • LockSupport类阻塞和唤醒线程不需要在锁块(synchronized或lock)中:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;public class JUC07 {public static void main(String[] args) {Thread t1 = new Thread(()->{System.out.println(Thread.currentThread().getName()+"\t -----come in");LockSupport.park();System.out.println(Thread.currentThread().getName()+"\t -----被唤醒了");},"t1");t1.start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}new Thread(()->{LockSupport.unpark(t1);//为t1线程发放许可证System.out.println(Thread.currentThread().getName()+"\t -----发出通知");},"t2").start();}
}

image-20240508221738371

  • LockSupport类阻塞和唤醒线程不需要先阻塞再唤醒,可以提前唤醒(提前给线程发放许可证)
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;public class JUC07 {public static void main(String[] args) {Thread t1 = new Thread(()->{try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"\t -----come in"+System.currentTimeMillis());LockSupport.park();System.out.println(Thread.currentThread().getName()+"\t -----被唤醒了"+System.currentTimeMillis());},"t1");t1.start();new Thread(()->{LockSupport.unpark(t1); //为t1线程发放许可证System.out.println(Thread.currentThread().getName()+"\t -----发出通知");},"t2").start();}
}

image-20240508222827180

sleep方法3秒后醒来,执行park无效,没有阻塞效果,解释如下:先执行了unpark(t1)导致上面的park方法形同虚设无效,时间一样。类似高速公路的ETC,提前买好了通行证unpark,到闸机处直接抬起栏杆放行了,没有park拦截了。

注意

  • 一个线程最多拥有一张许可证
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;public class JUC07 {public static void main(String[] args) {Thread t1 = new Thread(()->{try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"\t -----come in"+System.currentTimeMillis());LockSupport.park(); //消费一张许可证LockSupport.park(); //只为t1线程发放了一张许可证,再次消费许可证时,会阻塞在这里System.out.println(Thread.currentThread().getName()+"\t -----被唤醒了"+System.currentTimeMillis());},"t1");t1.start();new Thread(()->{LockSupport.unpark(t1); //为t1线程发放许可证。t1的许可证数量为1LockSupport.unpark(t1); //再次为t1线程发放许可证,但是一个线程只允许拥有一张许可证,所以t1的许可证数量还是1System.out.println(Thread.currentThread().getName()+"\t -----发出通知");},"t2").start();}
}

image-20240508223920718

面试题
  • 为什么可以突破wait/notify的原有调用顺序?

因为unpark发放了一个凭证,之后再调用park方法,就可以名正言顺的消费凭证,故不会阻塞。先发放了凭证,后续可以畅通无阻。

  • 为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?

因为凭证的数量最多为1,连续调用两次unpark和调用一次unpark效果一样,只会增加一个凭证;而调用两次 park却需要消费两个凭证,证不够,不能放行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/3030819.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

sipeed 的 MaixCam UART操作

发现问题 根据sipeed MaixCam官方文档 使用MaixVision会报错。 正确的接线 1,usb转ttl的RX和TX与sipeed MaixCam官方赠送的usb转接头反向连接,GND互相连接。 2,再用一根tpyc-c为其供电。 连接WiFi路由器 MaixCam液晶屏输入WiFi名称和密…

【Nginx】如何在 Nginx 中阻止来自特定国家的 IP 地址访问

文章目录 前言一、准备工作二、查看 Nginx 服务器都拥有哪些模块2.1 先查看本地nginx是否有ngx_http_geoip2模块2.2 安装nginx并配置ngx_http_geoip2模块2.2.1下载所需版本的nginx到服务器2.2.2 先安装所需依赖2.2.3 解压文件2.2.4 下载ngx_http_geoip2模块2.2.5 编译安装nginx…

视频批量剪辑指南:一键合并视频并添加背景音乐,高效便捷

在数字化时代,视频剪辑已经成为了一项常见且重要的技能。无论是制作家庭影片、工作展示还是社交媒体内容,掌握高效的视频剪辑技巧都能极大地提升我们的工作效率和创作质量。本文将为您介绍云炫AI智剪中高效的视频批量剪辑方法,让您能够一键合…

(Java)心得:LeetCode——15.三数之和

一、原题 给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k ,同时还满足 nums[i] nums[j] nums[k] 0 。请你返回所有和为 0 且不重复的三元组。 注意:答案中不可以包含重复的三元组。…

Java集合框架之LinkedHashSet详解

哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云;欢迎大家常来逛逛 今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一…

QX---mini51单片机学习---(6)独立键盘

目录 1键盘简绍 2按键的工作原理 3键盘类型 4独立键盘与矩阵键盘的特点 5本节相关原理图 6按键特性 7实践 1键盘简绍 2按键的工作原理 内部使用轻触按键,常态按下按键触点才闭合 3键盘类型 编码键盘与非编码键盘 4独立键盘与矩阵键盘的特点 5本节相关原理…

Python框架Django入门教程

Django 是一个使用 Python 编程语言开发的、免费且开源的 Web 应用框架。它遵循 "DRY(Dont Repeat Yourself)" 原则,旨在简化创建功能丰富的、高效率的 Web 网站。Django 提供了模型-视图-控制器(MVC)架构的…

Ubuntu安装库 版本问题,错误E: Unable to correct problems, you have held broken packages.

一、问题描述: Ubuntu系统指令安装 : sudo apt install -y build-essential提示: Reading package lists... Done Building dependency tree... Done Reading state information... Done Some packages could not be installed. This may mean that y…

Prompt|Kimi高阶技巧,99%的人都不知道

大家好,我是无界生长。 今天分享一条咒语,轻松让Kimi帮你生成流程图,学会了的话,点赞收藏起来吧! 效果展示 我们演示一下让kimi帮忙绘制 关注微信公众号“无界生长”的流程图,最终效果图如下所示 效果还不…

Java线程池:当核心线程数为 0 时,任务来了的执行流程

先说结论&#xff1a;创建一个临时线程直接执行 ThreadPoolExecutor.excute() public void execute(Runnable command) {if (command null)throw new NullPointerException();int c ctl.get();if (workerCountOf(c) < corePoolSize) {if (addWorker(command, true)) retu…

滑动窗口篇: 长度最小子数组|无重复字符最长字串

目录 1、滑动窗口算法 1.1 核心概念 1.2 基本步骤 1.3 应用场景 1.4 优势 2. leetcode 209 长度最小子数组 暴力解题思路&#xff1a; 滑动窗口思路&#xff1a; 3、无重复字符的最长子串 暴力解题思路&#xff1a; 滑动窗口思路&#xff1a; 1、滑动窗口算法 滑动…

while 习题

while 结构 习题 1.计算1到100所有整数和 2.提示用户输入一个小于100的整数&#xff0c;并计算从1到该数之间所有整数的和 3.求从1到100所有整数的偶数和、奇数和 echo -e \n 可以实现换行 4.用户输入密码&#xff0c;脚本判断密码是否正确&#xff0c;正确密码为123456&am…

基于Laravel 10 + Vue(scui) + MySQL的快速开发的后台管理系统

​ 系统介绍 ​基于Laravel 10 Vue(scui) MySQL的快速开发的后台管理系统 版权申明 禁止将本产品用于含诈骗、赌博、色情、木马、病毒等违法违规业务使用。 代码仓库 gitee地址&#xff1a; 基础版本 内置模块 用户管理&#xff1a;用于维护管理系统的用户&#xff0c…

笔记---DFS,深度优先搜索

深度优先搜索乃是注重深度&#xff0c;会把一条路径优先全部搜完然后再去回溯&#xff0c;再去搜其他路径 连通性模型 与BFS中的Flood Fill相似 AcWing.1112.迷宫 一天Extense在森林里探险的时候不小心走入了一个迷宫&#xff0c;迷宫可以看成是由 n∗n 的格点组成&#xff…

Python GraphQL服务器实现库之tartiflette使用详解

概要 Tartiflette是一个为Python编写的GraphQL服务器实现,它建立在现代异步编程库如asyncio之上,提供了高性能的GraphQL执行环境。Tartiflette专注于提供最佳的开发者体验,支持最新的GraphQL特性。 安装 安装Tartiflette相对简单,但需要依赖于一些系统级的库。 首先,需…

css定位+精灵图

一、定位 在CSS中&#xff0c;定位&#xff08;Positioning&#xff09;是一种布局技术&#xff0c;用于控制HTML元素在页面上的确切位置。CSS提供了几种不同的定位方案&#xff0c;每种方案都有其特定的用途和行为。以下是CSS中几种主要的定位方法&#xff1a; 静态定位&…

龙芯LA架构相关的存储管理

&#xff08;LoongArch-P92&#xff09; 目录 1.1 物理地址空间 1.2 虚拟地址空间 1.3 LA64架构下的虚拟地址缩减模式 1.4 存储访问类型 1.5 页表映射存储管理 1.5.1 TLB组织结构 1.5.2 基于TLB的虚实地址转换过程 1.5.3 TLB的软件管理 &#xff08;1&#xff09;…

开源高性能的分布式时序数据库:Lindb

Lindb&#xff1a;为大数据时代量身打造的高性能时序数据库&#xff0c;让海量数据存储与实时分析触手可及。- 精选真开源&#xff0c;释放新价值。 概览 Lindb 是一款开源的分布式时序数据库&#xff0c;它以其高性能和可伸缩性在海量数据存储及快速查询计算方面展现出独特的…

9.多数元素

文章目录 题目简介题目解答解法一&#xff1a;排序代码&#xff1a;复杂度分析&#xff1a; 解法二&#xff1a;摩尔投票法代码&#xff1a;复杂度分析&#xff1a; 解法三&#xff1a;哈希表代码复杂度分析&#xff1a; 题目链接 大家好&#xff0c;我是晓星航。今天为大家带来…

#兼职副业赚钱吗?# 宝妈与上班族在水牛社的财富探索

在这个繁忙的都市节奏中&#xff0c;宝妈与上班族都面临着平衡家庭与经济的挑战。那么&#xff0c;兼职副业真的能为他们带来额外的收入吗&#xff1f;接下来&#xff0c;让我们通过两个实例&#xff0c;揭示宝妈和上班族是如何在水牛社找到兼职副业赚钱的契机的。 ✨ 宝妈的故…