0
点赞
收藏
分享

微信扫一扫

java面试基础知识点罗列

晚熟的猫 2022-01-27 阅读 112

1.什么是面向对象?谈谈你对面向对象的理解。

根据处理问题的不同角度

    面向过程:更注重事情的每个步骤以及执行顺序

    面向对象:更注重于事情有哪些参与者,以及各自需要做什么

二者优势对比:

    面向过程更直接高效,面向对象更易于扩展、复用与维护。

面向对象的三大特性

封装性

内部细节对外部保持透明,外部调用者无需修改或关心内部实现。

    *举例*

    javabean的属性私有,提供getset对外访问。属性的赋值只能通过javabean本身决定,不能让外部干预

继承性

    子类共性的方法或属性直接使用父类的,不需要自己再定义,只需扩展自己个性化的方法

多态性

    分为编译时多态(方法重载)与运行时多态(方法重写)

    实现多态需要:

        一:子类继承父类并重写父类的方法

        二:父类的声明对子类的引用

2. JDK、JRE、JVM三者之间的关系

JDK:java开发工具包

JRE:java运行时环境

JVM:java虚拟机

1.png

 

3.重载与重写的区别

重载:发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,不能根据返回类型区分,发生在编译时。

重写(覆盖):发生在父子类中,方法名、参数列表必须相同,返回值范围、抛出异常范围小于等于父类,访问修饰符大于等于父类;父类中方法访问修饰符为private则子类不能重写。

 

4.==与equals

==比较的是引用,equal比较的是内容。

1.==比较的是栈中的值,基本数据类型是变量值,引用数据类型时堆中内存对象的地址值。

2.equals中默认也是使用==比较,通常会重写让它不是比较引用而是比较数据内容。

5.final的作用

修饰类:表明类不可被继承

修饰方法:表明该方法不可被子类重写,但可以重载

修饰变量:表明变量一旦被赋值不可被修改

(1)修饰成员变量

final修饰类变量,只能在静态代码块与声明时指定初始值

final修饰成员变量,可以在非静态代码块声明该变量或构造器中执行初始值

(2)修饰局部变量

可以在定义时指定默认值,也可不指定,但在使用之前一定要赋值,不允许第二次赋值

(3)修饰基本数据类型与引用数据类型

基本数据类型:在初始化后不可更改

引用数据类型:初始化后不可指向另一个对象,但引用的值是可以变得。

5.为什么局部内部类和匿名内部类只能访问局部final变量

内部类与外部类属于同一级别,编译会产生两个.class文件,内部类不会因为定义在方法中就随着方法的执行完毕就销毁。

但是当外部类方法结束时,局部变量会被销毁,而内部类对象回访问一个不存在的对象,因此将局部变量复制一份作为内部类的成员变量。

但如果对内部类中的局部变量进行修改,方法中的局部变量也会修改,因此使用final修饰局部变量,初始化后便不可更改,保证内部类成员变量与方法的局部变量的一致。

 

6.String、StringBuffer、StringBuilder

String:

使用final修饰的字符数组进行保存,不可变。如需修改,则需新建对象。

StringBuffer

使用无final修饰的字符数组进行保存,StringBuffer的方法都是synchronized修饰的,线程安全

StringBuilder

使用无final修饰的字符数组进行保存,线程不安全

场景:经常需要改变字符串内容,使用后面两个

优先使用StringBuilder,多线程使用共享变量时使用StringBuffer

 

7.接口和抽象类的区别

抽象类可以存在定义构造函数,接口不可以定义

抽象类中可以有抽象方法和具体方法,接口中只能有抽象方法

抽象类中可以有静态方法,接口中不可以有静态方法

抽象类只能继承一个,接口可以实现多个。

抽象类中成员变量可以是各种类型的,接口中只能是public static final类型的

JDK8中的改变

1.允许接口中有具体实现的方法,用default修饰

2.允许接口中可以包含静态方法。

抽象类表达的是is a的关系,包含实现子类的通用特性。

接口表达的是like a的关系,接口的核心是定义行为,对实现类的主体、如何实现并不关心。

关注本质用抽象类,关注操作用接口。

 

8.List和Set的区别

List:有序,可重复按照对象进入顺序保存对象,允许有多个NULL对象,可以使用get(int index)获取指定下标元素,也可使用Iterator取出所有元素,再逐一遍历。

