JAVA面经整理(4)

news2024/11/27 5:27:42
一)Volitaile关键字的作用:

volatile的使用:常常用于一写多读的情况下,解决内存可见性和指令重排序

JAVA内存的JMM模型:主要是用来屏蔽不同硬件和操作系统的内存访问差异的,在不同的硬件和不同的操作系统内存的访问是有差异的,这种差异会导致相同的代码在不同的硬件和操作系统会有不同的行为,JMM内存模型就是为了解决这个差异,统一相同代码在不同硬件和不同操作系统的差异的

JAVA内存模型规定:所有的变量(包括普通成员变量和静态成员变量),都是必须存储在主内存里面,每一个线程都有自己的工作内存,线程的工作内存保存了该线程用到的变量和主内存的副本拷贝,线程对变量的操作都在工作内存中进行,线程是不可以直接读写主内存的变量

但是Java的内存模型会带来一个新的问题,那就是说当某一线程修改了主内存共享变量的值之后,那么其他线程可能就不会感知到此值被修改了,它会一直使用工作内存的旧值,这样程序的执行就不会符合我们的预期了

内存可见性:指的是多个线程同时进行操作同一个变量,其中某一个线程修改了变量的值之后,其他线程无法进行感知变量的修改,这就是内存可见性问题

关键字volitaile和synchronized就可以强制保证接下来的操作是在操作内存,在生成的java字节码中强制插入一些内存屏障的指令,这些指令的效果,就是强制刷新内存,同步更新主内存和工作内存中的内容,在牺牲效率的时候,保证了准确性

synchronized,双重if,volatile

指令重排序是指编译器或者CPU优化程序的一种手段,调整指令执行的先后顺序,提高程序的执行性能,但是在多线程情况下会出现问题

1)之前咱们在说volatile的时候是说,此处的volatile是为了保证让其他线程修改了这里面的instance之后,保证后面的线程可以及时感知到修改,因为其他线程不也是加上synchronized来进行修改的吗?

2)当我们去执行instance=new instance()的时候,我们本质上干了三件事情

2.1)创建内存

2.2)针对内存空间进行初始化

2.3)把内存的地址赋值给引用

3)上面的这三个步骤可能会触发指令重排序,也就是说乱序执行,这里的执行顺序,可能是1,2,3,也可能是1,3,2,可能就是说把地址空间赋给引用了,然后再进行初始化;

咱们加上了volatile就可以保证这里面的指令就是按照1,2,3的顺序来进行执行的,保证其他线程拿到的实例也是一个完整的实例

  private Singleton(){};
    private static Singleton singleton=null;
    public static Singleton GetInstance(){
        if(singleton==null){
            synchronized(Object.class){
                if(singleton==null){
                    singleton=new Singleton();
                }
            }
        }
        return singleton;
    }
}

单例模式适用于经常被访问的对象

或者是创建和销毁需要需要进行调用大量资源和时间的对象

1)创建一个私有的构造方法:防止外部直接new破坏单例模式

2)创建一个私有变量static保存该单例对象

3)提供公开的static方法返回单例对象

饿汉模式:在类加载的时候直接创建并进行初始化对象,在程序启动的时候只进行加载一次

实现简单,不存在线程安全问题,但是因为类加载的时候就创建了该对象

创建之后如果没有进行使用,那么就造成了资源浪费,依赖的是classLoader机制

懒汉模式:延迟加载,只有被使用的时候,才会被初始化

枚举:在第一次被使用的时候,才可以被JAVA虚拟机进行加载并初始化,所以他也是线程安全,并且是懒加载

enum TestEnum{//不要加class
    RED,Blue;//加上分号
    public static TestEnum GetInstance(){//返回类型是你自定义的类名,不是enum
        return RED;
    }
}
二)synchronized的底层实现原理: 

synchronized底层是通过JVM内置的监视器锁来实现的,而监视器锁有是依靠于操作系统的底层mutex互斥量来实现的,进入到synchronized修饰的代码,相当于加了moniterenter,结束synchronized修饰的代码,相当于是moniterexit

监视器:监视器是一种机制,用来进行保障任何时候,都只有一个线程来进行执行指定区域的代码

1)一个监视器就类似于一个建筑,建筑里面有一个特殊的房间,这个房间同一时刻只能被一个线程所占有,一个线程从进入到该房间到离开该房间,可以全程占有该房间的所有数据;

