JAVA面经整理(4)
一)Volitaile关键字的作用:
1)保证多线程环境下共享变量的可见性,对于一个线程对于一个共享表变量的修改,其他线程可以立即看到修改之后的共享变量的值
2)可以增加内存屏障来放置多个指令之间的重排序
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{//不要加classRED,Blue;//加上分号public static TestEnum GetInstance(){//返回类型是你自定义的类名,不是enumreturn 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的person2Thread.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中的person1Thread.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()方法会改变线程的状态,从NEW状态编程running状态,futureTask属于同步阻塞
为什么start方法只能调用一次呢?
原因是当start代码实现的第一行,会先进行判断当前的状态是不是0,也就是说是否是新建状态,如果不是新建状态NEW,那么就会抛出IllegalThreadStateException非法线程状态异常
当线程调用了第一个start方法之后,线程的状态就会由新建状态NEW变成RUNNABLE状态,此时再次调用start方法,JVM就会判断当前线程已经不等于新建状态了,从而会抛出IllegalThreadStateException异常,所以线程状态是不可逆的;
public static void main(String[] args) throws IOException, InterruptedException {Thread thread=new Thread(){public void run(){System.out.println(Thread.currentThread().getName()+"正在执行");}};thread.start();//thread.run()thread.sleep(1000);System.out.println("main线程正在执行");}
十二)synchronized的三种用法
1)修饰普通方法:加在访问修饰限定符,方法返回值之间
public synchronized void method(){};修饰普通方法,作用的对象是调用这个方法的对象
2)修饰静态方法:public static synchronized void staticMethod{};,当synchronized修饰静态方法的时候,锁的是类对象,这个锁对于所有调用这个锁的对象都是互斥的:注意,当修饰静态方法的时候,所有调用这个静态方法的对象都是互斥的,但是普通方法是指对对象级别的,不同的对象有着不同的锁
3)修饰代码块:在我们的日常开发中,最常用的是给代码块加锁,而不是给方法进行加锁,因为给方法进行加锁,相当于是给整个方法全部进行加锁,这样的话锁的粒度就太大了,程序的执行性能就会受到影响,加锁的对象常用this或者xxx.class这样的形式来进行表示
十三)线程的中断:
线程的中断,核心就是让线程的入口函数也就是run方法执行完毕,它指的是内存中的线程结束了,而不一定是Thread对象销毁;
在JAVA中停止线程有三种方法:
1)使用标志位,在程序的执行代码中使用一个标志位来控制程序的执行,当标志位是true的时候,线程可以继续执行,当标志位是false的时候,线程退出循环或者执行完任务之后停止,所以可以通过设置标志位来停止线程的执行,缺点就是线程中断的不够及时,因为在线程执行过程中无法调用while(flag)来及时判断线程是否处于终止状态,只能在下一轮中进行判断是否要终止当前线程,所以中断线程不及时
2)调用interrupt方法来中断线程的执行,当线程被中断的时候,线程本身会受到一个中断信号,可以在代码中检查线程的中断状态并进行处理,线程在接收到中断指令之后,立即中断了线程,相比于上一种自定义中断标识符的方法来说,它能更及时的响应中断线程指令
3)stop方法,但是现在已经是一种被弃用的方法了,是一个非安全的方法,因为他可能会导致线程的资源不会被正确的释放,可能会导致资源泄露等问题
十四)wait和sleep有什么区别?
wait方法和sleep方法都是用来将线程进入到休眠状态的,并且咱们的sleep方法和wait方法都是可以响应interrupt中断,也就是说在线程进行休眠的过程中,如果收到interrupt的中断信号,都可以进行响应并进行中断,并且都可以抛出InterruptedException异常
1)wait 方法属于 Object 类的方法,而 sleep 属于 Thread 类的方法
2)语法使用不同,wait必须和synchronized一起进行搭配使用,否则就会抛出IIIegalMonitorStateException异常,而sleep无需和synchronized一起使用;
Object object=new Object();System.out.println("wait前");object.wait();System.out.println("wait执行完成之后");
Exception in thread "main" java.lang.IllegalMonitorStateException
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只是为了让线程休眠,并不会涉及到多个线程之间的配合
public static void main(String[] args) {Object locker=new Object();Thread t1=new Thread(()->{synchronized (locker){try {System.out.println("线程1获取到了锁,开始调用wait方法等待");locker.wait();System.out.println("线程1结束等待又重新获取到了锁");} catch (InterruptedException e) {e.printStackTrace();}}});Thread t2=new Thread(()->{System.out.println("请输入一个整数");Scanner scanner=new Scanner(System.in);int num=scanner.nextInt();synchronized (locker){locker.notify();}});t1.start();t2.start();}
public static void main(String[] args) {Object object=new Object();Thread t1=new Thread(()->{synchronized (object){try {object.wait();} catch (InterruptedException e) {e.printStackTrace();}}});} 1)在上面的这个代码中,wait那个对象,就需要针对哪一个对象来进行加锁 2)我们synchronized锁住的对象和调用wait的对象是一样子的
十五)wait操作为什么是原子的?wait和notify为什么一定要搭配synchronized进行使用?
synchronized加锁的对象和调用wait和notify的对象必须是一致
wait的操作一共有三步:
1)wait主动释放当前线程持有的锁
2)等待当前线程被唤醒
3)wait不断去尝试重新获取到锁
上面的这个过程没有任何问题,但是下面这个过程就会导致线程1一直阻塞
1)是为了防止多线程并发过程中,程序的执行混乱问题
2)咱们现在来进行实现一个阻塞队列,假设wait方法和notify方法不需要进行加锁操作,当我们进行读取数据的时候,如果有数据就会进行返回数据,没有数据就会阻塞等待数据,实现代码如下:
class MyBlockingQueue{Queue<String> queue=new LinkedList<>();public void put(String data){//像咱们的阻塞队列里面加入数据queue.add(data);//唤醒线程继续向下执行,此时的唤醒是指唤醒take方法的线程notify();}public String take() throws InterruptedException{while(queue.isEmpty()){wait();}return queue.remove();}}
上面的程序执行过程分成三步:
1)线程1执行take方法首先判断当前队列中是否存在数据
2)如果说当前队列中没有数据,那么执行wait休眠操作(在while循环里面进行执行wait操作,等待进行唤醒)
3)线程2给队列中添加元素,并唤醒线程1继续执行
1)上述执行流程是有问题的,假设线程1执行完take方法的时候,进行判断队列为空,刚要执行wait操作进行休眠,但是此时线程2执行put操作突然进行添加数据
2)然而之前线程1已经执行完判断了,所以就会直接进入到休眠状态,此时线程1一直进行wait操作,此时况且线程2进行插入的数据永远不能被线程1读取,那么就会造成程序并发执行导致执行结果混乱的问题,会导致线程一直进行休眠的问题
十六)为什么wait被定义在Object类中,而sleep定义在Thread类中
1)因为JAVA中每一个对象上都有一把监视器锁,因为每一个对象都可以上锁,这就要求在对象头上要求有一个用来保存锁信息的位置,因为这个锁是对象级别的,而不是线程级别的,wait,notify都是针对于锁级别的操作,他们的锁属于对象,所以定义在Object类中最合适,因为Object是所有对象的父类,因为如果把wait/notify/notifyAll方法定义在Thread类中,会带来很大的局限性,比如一个线程可能持有多把锁,以便实现相互配合的复杂逻辑,假设此时wait方法定义到Thread类中,既然我们是让当前线程去等待某个对象的锁,自然应该通过操作对象来实现,而不是操作线程
2)让某个线程暂停运行一段时间,其控制范围是由当前线程决定,也就是说,在线程里面决定两者都可以让线程暂停一段时间,但是本质的区别是一个线程的运行状态控制,一个是线程之间的通讯的问题
十七)sleep和yield有什么区别?
sleep和yield都是Thread类中的静态方法,yield是暂停当前执行的线程对象,就是放弃当前拥有的CPU资源,并执行其他线程,就是让当前运行的线程回到就绪状态,也就是可执行状态,就是以保证相同优先级的状态具有执行机会
1)sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给优先级低的线程以运行的机会,而yield()方法只会给相同优先级或者更高优先级的线程以运行机会,yield(),虽然不会经常用到,让线程主动让出CPU,但是不会改变线程的状态
2)线程执行sleep()方法后会转入阻塞状态,所以,执行sleep()方法的线程在指定的时间内肯定不会被执行,而yield()方法只是使当前线程重新回到可执行状态,所以执行yield()方法的线程有可能在进入到可执行状态后马上又被执行。
3)sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常;
线程休眠:sleep
操作系统是如何管理进程的?是通过一个类来进行描述的,通过一个双向链表来进行组织的,这个说法可以是说针对只有一个线程的进程,是如此的,但是如果说一个进程里面有多个线程,每一个线程里面都有一个PCB,一个进程对应的就是一组PCB;
1)咱们的上面的这个双向链表属于就绪队列,但是在我们的操作系统内核里面,这样的队列却不是有多个,如果某一个线程调用了sleep方法,这个PCB就会进入到阻塞队列
2)咱们的操作系统进行调度线程的时候,就是从就绪队列里面挑选合适的PCB到CPU上面运行,那么我们阻塞队列里面的PCB就只能干等着,啥时候这个PCB可以回到就绪队列里面呢?那么只有说睡眠时间到了,咱们的系统才会把刚才的这个PCB从阻塞队列挪回到就绪队列里面
3)只有就绪队列里面的PCB才有被调度上CPU执行的权力,阻塞队列里面的PCB没有资格进入到CPU上面执行
4)一个进程对应的就是一组PCB,每一个PCB上面就有一个字段叫做tgroupID,这个ID其实就是进程的tgroupID,同一个进程中的若干个线程的tgroupID其实是相同,linux的系统内核是不区分进程和线程的,linux内核只认PCB,进程和线程其实是咱们程序员写有关于应用程序的代码才搞出来的词,实际上linux内核只认PCB,在linux系统内核里面我们把线程称之为轻量级进程,只不过是有些PCB共用同一个内存,有些PCB共用同一块虚拟地址空间,有些PCB有不同的虚拟地址空间,前面所说的进程线程概念是站在一个更加抽象的角度,站在用户写代码的角度来进行看待的,但在操作系统内核实现的角度,是一视同仁,使用同样的方式来进行表述的;
5)咱们的上面的这个链表就是就绪队列的双向链表,如果我们的某个线程调用了sleep方法,这个PCB就会进入到阻塞队列,实际上,咱们的操作系统在进行调度线程的时候,就是从我们的就绪队列中查找合适的PCB到我们的CPU上面执行,当我们的睡眠时间到了,系统就会把刚才这个PCB从阻塞队列挪回到就绪队列,join也会导致线程进入到阻塞队列里面
使用JAVA来进行打印线程的所有状态:
public class Teacher {public static void main(String[] args) {for(Thread.State value:Thread.State.values()){System.out.println(value);}} }
也就是说这些状态是JAVA自己搞出来的,就和操作系统中的PCB的状态没有啥关系
public static void main(String[] args)throws InterruptedException{Thread t1=new Thread(){public void run() {for(int i=0;i<10;i++){// System.out.println(Thread.currentThread().getName());}try{sleep(100);}catch(InterruptedException e){e.printStackTrace();;}System.out.println("线程结束");}};System.out.println(t1.getName());//获取线程名字System.out.println(t1.getPriority());//获线程优先级System.out.println(t1.isDaemon());//该线程是否为守护线程System.out.println(t1.getId());System.out.println(t1.isAlive());System.out.println(t1.isInterrupted());System.out.println(t1.getState());//获取到指定线程的状态t1.start();while(t1.isAlive()){System.out.println(t1.getState());System.out.println(t1.isInterrupted());}System.out.println(t1.getState());}}
十八)线程的状态有哪些?
相关文章:
JAVA面经整理(4)
一)Volitaile关键字的作用: 1)保证多线程环境下共享变量的可见性,对于一个线程对于一个共享表变量的修改,其他线程可以立即看到修改之后的共享变量的值 2)可以增加内存屏障来放置多个指令之间的重排序 volatile的使用:常常用于一写多读的情况下ÿ…...
Python3数据科学包系列(一):数据分析实战
Python3中类的高级语法及实战 Python3(基础|高级)语法实战(|多线程|多进程|线程池|进程池技术)|多线程安全问题解决方案 Python3数据科学包系列(一):数据分析实战 Python3数据科学包系列(二):数据分析实战 认识下数据科学中数据处理基础包: (1)NumPy 俗话说: 要学会跑需先…...
【LittleXi】【MIT6.S081-2020Fall】Lab: locks
【MIT6.S081-2020Fall】Lab: locks 【MIT6.S081-2020Fall】Lab: locks内存分配实验内存分配实验准备实验目的1. 举一个例子说明修改前的**kernel/kalloc.c**中如果没有锁会导致哪些进程间竞争(races)问题2. 说明修改前的kernel/kalloc.c中锁竞争contention问题及其后果3. 解释a…...
图像压缩:Transformer-based Image Compression with Variable Image Quality Objectives
论文作者:Chia-Hao Kao,Yi-Hsin Chen,Cheng Chien,Wei-Chen Chiu,Wen-Hsiao Peng 作者单位:National Yang Ming Chiao Tung University 论文链接:http://arxiv.org/abs/2309.12717v1 内容简介: 1)方向:…...
C++ 类和对象篇(四) 构造函数
目录 一、概念 1. 构造函数是什么? 2. 为什么C要引入构造函数? 3. 怎么用构造函数? 3.1 创建构造函数 3.2 调用构造函数 二、构造函数的特性 三、构造函数对成员变量初始化 0. 对构造函数和成员变量分类 1. 带参构造函数对成员变量初始化 2. …...
Swing程序设计(5)绝对布局,流布局
文章目录 前言一、布局管理器二、介绍 1.绝对布局2.流布局总结 前言 Swing窗体中,每一个组件都有大小和具体的位置。而在容器中摆放各种组件时,很难判断其组件的具体位置和大小。即一个完整的界面中,往往有多个组件,那么如何将这…...
linux基础知识之文件系统 df/du/fsck/dump2fs
du du [选项][目录或者文件名] -a 显示每个子文件等磁盘占用量,默认只统计子目录的磁盘占用量 -h 使用习惯单位显示磁盘占用量,如KB,MB或者GB -s 统计总占用量,不列出子目录和文件占用量 面向文件 du -a 16 ./.DS_Store 8 ./requi…...
华为云云耀云服务器L实例评测|Elasticsearch的Docker版本的安装和参数设置 端口开放和浏览器访问
前言 最近华为云云耀云服务器L实例上新,也搞了一台来玩,期间遇到各种问题,在解决问题的过程中学到不少和运维相关的知识。 本篇博客介绍Elasticsearch的Docker版本的安装和参数设置,端口开放和浏览器访问。 其他相关的华为云云…...
8章:scrapy框架
文章目录 scrapy框架如何学习框架?什么是scarpy?scrapy的使用步骤1.先转到想创建工程的目录下:cd ...2.创建一个工程3.创建之后要转到工程目录下4.在spiders子目录中创建一个爬虫文件5.执行工程setting文件中的参数 scrapy数据解析scrapy持久…...
软件工程与计算总结(二)软件工程的发展
本章开始介绍第二节内容,主要是一些历史性的东西~ 一.软件工程的发展脉络 1.基础环境因素的变化及其对软件工程的推动 抽象软件实体和虚拟计算机都是软件工程的基础环境因素,它们能从根本上影响软件工程的生产能力,而且是软件工程无法反向…...
Appium开发
特点 开源免费支持多个平台 IOS(苹果)、安卓App的自动化都支持 支持多种类型的自动化 支持苹果、安卓应用原生界面的自动化支持应用内嵌网络视图的自动化支持手机浏览器(Chrome)中的web网站自动化支持flutter应用的自动化 支持多种编程语言 像selenium一样,可以用多…...
EGL函数翻译--eglInitialize
EGL函数翻译–eglInitialize 函数名 EGLBoolean eglInitialize(EGLDisplay display,EGLInt* major,EGLInit* minor); 参数描述 参数display: EGL要初始化的显示连接。 参数major: 输出EGL的主版本号;参数可为空。 参数minor: 输出EGL的次版本号;参数可…...
二项分布以及实现
文章目录 前言所谓二项分布就是只会产生两种结果的概率 1.概念 前言 所谓二项分布就是只会产生两种结果的概率 1.概念 下面是一个二项分布的的theano实现 import numpy as np import theano import theano.tensor as T from theano.tensor.nnet import conv from theano.ten…...
css自学框架之幻灯片展示效果
这一节,我自学了焦点图效果(自动播放,圆点控制),首先看一下效果: 下面我们还是老思路,css展示学习三个主要步骤:一是CSS代码,二是Javascript代码,三是Html代码。 一、css代码主要如…...
坦克世界WOT知识图谱三部曲之爬虫篇
文章目录 关于坦克世界1. 爬虫任务2. 获取坦克列表3. 获取坦克具体信息结束语 关于坦克世界 《坦克世界》(World of Tanks, WOT)是我在本科期间玩过的一款战争网游,由Wargaming公司研发。2010年10月30日在俄罗斯首发,2011年4月12日在北美和欧洲推出&…...
Idea上传项目到gitlab并创建使用分支
Idea上传项目到gitlab并创建使用分支 1 配置git 在idea的setting中,找到git,配置好git的位置,点击Test按钮显示出git版本号,则说明配置成功。 2 项目中引入git Idea通过VCS,选择Create Git Repository 在弹出的对话框…...
3D孪生场景搭建:参数化模型
1、什么是参数化模型 参数化模型是指通过一组参数来定义其形状和特征的数学模型或几何模型。这些参数可以用于控制模型的大小、形状、比例、位置、旋转、曲率等属性,从而实现对模型进行灵活的调整和变形。 在计算机图形学和三维建模领域,常见的参数化模…...
最短路径专题6 最短路径-多路径
题目: 样例: 输入 4 5 0 2 0 1 2 0 2 5 0 3 1 1 2 1 3 2 2 输出 2 0->1->2 0->3->2 思路: 根据题意,最短路模板还是少不了的, 我们要添加的是, 记录各个结点有多少个上一个结点走动得来的…...
【Linux】Linux常用命令—文件管理(上)
创作不易,本篇文章如果帮助到了你,还请点赞 关注支持一下♡>𖥦<)!! 主页专栏有更多知识,如有疑问欢迎大家指正讨论,共同进步! 🔥c系列专栏:C/C零基础到精通 🔥 给大…...
【Python】基于OpenCV人脸追踪、手势识别控制的求生之路FPS游戏操作
【Python】基于OpenCV人脸追踪、手势识别控制的求生之路FPS游戏操作 文章目录 手势识别人脸追踪键盘控制整体代码附录:列表的赋值类型和py打包列表赋值BUG复现代码改进优化总结 py打包 视频: 基于OpenCV人脸追踪、手势识别控制的求实之路FPS游戏操作 手…...
约束优化算法(optimtool.constrain)
import optimtool as oo from optimtool.base import np, sp, pltpip install optimtool>2.4.2约束优化算法(optimtool.constrain) import optimtool.constrain as oc oc.[方法名].[函数名]([目标函数], [参数表], [等式约束表], [不等式约数表], [初…...
如何查看postgresql中的数据库大小?
你可以使用以下命令来查看PostgreSQL数据库的大小: SELECT pg_database.datname as "database_name", pg_size_pretty(pg_database_size(pg_database.datname)) AS size_in_mb FROM pg_database ORDER by size_in_mb DESC;这将返回一个表格࿰…...
使用python-opencv检测图片中的人像
最简单的方法进行图片中的人像检测 使用python-opencv配合yolov3模型进行图片中的人像检测 1、安装python-opencv、numpy pip install opencv-python pip install numpy 2、下载yolo模型文件和配置文件: 下载地址: https://download.csdn.net/down…...
项目进展(三)-电机驱动起来了,发现了很多关键点,也遇到了一些低级错误,
一、前言 昨天电机没有驱动起来,头发掉一堆,不过今天,终于终于终于把电机驱动起来了!!!!,特别开心,哈哈哈哈,后续继续努力完善!!&…...
目标检测算法改进系列之Backbone替换为RepViT
RepViT简介 轻量级模型研究一直是计算机视觉任务中的一个焦点,其目标是在降低计算成本的同时达到优秀的性能。轻量级模型与资源受限的移动设备尤其相关,使得视觉模型的边缘部署成为可能。在过去十年中,研究人员主要关注轻量级卷积神经网络&a…...
学习 Kubernetes的难点和安排
Kubernetes 技术栈的特点可以用四个字来概括,那就是“新、广、杂、深”: 1.“新”是指 Kubernetes 用到的基本上都是比较前沿、陌生的技术,而且版本升级很快,经常变来变去。 2.“广”是指 Kubernetes 涉及的应用领域很多、覆盖面非…...
【MATLAB源码-第42期】基于matlab的人民币面额识别系统(GUI)。
操作环境: MATLAB 2022a 1、算法描述 基于 MATLAB 的人民币面额识别系统设计可以分为以下步骤: 1. 数据收集与预处理 数据收集: 收集不同面额的人民币照片,如 1 元、5 元、10 元、20 元、50 元和 100 元。确保在不同环境、不…...
【软件测试】软件测试的基础概念
一、一个优秀的测试人员需要具备的素质 技能方面: 优秀的测试用例设计能力:测试用例设计能力是指,无论对于什么类型的测试,都能够设计出高效的发现缺陷,保证产品质量的优秀测试用例。这就需要我们掌握设计测试用例的方…...
Docker-mysql,redis安装
安装MySQL 下载MySQL镜像 终端运行命令 docker pull mysql:8.0.29镜像下载完成后,需要配置持久化数据到本地 这是mysql的配置文件和存储数据用的目录 切换到终端,输入命令,第一次启动MySQL容器 docker run --restartalways --name mysq…...
五种I/O模型
目录 1、阻塞IO模型2、非阻塞IO模型3、IO多路复用模型4、信号驱动IO模型5、异步IO模型总结 blockingIO - 阻塞IOnonblockingIO - 非阻塞IOIOmultiplexing - IO多路复用signaldrivenIO - 信号驱动IOasynchronousIO - 异步IO 5种模型的前4种模型为同步IO,只有异步IO模…...
北京学做网站/百度怎样发布作品
身为职业化的职场人士,熟练使用办公软件,掌握一些电脑快捷操作方式非常重要。当你抱怨“为什么加班的总是我”的时候,先看看是不是别人10分钟搞定的事,你要用俩小时呢?一、办公最常使用的快捷键:1、复制&am…...
汉邦未来网站开发/信息流广告投放流程
今天在用python生成数据库脚本时一直出现migrate这个包不存在。但是在安装flask的时候已经安装了这个包了。 from migrate.versioning import api最后找到了方法,在ubuntu下面运行 sudo apt-get install python-migrate 命令即可。原因是flask自带的migrate的包中并…...
wordpress登录名/网络营销的定义是什么
需要用到IRremote库文件 红外遥控按键16进制编码,使用时添加前缀 0X 红外接收 .源代码 //***************** //红外接收模块测试 //***************** #include <IRremote.h> IRrecv irrecv(6); //创建红外模块对象,并绑定红外接收模块引脚 decode_results …...
建筑人才网 中高端招聘网站/全网整合营销公司
基于SSH的长途汽车票务售票系统的设计(Struts2,MySQL)(含录像)(毕业论文说明书14000字,程序代码,MySQL数据库)摘 要随着科学技术的不断提高,计算机科学日渐成熟,其强大的功能已为人们深刻认识,它已进入人类社会的各个领域并发挥着越来越重要…...
淘宝客网站做seo/优秀网站设计
代码里面写 if else 或者 switch case 语句,很常见,那么这2个写法除了姿势不一样以为,他们的效率是不是也差距比较大呢? 1,switch case 比 一个个if else快吗? 2,switch case会因为case的数据…...
wordpress 修改css样式/网站外链查询
本人是python新手,处于探索学习阶段,如果有相同爱好者 可以加我微信进行交流:fei_1911 好!废话不说,直接上代码 from urllib import request import pandas as pd from bs4 import BeautifulSoup import csv import ti…...