Set:无序,不可重复,最多有一个NULL元素对象,取元素只能使用Iterator取出所有元素,再逐一遍历。

9.HashCode和equals

HashSet如何检查重复

对象加入HashSet时,先计算对象的HashCode判断对象加入的位置,看位置是否有值,如果没有,就加入,如果有值,会调用equals()方法检查两个对象是否相同,如果相同,不会加入成功,如果不同,重新放到其他位置,减少equals()判断,提高执行速度。

 

HashCode:确定该对象在哈希表中的索引位置。

两个对象相等,HashCode一定相同

两个对象相等,equals()判断返回true

两个对象HashCode相等,它们不一定相等

重写equals()时,HashCode也必须重写

HashCode默认对堆中的对象产生独特值。

10.ArrayList与LinkedList的区别

ArrayList:基于动态数组,使用连续内存存储,适合下标访问。

扩容机制:因为数组长度固定,超出长度时需要新建数组,将旧数组的数据拷贝到新数组。如果不是插入末尾会涉及到元素的移动

LinkedList:基于链表,可存储在分散的内存中,适合数据的删除与插入操作,不适合查询:需要逐一遍历。

遍历LinkedList时不要使用for循环,因为每次循环体内获取某一元素时,都需要重新遍历。

ArrayList的增删未必比LinkedList慢

1.如果是在末尾进行增删并且初始容量足够,不涉及到元素的移动与复制数组,当数据量有百万级,速度比LinkedList要快(需要创建大量Node对象)

2.删除位置在中间时,由于LinkedList消耗主要是在遍历上,ArrayList主要是在移动与复制上。当数据量有百万级,速度比LinkedList要快。

11.HashMap与HashTable的区别?底层实现是什么?

1.区别:

(1)HashMap是非线程安全的,HashTable是线程安全的,方法都是synchronized进行修饰过的。

(2)HashMap允许key和value为null,HashTable不允许

2.底层实现:数组加链表

jdk8之后,当链表长度大于8,数组长度超过64,链表转变为红黑树,元素以内部类node节点存在

 

当往HashMap中添加key-value时,首先计算key的hash,根据hash确定存放位置。若该位置没有元素,直接插入,如果有,则迭代该处元素链表并依次比较key的hash。如果hash值相等,进行equals比较,如果也相等,使用新的Entry的value覆盖原来节点的value,如果hash值相等但key值不相等,将该节点插入该链表表头。

key为null存在下标为0的位置

数组扩容:创建新的数组,将旧数组的元素复制到新数组中。

12.ConcurrentHashMap原理,jdk7和jdk8版本区别

jdk7:

 

数据结构:ReentrantLock+Segment+HashEntry,一个Segment包含一个HashEntry,每个HashEntry又是一个链表结构

元素查询:二次hash,第一次确定在哪个Segment,第二次hash定位到元素所在链表的头部

锁:分段锁Segment Segment继承ReentrantLock,锁定操作的Segment,其他Segment不受影响,数组扩容不会影响其他Segment

get方法无需加锁,volatile保证

 

-jdk8

数据结构:synchronized + CAS + Node + 红黑树,node的val和next都用volatile修饰,保持可见性

查找、替换、赋值都用CAS(乐观锁机制)

锁:锁链表的head结点,不影响其他元素读写,锁粒度更细,效率更高,扩容时,阻塞所有的读写操作、并发扩容

读操作无锁:

node的val和next都用volatile修饰,

数组用volatile修饰,保证扩容为线程感知

 

13.什么是字节码?使用字节码的好处

java源程序经编译器编译后变成字节码,然后由解释器将字节码文件转换为特定系统的机器码,在特定的机器上执行。体现了java的编译与解释并存的特点。

 

java源程序-->编译器-->jvm可执行的java字节码-->jvm-->jvm解释器--->机器可执行的的二进制机器码-->程序运行

 

字节码好处

 

通过字节码方式,解决了传统解释型语言执行效率低的问题,又保留了解释型语言可移植的特点。而且字节码并不专对一种特定机器,java程序可无须重新编译便可在不同计算机运行

 

14.Java类加载器

启动类加载器,负责加载%JAVA_HOME%/lib下的jar包和class文件

扩展类加载器,负责加载%JAVA_HOME%/lib/ext下的jar包和class文件