2)进入该建筑叫做进入监视器,进入该房间叫做获得监视器,独自占有该房间叫做拥有监视器,离开该房间叫做释放监视器,离开该建筑叫做退出监视器

synchronized修饰的代码块,进入到代码块被moniterenter,然后退出代码块moniterexit

监视器锁就是类似于一个房间,同一时刻只会允许一个人进来,在任何时候都是只能有一个人进来,是依靠ObjectMoniter实现的

1)_recursions是某一个线程某一次重复获取到锁的次数,可重入锁代表某一个线程可以重复的获取锁,因为synchronized是可重入锁,线程是可以重复的获取到这把锁,那么某一个线程每一次获取到锁的时候,计数器就会记录该线程和获取到锁的次数,每获取到一次锁,进入到这个房间,_recursions++,每当离开这个房间一次,那么这个计数器就--,当_recursions=0的时候,说明此时这个监视器是没有人的,就放开房间让其他线程进入

2)count记录每一个线程获取到锁的次数,就是前前后后这个这个线程一共获取这把锁多少次

3)_owner:The Owner的拥有者,是持有该ObjectMonitor监视器对象的线程;

4)_EntryList:EntryList监控集合,存放的是处于阻塞状态的线程队列,在多线程情况下,竞争失败的线程会进入到EntryList阻塞队列;

5)WaitSet:存放的是处于wait状态的线程队列,当线程拥有监视器锁得时候调用到了wait()方法之后,会自动释放监视器锁,this.owner=null,释放监视器锁的线程会进入到waitSet队列,

监视器的执行流程如下:

1)线程通过CAS(对比并进行替换)尝试获取该锁,如果获取成功,那么将owner字段设置成当前线程,表明该线程已经持有这把锁,并将_recursions冲入次数的属性+1,如果获取失败就先通过自旋CAS来进行获取该锁,如果还是失败那么就把当前线程放入到EntryList监测队列,进入到阻塞状态;

2)当拥有锁的线程执行了wait方法之后,调用wait的线程释放锁,将owner变量设置成null状态,同时把该线程放入到waitSet带授权队列中等待被唤醒;

3)当调用某一个拥有监视器锁的线程调用notify方法时,随机唤醒WaitSet队列中的某一个线程来尝试获取锁,等待拥有监视器锁的调用notify的线程释放锁后,当调用notifyAll时随机唤醒所有WaitSet的队列的线程尝试获取该锁;

4)当拥有监视器的线程执行完了释放锁之后,会唤醒EntryList中所有线程尝试获取到该锁;

 

wait方法也是可以指定休眠时间的,比如说现在有两个线程,线程1进入到了synchronized修饰的方法之后,调用wait方法的那一刻,线程1会放弃synchronzied的那把锁,线程1从进入到waitting状态,线程2获取到了同一把锁,然后执行对象的notifyAll方法,执行完线程2的synchronized方法之后线程2释放锁,然后去尝试唤醒所有wait的线程,然后所有的wait的线程都去尝试争夺这同一把锁,但是如果是线程2调用的是notify方法,然后其他wait的线程只会被唤醒一个,然后尝试获取到锁执行;

三)说一说synchronized锁升级的流程:

偏向锁,指的是偏向某一个线程,指的是所有的线程来了之后会进行判断,对象头中的头部保存当前拥有的锁的线程ID,判断当前线程ID是否等于_owner的线程ID,等于说明你拥有这个线程,就可以进入执行

1)无锁:刚一开始的时候,没有线程访问synchronized修饰的代码,说明此时是处于无锁状态

2)偏向锁:当某一个线程第一次访问同步代码块并获取到这把锁的时候,锁的对象头里面将线程的ID记录下来,下一次再有线程过来的时候,程序会直接判断对象头中的线程ID(第一次访问锁的线程ID)和实际访问程序的线程ID是否相同,如果是同一个,那么程序会继续向下访问,如果不相同,说明有两个线程以上进行争夺锁,于是尝试通过CAS获取到这把锁,如果获取不到,就升级成轻量级锁

