【Java难点】多线程-高级

news2024/12/23 19:22:23

悲观锁和乐观锁

悲观锁

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方法会释放锁lock
                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-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的许可证数量为1
            LockSupport.unpark(t1); //再次为t1线程发放许可证,但是一个线程只允许拥有一张许可证,所以t1的许可证数量还是1
            System.out.println(Thread.currentThread().getName()+"\t -----发出通知");
        },"t2").start();
    }
}

image-20240508223920718

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

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

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

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

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

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

相关文章

无线收发模块家电控制实验

zkhengyang可申请加入数字音频系统研究开发交流答疑群(课题组) 当然可以先用固定电平发送,可以实现,0/1数据发送,接收。 可以使用51单片机来编码码,解码,或者任何MCU或者SOC,DSP,FPGA。 注意G…

【全开源】酷柚易汛ERP 源码部署/售后更新/上线维护

一款基于FastAdminThinkPHPLayui开发的ERP管理系统,帮助中小企业实现ERP管理规范化,此系统能为你解决五大方面的经营问题:1.采购管理 2.销售管理 3.仓库管理 4.资金管理 5.生产管理,适用于:服装鞋帽、化妆品、机械机电…

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

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

Python 全栈系列242 踩坑记录:租用算力机完成任务

说明 记一次用算力机分布式完成任务的坑。 内容 1 背景 很早的时候,做了一个实体识别模型。这个模型可以识别常见的PER、ORG、LOC和TIME几种类型实体。 后来,因为主要只用来做PER、ORG的识别,于是我根据业务数据,重新训练了模…

一个可以同时使用USB和WIFI传输文件到电脑的软件

双轨快传 结合USB2.0和WIFI6技术,通过1000Mbps网口实现每秒高达150MB的传输速率(理论上可达40MB/s通过USB和110MB/s通过WIFI)。 使用 模式 支持普通模式和Root模式,Root模式可访问~/Android/data/与/data/data/目录下的文件。 …

web安全之登录框渗透骚姿势,新思路

不管漏洞挖掘还是挖SRC,登录框都是重点关注对象,什么漏洞都有可能出现, 本篇文章做个总结,后面发现新思路后会继续更新 万能密码 or 弱口令 SQL注入 水平越权 垂直越权 逻辑漏洞 短信轰炸 邮箱轰炸 信息泄露 验证码DOS XSS万能密…

搭建本地yum仓库

步骤 找个地方存你的rpm包 #我创建了一个rpm文件夹存放我的rpm包 makdir -p /opt/repo/rpmcreaterepo 这个很重要,一定要安装 # 我的能连外网,所以直接yum安装,你的自己想办法 yum install createrepo -y创建repodata 安装了createrepo后…

【SRC-CPP-OpenCV】给图片更换背景色

文章目录 Part.I IntroductionPart.II Main_bodyChap.I 源码简析Chap.II 效果展示 Part.III 源码Reference Part.I Introduction 本文将介绍如何用 OpenCV 更换图片的背景色(附有完整代码)。 Part.II Main_body Chap.I 源码简析 配置部分&#xff1a…

Electron 报错:WinState is not a constructor

文章目录 问题分析 问题 在使用 electron-win-state 库时报错如下 代码如下: const WinState require(electron-win-state) const winState new WinState({ defaultWidth: 800,defaultHeight: 600,// other winState options, see below })const browserWindow…

区块链的跨链交互:从学校间交流看跨链技术

区块链是一种去中心化的分布式账本技术,它通过加密学和共识机制来确保数据的安全性和不可篡改性。每个区块链就像一所独立的学校,有自己的制度、学生和重点专业。它们各自运行,有时在同一领域展开不同的活动。随着区块链技术的不断发展&#…

Java入门基础学习笔记15——强制类型转换

大范围类型的变量是否可以赋值给小范围类型的变量呢? IDEA直接报错。直接报错,是提醒你有问题。但是我非常进行类型转换。 非要强行赋值呢? 强制类型转换,强行将类型范围大的变量,数据赋值给类型范围小的变量。 数据…

几个字符串函数的使用和模拟实现(1)

下面介绍几个常见的字符串函数的使用并且学会自己创建实现与它们一样的功能&#xff0c;借此来巩固与字符串、函数、指针相关方面的知识。 strlen函数&#xff08;头文件是<string.h>&#xff09; 由此可见&#xff1a; strlen函数的返回值是返回值是size_t&#xff0c;…

【回溯 状态压缩 深度优先】37. 解数独

本文涉及知识点 回溯 状态压缩 深度优先 LeetCode37. 解数独 编写一个程序&#xff0c;通过填充空格来解决数独问题。 数独的解法需 遵循如下规则&#xff1a; 数字 1-9 在每一行只能出现一次。 数字 1-9 在每一列只能出现一次。 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只…

突然连接不上 Bitbucket.org?

提交或拉取时都出现了错误 查找一下 Bitbucket.org 的IP&#xff0c;使用这个网址 https://site.ip138.com/bitbucket.org/ 找到C:\Windows\System32\drivers\etc下的hosts文件用记事本/notepad 打开&#xff0c;咱加到host文件里面 一切又恢复正常

UE5 C++软引用

一.软引用通常是仅储存资源对象的资源路径没有与资源产生耦合关系的引用&#xff08;软引用加载到内存中&#xff0c;只有在需要的时候才会被加载&#xff09; 软引用通常有FSoftObjectPath、FSoftClassPath、TSoftObjectPtr、TSoftClassPtr。它指向的资源未被加载&#xff0c…

四足机器人摆线规划程序

一、标准摆线公式 { x r ∗ ( θ − sin ⁡ ( θ ) ) y r ∗ ( 1 − cos ⁡ ( θ ) ) \left\{\begin{array}{l} xr *(\theta-\sin (\theta)) \\ yr *(1-\cos (\theta)) \end{array}\right. {xr∗(θ−sin(θ))yr∗(1−cos(θ))​ 这里的r表示摆线的圆的半径&#xff0c; θ \…

codeforces round944(div4)A~E题解

文章目录 [A. My First Sorting Problem](https://codeforces.com/contest/1971/problem/A)[B. Different String](https://codeforces.com/contest/1971/problem/B)[C. Clock and Strings](https://codeforces.com/contest/1971/problem/C)[D. Binary Cut](https://codeforces…

性能测试 --概念

什么是性能测试 性能测试和功能测试都是在系统测试阶段运行, 两者有什么区别呢? 案例:豌豆射手和三线射手都是射手, 它们的功能都是向前发射豌豆进行攻击, 能够攻击到地面的僵尸. 但是从性能上来讲, 豌豆射手只能攻击到一路的僵尸, 而三线射手能同时攻击三路(注:放在边路实际…

读天才与算法:人脑与AI的数学思维笔记24_预测性文本生成器

1. 起源 1.1. 人类讲故事可能起源于“假如……”这种问答结构 1.2. 讲故事是人类做安全试验的一种方式 1.2.1. 如果你问一个人“假如……”&#xff0c;其实是在探索你的行为对他可能带来的影响 1.3. 最早出现的故事极有可能就源自我们对在周遭混乱的环境中寻找某种秩序的渴…

基于vgg16和efficientnet卷积神经网络的天气识别系统(pytorch框架)全网首发【图像识别-天气分类】

一个能够从给定的环境图像中自动识别并分类天气&#xff08;如晴天、多云、雨天、雪天闪电等&#xff09;的系统。 技术栈&#xff1a; 深度学习框架&#xff1a;PyTorch基础模型&#xff1a;VGG16与EfficientNet任务类型&#xff1a;计算机视觉中的图像分类 模型选择 VGG16 …