应用程序类加载器,是自定义类加载器的父类,负责加载classpath下的类文件

可以通过继承ClassLoader实现自定义类

 

15.双亲委派机制

当一个类接收到类加载请求,他首先不会尝试自己去加载这个类,而是向上委派查找缓存,如果找到则直接返回,没有继续往上委派,委派到顶层之后,缓存中还没有,则到加载路径中查找,有则加载返回,没有这向下查找,直到发起加载的加载器为止。

双亲委派机制的优点:

 

安全性,避免用户自己编写的类动态替换java核心类,如String

避免类的重复加载,JVM中区分不同类,不仅根据类名,相同的class文件被不同的ClassLoader加载就是不同的两个类

16.Java中的异常体系

java中所有的异常都来自顶级父类Throwable

Throwable下有两个子类Exception和Error

 

Error时程序无法处理的错误,一旦出现,程序被迫停止

Exception不会导致程序停止,又分为运行时异常RunTimeException与检查异常CheckedException

RunTimeException经常发生在程序运行时,会导致当前线程执行失败,CheckedException发生在编译过程,会导致编译不通过

 

17.GC如何判断对象是否可以被回收

引用计数法:每个对象有个引用计数属性,新增一个引用计数+1,引用释放-1,计数为0可以回收

可达性分析:从GC ROOTS开始向下搜索,当一个对象没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机就判断是可回收对象。

GC ROOTs的对象有:

 

虚拟栈中引用的对象

方法区中常量引用的对象

方法区中类静态变量属性引用的对象

本地方法栈中JNI引用的对象

可达性算法

可达性算法中的不可达对象并不是立即死亡的。对象被宣告死亡至少需要经理两次标记过程:第一次是经过可达性分析发现没有与GC ROOTs相连的引用链,第二次是在虚拟机自动建立的Finalizer队列中判断是否需要执行finalize()方法

当对象不可达时,GC先判断该对象是否重写了finalize()方法,若未重写,则将其回收。否则将其放入Finalizer队列中,低优先级线程执行该队列中对象的finalize方法。执行完finalize方法后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象复活

 

每个对象只能执行一次finalize方法

 

18.线程的生命周期?线程有几种状态

操作系统层面

1.线程有五种状态:创建、就绪、运行、阻塞、死亡

2.阻塞又分为三种:

 

等待阻塞:运行的线程执行wait()方法,释放占用的资源,JVM将其放入等待池中。不能自动唤醒,必须依靠其他线程调用notify或notifyAll方法才能唤醒,wait是Object类的方法

同步阻塞:运行中的线程在获取对象的同步锁时,如果该同步锁被别的线程占用,则将该线程放入锁池

其他阻塞:运行的线程调用sleep或join方法,或者发出I/O请求,JVM就会将该线程设为阻塞状态。当sleep状态超时、join等待线程终止、或者I/O请求处理完毕,线程重新转入就绪状态。sleep是Thread类的方法

1.新建状态(New):创建一个线程

2.就绪状态(Runnable):线程创建之后,其他线程调用该对象的start方法,该线程处于可运行线程池中,等待获取CPU执行权

3.运行状态(Running):就绪的线程获取CPU执行权,运行代码

4.阻塞(Blocked):由于某些原因放弃CPU使用权,暂时停止运行。当线程进入就绪状态,才有机会运行

5.死亡状态(Dead):线程执行结束或异常退出run方法,线程结束生命周期。

 

#18.2简述java线程的状态

 

java线程的状态有六种:

 

新建(New):新建状态,线程被创建且未启动,此时还未调用 start 方法。

运行(Runnable):运行状态。其表示线程正在JVM中执行,但是这个执行,不一定真的在跑,也可能在排队等CPU。

阻塞(Blocked):线程等待获取锁,锁还没获得。

等待(waiting):等待状态。线程内run方法运行完语句Object.wait()/Thread.join()进入该状态

限期等待(timed_waiting):限期等待。在一定时间之后跳出状态。调用Thread.sleep(long) Object.wait(long) Thread.join(long)进入状态。其中这些参数代表等待的时间。

结束(terminated):结束状态。线程调用完run方法进入该状态。

19.sleep()、wait()、join()、yield()

1.sleep是Thread类的方法,wait()是Object类的方法