3)轻量级锁:这个还没有放弃挣扎,还会通过自旋的方式尝试得到锁,如果通过一定的次数得不到锁,因为synchronized是自适应自旋锁,synchronized是根据上一次自旋的结果来去决定这一次自旋的次数的,如果这个线程是通过上一次自旋来获取到锁的话那么会有极大的大概率这一次也是可能通过自旋的方式来获取到锁的,如果上一次获取次数也比较少,那么这一次自旋的次数也会变少,如果一定的自旋次数获取不到锁,直接阻塞到EntryList

4)重量级锁:升级成重量级锁

四)synchronized是固定自旋次数吗?

synchronized本身是一个自适应自旋锁,自适应自旋锁指的是线程尝试获取到锁的次数不是一个固定值而是一个动态变化的值,这个值会根据前一次线程自旋的次数获取到锁的状态来决定此次自选的次数,比如说上一次通过自选成功的获取到了锁,那么synchronized会自动判断通过这一次自旋获取到锁的概率也会大一些,那么这一次自旋的次数就会多一些,如果通过上一次自旋没有成功获取到锁,那么这一次成功获取到锁的概率也会变得非常低,所以为了避免资源的浪费,就会少循环或者是不循环,简单来说就是如果这一次自旋成功了,下一次自旋的次数会多一些,否则下一次自选的次数会少一些

五)线程通讯的方法都有哪些?

线程通讯指的是多个线程之间通过某一种机制进行协调和交互,例如线程等待和通知机制就是线程通讯的主要手段之一,就是一个线程休眠了,另外一个线程进行唤醒,每一个等待唤醒的手段都是有着不同的应用场景,下一个唤醒手段就是上一个唤醒手段的补充

1)wait和notify使用必须和synchronized搭配一起使用,况且wait会主动释放锁;

2)可以唤醒加了同一把锁下面的两个不同的线程组,Condition可以有更多的分支,能唤醒的更加精准,每一组线程都可以使用一个Condition来进行等待和唤醒,生产者不要唤醒生产者消费者不要唤醒消费者,在生产者里面可以调用消费者的Condition2进行唤醒

3)可以指定某一个线程来唤醒,LockSupport.park()休眠当前线程,park和unpark本身就是静态方法,LockSupport.unpark(线程对象),LockSupport可以不搭配synchronized和lock来结合使用,这里面得park方法那个线程调用LockSupport.park()方法,拿一个线程就会阻塞

2)一个lock可以创建多个Conidtion此时就可以调用Condition的await()方法和signal()方法

一个Lock可以创建多个Condition对象,搞一个Condition叫做生产者,再Condition搞一个叫做消费者,可以有更多的分支,唤醒就变的更加的精准,每一组线程可以使用一个Condition来进行等待和唤醒的操作,分两组绑定Condition;

2.1)一堆生产者可以使用一个Condtion对象1来进行唤醒,可以使用Condition对象1调用await()方法进行休眠生产者,如果想要唤醒生产者,就可以调用Condition对象1的signal来唤醒生产者

2.2)一堆消费者可以使用一个Condtion对象2来进行唤醒,可以使用Condition对象2调用await()方法进行休眠消费者,如果想要唤醒消费者,就可以调用Condition对象1的signal来唤醒消费者

2.3)但是生产者和消费者加的都是同一把锁,这样使用Condition类就可以唤醒加了同一把锁的两组线程进行唤醒了,可以指定的某一组线程中的某一个线程进行唤醒

但是两堆生产者和消费者都是加的同一把锁,所以就可以根据哪一个Condition对象来唤醒的是生产者还是消费者,也是随机唤醒,但是也是可以指定唤醒那一组,是生产者还是消费者,但是wait和notify一个锁,一个对象只能有一组,同时生产者也是可以调用消费者的一个Condition进行唤醒了

1)现在有一个生产者消费者模型,生产者会产生一些任务存放到任务队列中,消费者是从任务队列中取出任务进行消费执行,生产者和消费者都是一组线程;

2)没有任务,生产者休眠,为了保证资源不被浪费,消息队列没有任务,消费者也会休眠,假设生产者线程组的某一个生产者有任务开始就开始被唤醒将任务放到消息队列里面此时被唤醒的生产者将任务推动到消息队列里面,第二步就是休眠唤醒消费者去消费任务,如果此时使用的是Object中的唤醒机制,是将加了锁的线程随机唤醒,此时就会发生严重的问题,此时可能唤醒的是生产者和消费者,因为生产者和消费者加的是同一把锁,如果是唤醒的是生产者,此时会浪费资源,可能会导致消费者永远也不会消费消息队列中的元素

public class DemoWorld {
    public static void main(String[] args) throws InterruptedException {
     Thread t1=new Thread(()->{
         System.out.println("线程1开始阻塞");
         LockSupport.park();
         System.out.println("线程1继续执行");
     });
     Thread t2=new Thread(()->{
         System.out.println("线程2开始阻塞");
         LockSupport.park();
         System.out.println("线程2继续执行");
     });
     Thread t3=new Thread(()->{
         LockSupport.unpark(t1);
     });
     t1.start();
     t2.start();
     Thread.sleep(3000);
     t3.start();
    }
}
六)读写锁:创建读写锁,提高程序的执行性能,适用于读多写少

读写锁是将一把锁分成两部分,读锁和写锁,读锁是允许多个线程同时获得的,因为读操本身就是线程安全的,而写锁是互斥锁,是不允许多个线程同时获得些写锁的,况且写操作和读操作也是互斥的,读读不互斥,写写互斥,读写互斥

1)提高了程序执行的性能,多个读锁可以同时进行,相对于普通锁来说在任何情况下都要排队执行来说,读写锁提高了并发程序的执行性能

2)避免读到临时数据,读锁和写锁是互斥排队执行的,这样就保证了读取操作不会读到写一半的临时数据

多个线程获取到读锁,称之为读读不互斥,一个线程不能同时获取到读锁和写锁,写锁和写锁之间进行互斥

1)读读不互斥

public static void main(String[] args) throws InterruptedException {
       final ReentrantReadWriteLock commonLock=new ReentrantReadWriteLock();
       final ReentrantReadWriteLock.ReadLock readLock=commonLock.readLock();//获取到读写锁中的读锁
       final ReentrantReadWriteLock.WriteLock writeLock= commonLock.writeLock();//获取到读写锁中的写锁
       Thread t1=new Thread(()->{
           try {
               readLock.lock();
               System.out.println("线程1获取到了读锁");
           }finally {
               readLock.unlock();
               System.out.println("线程1释放了读锁");
           }
       });
       Thread t2=new Thread(()->{
           try {
               readLock.lock();
               System.out.println("线程2获取到了读锁");
           }finally {
               readLock.unlock();
               System.out.println("线程2释放了读锁");
           }
       });
       t1.start();
       t2.start();
    }

2)读写互斥,可以看到一个线程不能同时获取到读写锁中的读锁和写锁

 public static void main(String[] args) throws InterruptedException {
        final ReentrantReadWriteLock commonLock=new ReentrantReadWriteLock();
        final ReentrantReadWriteLock.ReadLock readLock=commonLock.readLock();//获取到读写锁中的读锁
        final ReentrantReadWriteLock.WriteLock writeLock= commonLock.writeLock();//获取到读写锁中的写锁
        Thread t1=new Thread(()->{
            try {
                writeLock.lock();
                System.out.println("线程1获取到了写锁");
            }finally {
                writeLock.unlock();
                System.out.println("线程1释放了写锁");
            }
        });
        Thread t2=new Thread(()->{
            try {
                readLock.lock();
                System.out.println("线程2获取到了读锁");
            }finally {
                readLock.unlock();
                System.out.println("线程2释放了读锁");
            }
        });
        t1.start();
        t2.start();
    }

public class DemoWorld {
    public static void main(String[] args) throws InterruptedException {
        final ReentrantReadWriteLock commonLock=new ReentrantReadWriteLock();
        final ReentrantReadWriteLock.ReadLock readLock=commonLock.readLock();//获取到读写锁中的读锁
        final ReentrantReadWriteLock.WriteLock writeLock= commonLock.writeLock();//获取到读写锁中的写锁
        Thread t1=new Thread(()->{
            try {
                writeLock.lock();
                System.out.println("线程1获取到了写锁");
            }finally {
                writeLock.unlock();
                System.out.println("线程1释放了写锁");
            }
        });
        Thread t2=new Thread(()->{
            try {
                writeLock.lock();
                System.out.println("线程2获取到了写锁");
            }finally {
                writeLock.unlock();
                System.out.println("线程2释放了写锁");
            }
        });
        t1.start();
        t2.start();
    }
}

七)公平锁和非公平锁有什么区别? 