2.sleep方法不会释放lock,但是wait会释放,而且会加入等待队列

3.sleep方法不需要依赖synchronized,可以在任何地方使用,但是wait方法需要依赖synchronized

4.sleep用于当前线程的休眠,wait用于多线程之间的通信

5.sleep会让出cpu并强制上下文切换,而wait不一定,wait后还是有机会重新竞争到锁继续执行。

 

yield()执行后直接进入就绪状态,释放cpu执行权,但保留cpu执行资格,有可能下次调度还会让该线程获取到执行权继续执行

 

join()执行后进入阻塞,例如在线程B中调用线程A的join(),线程B进入阻塞,直到线程A结束或中断线程

 

20.对线程安全的理解

不是线程安全,应该是内存安全,堆是共享内存,可被所有线程访问。

当多个线程访问一个对象时,如果不进行额外的同步控制或者协调操作,通过调用这个对象的行为都可以获取正确的结果,我们就说这个对象是线程安全的。

堆是进程和线程共有的空间,分全局堆和局部堆,全局堆是未分配的空间,局部堆是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行时可以像系统索取额外的堆,用完要还给操作系统,不然就是内存泄露。

Java中,堆是Java虚拟机管理的内存最大一块,所有线程共享的内存区域,在虚拟机启动时创建。堆所存在的内存区域的唯一目的就是存放实例,几乎所有对象实例以及数组都在这里分配内存。

 

栈是每个线程锁独有的,每个线程的栈相互独立,是线程安全的

 

目前主流操作系统都是多任务的,为保证安全,每个进程只能访问分配给自己的内存空间,不能访问别的进程。但是在每个进程的内存空间中都有一块公共区域,即堆,进程内所有线程都可以访问到该区域,这就是问题的潜在原因。

 

21.Thread和Runable的区别

Thread和Runable的实质是继承关系。用法上,如果有复杂线程操作需求,就选择继承Thread,如果只是简单执行一个任务,就实现Runnable。

 

22.谈谈对守护线程的理解

定义:守护线程是为所有非守护线程提供服务的线程;任何一个守护线程都是整个JVM中所有非守护线程的保姆

优先级:守护线程优先级较低

设置:通过setDaemon(true)来设置守护线程;将一个用户设置为守护线程的方式是在线程对象创造之前用线程对象的setDaemon方法。

Daemon线程中产生的子线程也是Daemon的。

GC垃圾回收线程:就是一个典型的守护线程,当程序中不再有任何运行的Thread,程序不会产生垃圾,当垃圾回收线程是JVM仅剩的线程时,垃圾回收线程会自动离开。它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。

生命周期:守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。也就是说守护线程不依赖于终端,但是依赖于系统,与系统“同生共死”。当 JVM 中所有的线程都是守护线程的时候,JVM 就可以退出了;如果还有一个或以上的非守护线程则 JVM 不会退出。

23.Synchronized与Lock的区别

Synchronized是内置Java关键字,Lock是一个Java类

Synchronized无法判断获取锁的状态,Lock可以判断是否获取到了锁

Synchronized自动释放锁,Lock手动释放锁,如果不释放,就会死锁。

Synchronized 线程1(获得锁,阻塞)、线程2(等待,傻傻的等);Lock不一定会等待下去:tryLock()

Synchronized 可重入锁,不可中断,非公平;Lock 可重入锁,可以判断锁,非公平(可以设置)

Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码

24.生产者消费者问题中虚假唤醒问题

生产者消费者问题的三步:判断等待、业务、通知

虚假唤醒:当一个条件满足时,很多线程都被唤醒了,但是只有其中部分是有用的唤醒,其它的唤醒都是无用功

1.比如说买货,如果商品本来没有货物,突然进了一件商品,这是所有的线程都被唤醒了,但是只能一个人买,所以其他人都是假唤醒,获取不到对象的锁

 

把 while (product >=1) {}换成 if (product >=1) {}就会出现虚假唤醒

因为if只会执行一次,执行完会接着向下执行if()外边的而while不会,直到条件不满足才会向下执行while()外边的。

 

避免虚假唤醒需要使用While()

 

25.集合类不安全:并发情况下的ArrayList不安全,如何解决?

解决方案:

List

 

1.使用Vector: List<> list = new Vector<>();

2.使用集合类的Collections.synchronizedList():