公平锁:每一个线程获取到锁的的顺序总是按照线程访问锁的先后顺序来进行获取的,最前面访问锁的那个线程总是能最先获取到锁

非公平锁:每一个线程获取锁的顺序是随机的,并不会遵循先来后到的规则,所有线程会竞争并获取锁

公平锁的运行原理:

3.1)获取到锁的时候,先将线程自己添加到等待队列的队尾并休眠,当某线程用完锁之后,会先唤醒等待队列的队首的线程去获取到锁,锁的使用顺序就是队列中的先后顺序,在整个过程中,线程会从运行状态切换成休眠状态,再从休眠状态变成运行状态

3.2)在整个过程中,线程每一次休眠和恢复都需要进行用户态和内核态的切换,这个状态的转换是比较慢的,更注重的是资源的平均分配,但是按序唤醒线程的开销比较大,所以公平锁执行效率比较慢

非公平锁的运行原理:

4.1)当线程尝试获取到锁的时候,会先通过CAS来进行尝试获取到锁,如果获取到锁就直接拥有锁,如果锁获取失败就进入到阻塞队列,等待下一次获取到锁,获取到锁不用遵循先来后到的规则,避免线程恢复和休眠的操作,加速了程序的执行效率,不用遵循先来先到的规则

4.2)非公平锁的吞吐率(单位时间内获取到锁的速率)要比公平锁的概率更高,但是可能会出现线程饿死的情况,资源分配随机性比较强

八)JUC包下面的Exchange交换器:实现两个线程之间的数据交换的

1)exchange(V x):等待另一个线程到达此交换点,然后将对象传输给另一个线程,并从另一个线程中得到交换的对象,如果另一个线程未到达交换点,那么调用exchange得线程会一直进行休眠除非遇到了线程中断;

2)exchange(V x,long timeout,Timeunit unit):等待另一个线程到达交换点,然后将这个对象传输给另一个线程了,并从另一个线程中得到要交换的对象,如果说另一个线程未达到次交换点,那么此线程会一直进行休眠,直到遇到了线程中断,或者等待的时间超过了设定的时间,那么会直接抛出异常;

3)也就是说exchange方法到达了一个交换点之后,线程会在这个交换点进行休眠等待,直到另一个线程也调用了exchange方法,他们会进行相互交换数据,然后会执行后续的代码

4)Exchange是用来实现两个线程之间的数据交换的,它可以进行传输任意类型的数据,只需要在进行创建的时候定义泛型类型就可以了,它的核心方法是exchange方法

当线程执行到这个方法之后,当前线程会执行休眠操作,会进行等待另一个线程进行这个交换点,如果说另一个线程进入到了交换点,那么两者会进行交换数据,并执行接下来的流程

class Person{
    public String username;
    public String desc;
}
public class DemoWorld {

    public static void main(String[] args) throws InterruptedException {
        Exchanger<Person> exchanger=new Exchanger<>();
        Thread t1=new Thread(()->{
            Person person1=new Person();
            person1.username="线程1";
            person1.desc="我是在t1线程创建的,现在过得很好";
            try {
                Thread.sleep(1000);
                Person person=exchanger.exchange(person1);//此时交换完成之后获取到了线程2的person2
                Thread.sleep(1000);
                System.out.println("当前打印的线程是"+Thread.currentThread().getName()+person.username+person.desc);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        },"线程1");
        Thread t2=new Thread(()->{
            Person person2=new Person();
            person2.username="线程1";
            person2.desc="我是在t1线程创建的,现在过得很好";
            try {
                Thread.sleep(1000);
                Person person=exchanger.exchange(person2);//此时交换之后获取到了线程1中的person1
                Thread.sleep(1000);
                System.out.println("当前打印的线程是"+Thread.currentThread().getName()+person.username+person.desc);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        },"线程2");
        t1.start();
        t2.start();
    }
}

九)进程和线程有什么区别?

上下文,状态,优先级,记账信息不共享,操作系统的调度器会非常频繁的进行线程切换,哪怕某个进程做某个工作做了一半,也有可能被打断;

单个CPU已经达到极限了,多核CPU代替单核CPU

在代码执行任务的时候,先把任务进行拆分,又有多个CPU来并发式的执行

什么情况下会造成线程从用户态到内核态的切换呢?

1)首先,如果在程序运行过程中发生中断或者异常,系统将自动切换到内核态来运行中断或异常处理机制

2)此外,程序进行系统调用也会从用户态切换到内核态

1)进程包含线程,如果将进程比作工厂,那么线程就是工厂中的若干流水线

2)创建线程比创建进程更轻量,销毁线程比销毁进程更轻量,调度线程比调度进程更轻量

3)切换速度不同:线程切换上下文速度是很快的,但是进程的上下文切换速度比较慢

4)操作系统创建进程,要给进程分配资源,进程是操作系统进行资源分配的最小单位,操作系统创建的线程,是要在CPU上面进行调度执行,线程是操作系统进行调度执行的最小单位

5)进程具有独立性,进程与进程之间资源不共享,每一个进程都有自己的虚拟地址空间,同一个进程的多个线程之间,共用这一块虚拟地址空间,一个进程挂了,不会影响到其他进程,但是同一个进程的多个线程,是在用同一个虚拟内存空间,一个线程挂了,是可能影响到其他线程的,甚至可能会导致整个进程崩溃

十)start和run方法有什么区别? 

run只是一个普通的方法,描述了任务的内容,start是一个特殊的方法,会在系统中创建线程

1)方法性质不同:调用start方法可以直接启动线程,并使线程进入就绪,当run方法执行完了,线程,也就结束了,但是如果直接执行run方法,会当作普通方法来调用,还是在main方法进行的,不会创建一个新线程;

2)执行速度不同:run方法也叫作线程体,它里面包含了具体要执行的业务代码,当进行调用run方法的时候,会立即执行run方法的代码,但是当我们调用start方法的时候,本质上是启动了一个线程并将这个线程的状态设置为就绪状态,也就是说调用start()方法,程序不会立即执行

3)调用次数不同:run方法是普通方法,普通方法是可以被调用多次,但是start方法是创建新线程执行任务,而start方法只能调用一次,否则就会出现IllegalThreadStateException非法线程状态

为什么start方法只能调用一次呢?

原因是当start代码实现的第一行,会先进行判断当前的状态是不是0,也就是说是否是新建状态,如果不是新建状态NEW,那么就会抛出IllegalThreadStateException非法线程状态异常

当线程调用了第一个start方法之后,线程的状态就会由新建状态NEW变成RUNNABLE状态,此时再次调用start方法,JVM就会判断当前线程已经不等于新建状态了,从而会抛出IllegalThreadStateException异常,所以线程状态是不可逆的;

十二)synchronized的三种用法

1)修饰普通方法:加在访问修饰限定符,方法返回值之间

public synchronized void method(){};修饰普通方法,作用的对象是调用这个方法的对象

2)修饰静态方法:public static synchronized void staticMethod{};,当synchronized修饰静态方法的时候,锁的是类对象,这个锁对于所有调用这个锁的对象都是互斥的:注意,当修饰静态方法的时候,所有调用这个静态方法的对象都是互斥的,但是普通方法是指对对象级别的,不同的对象有着不同的锁

3)修饰代码块:在我们的日常开发中,最常用的是给代码块加锁,而不是给方法进行加锁,因为给方法进行加锁,相当于是给整个方法全部进行加锁,这样的话锁的粒度就太大了,程序的执行性能就会受到影响,加锁的对象常用this或者xxx.class这样的形式来进行表示

十三)线程的中断:

线程的中断,核心就是让线程的入口函数也就是run方法执行完毕,它指的是内存中的线程结束了,而不一定是Thread对象销毁;

十四)wait和sleep有什么区别? 

wait方法和sleep方法都是用来将线程进入到休眠状态的,并且咱们的sleep方法和wait方法都是可以响应interrupt中断,也就是说在线程进行休眠的过程中,如果收到interrupt的中断信号,都可以进行响应并进行中断,并且都可以抛出InterruptedException异常

1)wait 方法属于 Object 类的方法,而 sleep 属于 Thread 类的方法

2)语法使用不同,wait必须和synchronized一起进行搭配使用,否则就会抛出IIIegalMonitorStateException异常,而sleep无需和synchronized一起使用;

3)wait会自动进行释放锁,调用wait的线程会主动进入到waitset队列里面,但是sleep不会主动释放锁,sleep在休眠状态并不会释放锁;

4)调用sleep方法会自动进入到time-waitting状态,但是调用wait方法会进入到waitting状态