List<> list = new Collections.synchronizedList()

3.写入时复制 CopyOnWrite:List<> list = new CopyOnWriteArrayList<>();读写分离

CopyOnWrite在写入时复制,可以避免写入时覆盖,而Vector底层使用synchronized方法,效率较低

Set

 

1.使用集合类的Collections.synchronizedSet(new HashSet<>()):

2.写入时复制 CopyOnWrite:Set<> set = new CopyOnWriteArraySet<>();读写分离

Map

 

使用ConcurrentHashMap<>():Map<> map = new ConcurrentHashMap<>();

25.简述CountDownLatch、CyclicBarrier以及Semaphore

CountDownLatch:countDownLatch.countDown()&countDownLatch.await()

countDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。

是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,调用countDown方法,计数器的值就减1,当计数器的值为0时,表示所有线程都执行完毕,然后在等待的线程就可以恢复工作了。只能一次性使用,不能reset。

 

CyclicBarrier:CyclicBarrier.await()

主要功能和countDownLatch类似,也是通过一个计数器,使一个线程等待其他线程各自执行完毕后再执行。但是其可以重复使用(reset)。

 

Semaphore:信号量semaphore.acquire()&semaphore.release()

Semaphore 的构造方法参数接收一个 int 值,设置一个计数器,表示可用的许可数量即最大并发数。使

用 acquire 方法获得一个许可证,计数器减一,使用 release 方法归还许可,计数器加一。如果此时计

数器值为0,线程进入休眠。

 

26.Collection类的继承关系以及阻塞队列四组API

Collection类的继承关系

Collection集合.png

 

阻塞队列四组API

阻塞队列四组API.png

 

同步队列

SynchronousQueue:不存储元素的阻塞队列,每一个存储必须等待一个取出操作

27.简述线程池

缺点:没有线程池的情况下,多次创建,销毁线程开销比较大。

线程池的作用:线程复用、节省资源、方便控制管理、控制最大并发数

线程池创建线程时,会将线程封装成工作线程 Worker,Worker 在执行完任务后还会循环获取工作队列中的任务来执行。

线程池执行任务:

 

1.核心线程池未满,创建一个新的线程执行任务

2.核心线程池已满,工作队列未满,将线程存储在工作队列

3.工作队列已满,线程池小于最大线程数就创建一个新线程处理任务

4.超过最大线程数,按照拒绝策略处理任务

27.线程复用的原理

在线程池中,同一个线程可以从阻塞队列中不断获取新任务来执行,核心原理在于线程池对线程封装成工作线程worker,并不是每次执行任务都调用start()方法,而是去让线程执行一个循环任务,在循环任务中不停检查是否有任务需要执行,如果有则通过调用run()方法来执行。

28.线程池:三大方法&七大参数&四大拒绝策略

三大方法:使用Executors工具类创建线程池

 

单个线程:Executors.newSingleThreadExecutor()

创建一个固定的线程池大小:Executors.newFixedThreadPool(5)

可动态调节的线程池:Executors.newCachedThreadPool()

七大参数:使用ThreadPoolExecutor创建线程池

 

corePoolSize:常驻核心线程数。超过该值后如果线程空闲会被销毁。

maximumPoolSize:线程池能够容纳同时执行的线程最大数。

keepAliveTime:线程空闲时间,线程空闲时间达到该值后会被销毁,直到只剩下 corePoolSize 个

线程为止,避免浪费内存资源。

4.TimeUnit:空闲时间单位(TimeUnit.SECONDS)

workQueue:工作队列(常用阻塞队列--new LinkedBlockingQueue<>())

threadFactory:线程工厂,用来生产一组相同任务的线程(Executors.defaultThreadFactory())

handler:拒绝策略。有以下几种拒绝策略:

四大拒绝策略

 

1.AbortPolicy:丢弃任务并抛出异常

2.DiscardPolicy 表示直接抛弃当前任务但不抛出异常。

3.DiscardOldestPolicy 抛弃队列里等待最久的任务并把当前任务加入队列

4.CallerRunsPolicy: 重新尝试提交该任务(哪儿来的去哪儿)

29.线程池中阻塞队列的作用?为什么先添加到队列而不是先创建最大线程?

与一般队列的对比:

 

一般队列只能保持固有长度的缓冲区,如果任务数超出缓冲区,就无法保留当前任务了。