5)等待机制:sleep是指定一个固定的时间去进行阻塞等待,wait既可以指定时间,又可以无限进行等待

6)唤醒机制:wait唤醒是可以通过notify机制或者interrupt或者时间到来进行唤醒,sleep通过时间到或者interrupt来唤醒;

7)方法设计初衷:wait的作用主要是为了协调线程之间的先后顺序,这样的场景并不适合sleep,sleep只是为了让线程休眠,并不会涉及到多个线程之间的配合

 

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

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

相关文章

C. Best Binary String

题目&#xff1a;样例&#xff1a; 输入 4 ??01? 10100 1??10? 0?1?10?10输出 00011 10100 111101 011110010 思路&#xff1a; 根据题意&#xff0c; 题目意思是 ‘&#xff1f;’ 字符 该替换何值&#xff0c;使得原字符串&#xff0c;最少操作 翻转任意区间&#…

贪心算法+练习

正值国庆之际&#xff0c;祝愿祖国繁荣昌盛&#xff0c;祝愿朋友一生平安&#xff01;终身学习&#xff0c;奋斗不息&#xff01; 目录 1.贪心算法简介 2.贪心算法的特点 3.如何学习贪心算法 题目练习&#xff08;持续更新&#xff09; 1.柠檬水找零&#xff08;easy&…

QT:鼠标画线(双画布)

widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QPoint> //点 #include <QMouseEvent> //鼠标事件 #include <QPaintEvent> //绘图事件class Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent 0);~Wi…

论文笔记:TMN: Trajectory Matching Networks for PredictingSimilarity

2022 ICDE 1 intro 1.1 背景 轨迹相似度可以划分为&#xff1a; 非学习度量方法 通常是为一两个特定的轨迹距离度量设计的&#xff0c;因此不能与其他度量一起使用通常需要二次时间&#xff08;O(n^2)&#xff09;来计算轨迹之间的精确距离基于学习的度量方法 利用机器学习…

第一届“龙信杯”取证

案情简介 2023年9月&#xff0c;某公安机关指挥中心接受害人报案&#xff1a;通过即时通讯工具添加认识一位叫“周微”的女人&#xff0c;两人谈论甚欢&#xff0c;确定网上恋爱关系&#xff0c;后邀约裸聊&#xff0c;受害人上钩后&#xff0c;“周微”和受害人进行裸聊&#…

Kafka集群架构设计原理详解

文章目录 1、zookeeper整体数据2、Controller Broker选举机制 1、zookeeper整体数据 Kafka将状态信息保存在Zookeeper中&#xff0c;这些状态信息记录了每个Kafka的Broker服务与另外的Broker服务有什么不同。通过这些差异化的功能&#xff0c;共同体现出集群化的业务能力。这些…

SpringCloud Alibaba - Sentinel 授权规则、自定义异常结果

目录 一、授权规则 1.1、什么是授权规则 1.2、授权规则的配置 1.2.1、配置信息介绍 1.2.2、如何得到请求来源 1.2.3、实现步骤 a&#xff09;给网关过来的请求添加请求头信息 b&#xff09;在 订单微服务 中实现 RequestOriginParser 接口中的 parseOrigin 方法 c&…

【JAVA】普通IO数据拷贝次数的问题探讨

最近看到网上有些文章在讨论JAVA中普通文件IO读/写的时候经过了几次数据拷贝&#xff0c;如果从系统调用开始分析&#xff0c;以读取文件为例&#xff0c;数据的读取过程如下&#xff08;以缓存I/O为例&#xff09;&#xff1a; 应用程序调用read函数发起系统调用&#xff0c;此…

1574. 删除最短的子数组使剩余数组有序

1574. 删除最短的子数组使剩余数组有序 原题链接&#xff1a;完成情况&#xff1a;解题思路&#xff1a;参考代码&#xff1a; 原题链接&#xff1a; 1574. 删除最短的子数组使剩余数组有序 https://leetcode.cn/problems/shortest-subarray-to-be-removed-to-make-array-so…

竞赛选题 机器视觉 opencv 深度学习 驾驶人脸疲劳检测系统 -python

文章目录 0 前言1 课题背景2 Dlib人脸识别2.1 简介2.2 Dlib优点2.3 相关代码2.4 人脸数据库2.5 人脸录入加识别效果 3 疲劳检测算法3.1 眼睛检测算法3.2 打哈欠检测算法3.3 点头检测算法 4 PyQt54.1 简介4.2相关界面代码 5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#x…