而阻塞队列可以通过阻塞来保留住当前想要继续入队的任务,与此同时,阻塞队列自带阻塞与唤醒功能,当任务队列中没有任务时,线程池通过阻塞队列的take方法挂起,维持核心线程的存活,不至于一直占用cpu。

29.线程池的最大大小(maximumPoolSize)应该如何去设置?

1.CPU密集型:CPU有多少核,就是多少-->

Runtime.getRuntime().availableProcessors()

2.IO密集型: 判断程序中十分耗IO的线程,例如有15个大型任务耗IO资源,一般选择其两倍。

30.四大函数式接口

1.函数式接口:接收传入参数,返回类型R

Function func = (str)->{return str;};

2.断定型接口:接收传入参数,返回布尔类型

Predicate<String> pre = (str) ->{return str.isEmpty();};

3.消费型接口:接收传入参数,不返回

Consumer<String> con = (str) -> { System.out.println(str); };

4.供给型接口:不接受参数,返回类型R

Supplier<Integer> sup = () -> {return 1024;};

30.谈一谈ThreadLocal

ThreadLocal是线程共享变量。ThreadLocal有一个静态内部类ThreadLocalMap,其Key是ThreadLocal对象,值是Entry对象,ThreadLocalMap为每个线程私有。

image.png

 

ThreadLocal存在内存泄露问题:

 

由于ThreadLocalMap的生命周期与Thread一样长,如果没有手动删除对应Key就会导致内存泄露,而不是因为弱引用

解决方法:

 

1.每次使用完,ThreadLocal调用remove()方法清除数据

2.将ThreadLocal变量定义为private static,这样就一直存在ThreadLocal强引用,保证能通过Key访问到Value值,进而清除。

31.内存泄露&四中引用

内存泄露:不再被使用的对象或者变量占用的内存不能被回收,就是内存泄露。

引用:如果reference类型中的数据中存储的数值是另一块内存的起始地址,就成这块内存代表一个引用

 

强引用:Java中默认引用是强引用,只要强引用存在,垃圾回收器永远不会回收被引用的对象,哪怕内存不足,只会抛出OOM,不会去回收

软引用:用于描述一些非必须但有用的对象,内存够足时,软引用对象不会被回收,只有在内存不足时,才会回收软引用对象,如果内存还是不够,才会抛出OOM,这种特性使他往往用于实现缓存技术。在 JDK1.2 之后,用java.lang.ref.SoftReference类来表示软引用。

弱引用:比软银用的强度更弱。只要JVM进行垃圾回收,那些被弱引用关联的对象都会被回收。在 JDK1.2 之后,用java.lang.ref.WeakReference来表示弱引用。

虚引用:最弱的引用关系。与其他几种不同,不会决定对象的生命周期,而是被用来跟踪对象被垃圾回收器回收的活动,必须与引用队列联合使用。当垃圾回收器准备回收一个对象的时候,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。

32.并发、并行、串行

并行:时间重叠,两个任务在同一时刻互不干扰执行

并发:允许两个任务互相干扰,两者交替执行,同一时间只能有一个任务执行

串行:多个任务挨个执行,时间不可能发生重叠

 

33.并发三大特性:原子性、可见性、有序性

原子性:指在一个操作中,cpu不可以中途暂停再调度,即不被中断操作,要么全部执行完成,要么全部执行失败。

可见性:当一个线程修改了共享变量,其他线程能够立即得知被修改过。关键字volatile、synchronized、final

有序性:虽然多线程存在并发和指令优化等操作,但在本线程内观察该线程的所有执行操作都是有序的。

34.volatile关键字

可见性:当一个线程修改了共享变量,其他线程能够立即得知被修改过

不保证原子性:原子性:一个操作不能被打断,要么全部执行完毕,要么不执行。

禁止指令重排(有序性)

35.什么是指令重排

在程序执行时,为提高性能,编译器和处理器往往会对指令进行重排,而不是按照原来的顺序执行。

重排序分为以下三种:

 

1.编译器优化的重排序:编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。

2.指令级并行的重排序:现代处理器采用了指令级并行技术,来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。

3.内在系统的重排序:由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

如何禁止指令重排?

 

内存屏障:禁止上面的指令与下面的指令顺序进行交换

举报

相关推荐

0 条评论