redis runtime error: invalid memory address or nil pointer dereference

var Conn redis.Connfunc RedisInit(){Conn, err redis.Dial("tcp", "127.0.0.1:6379")if err ! nil {fmt.Println("redis.Dial err", err)return}fmt.Println("conn suc.11..", Conn)//defer Conn.Close()看看这两段代码的区别 而且…

游戏报错提示d3dx9_43.dll丢失怎么解决?这4个解决方法可以解决

随着科技的发展&#xff0c;电脑已经成为我们生活中不可或缺的一部分。然而&#xff0c;在使用电脑的过程中&#xff0c;我们可能会遇到各种问题&#xff0c;其中之一就是“d3dx9_43.dll丢失”。这个问题可能会影响到我们的电脑性能&#xff0c;甚至导致一些软件无法正常运行。…

管理经济学基本概念(三): 制定战略、竞争优势、员工利益等

1、怎么制定战略才能具备可持续的竞争优势 1.1、什么是可持续的竞争优势&#xff1f; 拥有以下两点&#xff0c;厂商就有了一种竞争优势&#xff0c;具有竞争优势的厂商能够赚取正值的经济利润&#xff1a; 以更低的成本像竞争对手一样提供相同的产品或服务效益 以相同的成本…

使用华为eNSP组网试验⑷-OSPF多区域组网

今天进行了OSPF的多区域组网试验&#xff0c;本来这是个很简单的操作&#xff0c;折腾了好长时间&#xff0c;根本原因只是看了别人写的配置代码&#xff0c;没有真正弄明白里面对应的规则。 一般情况下&#xff0c;很多单位都使用OSPF进行多区域的组网&#xff0c;大体分为1个…

[React] Zustand状态管理库

文章目录 1.Zustand介绍2.创建一个store3.使用方法3.1 获取状态3.2 更新状态3.3 访问存储状态3.4 处理异步数据3.5 在状态中访问和存储数组3.6 持续状态 4.总结 1.Zustand介绍 状态管理一直是现代程序应用中的重要组成部分, Zustand使用 hooks 来管理状态无需样板代码。 更少…

2023腾讯云服务器优惠价格表_10月更新报价

阿里云服务器10月报价表来了&#xff0c;和9月份价格差不多&#xff0c;再等一个月就到腾讯云双十一优惠活动了&#xff0c;腾讯云百科先来说说10月腾讯云服务器优惠价格表&#xff1a;轻量应用服务器2核2G3M带宽95元一年、2核4G5M带宽218元一年、2核2G4M带宽三年价540元一年、…

多头注意力机制

前面已经讲完了自注意力机制&#xff0c;简单来讲&#xff0c;就是对一组向量空间分别求内积&#xff0c;然后进行缩放&#xff0c;最后对不同的向量使用压缩后的分数累加求和。 1.多头是个什么东西&#xff1f; 实际上很简单&#xff0c;自注意力层的输出空间被分解为一组独立…

(ubuntu)Docker 安装linux 详情过程

文章目录 前言Docker 安装linux第一步&#xff1a;使用dokcker 拉取镜像&#xff1a;第二步&#xff1a;创建本地目录&#xff08;用于挂载&#xff09;第三步&#xff1a;&#xff08;上传配置文件&#xff09;修改配置文件第四步&#xff1a;创建docker容器第五步: 测试本地连…

检测防火墙是否开启、判断程序是否加入防火墙白名单(附源码)

VC常用功能开发汇总&#xff08;专栏文章列表&#xff0c;欢迎订阅&#xff0c;持续更新...&#xff09;https://blog.csdn.net/chenlycly/article/details/124272585C软件异常排查从入门到精通系列教程&#xff08;专栏文章列表&#xff0c;欢迎订阅&#xff0c;持续更新...&a…

软考——软件设计师中级2023年11月备考(1.计算机组成原理)

一、计算机组成原理 1.数据的表示 1.1 十进制转R进制 方法&#xff1a;对十进制数除R取余&#xff0c;最后对余数取倒序 如&#xff1a; 1.2 原码反码补码 1.3 浮点数 1.4 校验码 —— 海明码 &#xff08;非重点&#xff0c;了解即可&#xff09; 海明码的构成方法&…