18年秋招JAVA面经精心整理总结

所需积分/C币:3 2018-12-27 16:14:04 1.14MB PDF
46
收藏 收藏
举报

春招加上秋招,翻阅各个论坛,终结各个公司的开发技术面试问题,再加上自己实战面试的公司,精心整理,呕血总结,明了本手册,拿offer如探囊取物
equals0默认是通过=来判断,及判断两个对象的内存地址是不是一样的。所以要重写 3 Hashtable和 ConcurrentHashmap、这两者有什么区别?分 段锁的具体细节? hashtable继承于 Dictionary类,实现了Map, Cloneable, Serializable接口。存储的是内容是键值对,通过 拉链法实现哈希表。 HashTable容器使用 synchronized来保证线程安全,但在线程竞争激烈的情况下 Hash Table的效率非常低下 ·〔 oncurrentHashmap在 concurrent包中。使用的是分段锁的概念,把一个大的MAP拆分成几个类似 hashtable结构根据 key. hashCodet来决定把key放到哪个 Hash Table中。就是把Map分成了N个 Segment, Segment是一种可重入锁 Reentrantlock,pu和get的时候,都是现根据 key hashCode算出放到哪个 Segment中,只有在同一个分段内才存在竟态关系。比如、原来只能一个线程进入,现在却能同时16个(默 认是分16段)写线程进入,提升并发性, (写线程才需要锁定,而读线程并不锁定,只有在求size等操作时才需要锁定整个表)。 ConcurrentHash Map中的 HashEntry相对于 HashMap中的 Entry有一定的差异性: HashEntry中的 value以及ηeκt都被 volatile(不保留副本)修饰;这样在多线程读写过程中能够保持它们的可见性。 ConcurrentHashMap中主要实体类就是三个; ConcurrentHashMap(整个Hash表), Segment(桶) HashEntry(节点 Hash table的线程安全使用的是一个单独的全部Map范围的锁, ConcurrentHash Map抛弃了 HashTable的单 锁机制,使用了锁分离技术,使得多个修改操作能够并发进行,只有进行SZE操作时 ConcurrentHash Map会 锁住整张表。 · Hashtable的pu和get方法都是同步方法,而 ConcurrentHashMap的get方法多数情况都不用锁,put方法需 要锁。但是 ConcurrentHashMap不能替代 Hash Table,因为两者的迭代器的一致性不同的, hash table的迭代 器是强一致性的,而 concurrenthashmap是弱一致的。 ConcurrentHash Map的get, clear, iterator都是 弱一致性的。 1)分段锁 ·〔 oncurrentHashMap使用分段锁 Segment来保护不同段的数据,在插入和获取元素的时候,必须先通过哈 希算法定位到 sEgment。get操作高效,ge过程不需要加锁,除非读到的值是空的才会加铡重读,get方法里 将要使用的共享变量都定义成 volatile,如用于统计当前 Segement大小的 count字段和用于存储值的 HashEntryl的vaue。(定义成 volatile的变量,能够在线程之间保持可见性,能够被多线程冋时读,并且保 证不会读到过期的值,但是只能被单线程写)在get操作里只需要读不需要写共享变量 count和 value,所以可 以不用加锁 ·put方法里需要对共亨变量进行写入操作,所以为了线程安全,在操作共享变量时必须得加锁。PU方法首先 定位到 Segment,然后在 Segment里进行插入操作。插入操作需要经历两个步骤,第一步判断是否需要对 Segment里的 Hash Entry数组进行扩容,第二步定位添加元素的位置然后放在 HashEntry数组里。 扩容:在插入元素前会先判断 Segmen里的 HashEntry数组是否超过容量( threshold),如果超过阀值,数 组进行扩容。扩容的时候首先会创建一个两倍于原容量的数组,然后将原数组里的元素进行再hash后插入到 新的数组里。为了高效 ConcurrentHashMap不会对整个容器进行扩容,而只对某个 segmen进行扩容。 ·size:安全的做法,是在统计size的时候把所有 Segment.的ρut, remove和 Iclean方法全部锁住,但是这种 做法显然非常低效。如果统计的过程中,容器的 count发生了变化,则再采用加锁的方式来统计所有 SegmentI的大小。使用 mod Coun变量,在put, remove和ean方法里操作元素前都会将变量 mod Count进 行加1,那么在统计size前后比较 modcount是否发生变化,从而得知容器的大小是否发生变化 解决hash冲突的方法 1开放定址法(线性探测再散列,二次探测再散列,伪随机探测再散列)2.再哈希法3.链地址法( ava hashmap就 是这么做的)4建立一个公共溢出区(冲突的都放在另一个地方,不在表里面) 4乐观锁与悲观锁原理及实现 1)乐观锁 ·总是认为不会产生并发问题,每次去取数据的时候总认为不会有其他线程对数据进行修改,因此不会上锁, 但是在更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用版本号机制或CAS操作实现。 version方式:一般是在数据表中加上一个数据版本号 version字段,表示数据被修改的次数,当数据被修改 时, version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取 version值,在提交更新时,若 刚才读取到的 version值为当前数据库中的 version值相等时才更新,否则重试更新操作,直到更新成功 CAS操作方式:即 compare and swap或者 compare and set,涉及到三个操作数,数据所在的内存值,预 期值,新值。当需要更新时,判断当前內存值与之前取到的值是否相等,若相等,则用新值更新,若失败则 重试,一般情况下是一个自旋操作,即不断的重试。 2)悲观锁 总是假设最坏的情况,每次取数据时都认为其他线程会修改,所以都会加锁(读锁、写锁、行锁等),当其 他线程想要访问数据时,都需要阻塞挂起。可以依靠数据库实现,如行锁、读锁和亐锁等,都是在操作之前 加锁,在Java中, synchronized的思想也是悲观锁。 5BO与N|o、AO的区别 Inputstream用于字节流读文件, inputstream read= new fileinputstream0 outputstream用于字节流写文件 reader用于字符流读文件 writer用于字符流写文件 bufferinputstream, bufferoutputstream用于字节流,速度更快,对流进行写入时提供一个 buffer来提高|O 效率。在进行磁盘或网络O时,原始的 nputstream对数据读取的过程都是一个字节一个字节操作的,而 BufferedInputStream在其内部提供了一个 buffer,在读数据时,会一次读取一大块数据到 buffer中,这样比 单字节的操作效率要高的多,特别是进程磁盘O和对大量数据进行读写的时候。 O的方式通常分为几种,同步阻塞的B|O、同步非阻塞的NO、异步非阻塞的AO · Java blo:同步并阳塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动 一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善 ☆如果BIo要能够同时处理多个客户端请求,就必须使用多线程,即每次 accept阻塞等待来自客户端请求,一旦 受到连接请求就建立兴通信套接字**同时开启一个*新的线程*来处理这个*套接字的数据读写请求,然后立刻又 继续 accept等待其他客户端连接请求,即为每一个客户端连接请求都创建一个线程来单独处理。 B工O方式适用于**连接数目比较小且固定*的场景,这种方式对服务器资源要求比较高,并发局限于应用中。 · Java Nio:同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多潞复 用器上,多路复用器轮询到连接有0请求时才启动一个线程进行处理。NO类,它可以使用 Native函数库 直接分配堆外内存,然后通过一个存储在Java堆里的 DirectByte Buffer对象作为这块内存的引用进行操作。 这样能在一些场景中显著提高性能,因为避免了在」ava堆和Natiⅳe堆中来回复制数据。 ☆用多路复用器,可以监听来自多个客户端的IO事件 在一个线程中就可以调用多路复用接口(java中是 select)阻塞同时监听来自多个客户端的Io请求,一旦有 收到Io请求就调用对应函数处理。 ☆NIo适合处理连*接数目特别多,但是连接比较短**(轻操作)的场景,Jety,Mina,zo0 Keeper等都是 基于 Java nlo实现。 Java AlO(NO.2):异步非阻塞,服务器实现模式为—个有效请求一个线程,客户端的/O请求都是由OS先完 成了再通知服务器应用去启动线程进行处理 6 Nginx(读 engine x)与 Redis 旨在用最少资源处理高并发问题 · Nginx是一个web服务器,提供网页服务 redis可做数据库或缓存,大多数情况下被拿来做缓存 7 JAVA C与」N ·c就是持续化集成,就是在一个大型项目中,当有人提交了后,C务器会去把更改的代码自动拿下来编译, 然后发布,让程序员进行测试 · JNIGjava native interface)java本地接口,负责与其它语言通心(C/C++)。 8」AVA类加载机制 类是在运行期间动态加载的 ·丿M类加载机制分为五个部分:加载,验证,准备,解析,初始化(使用和卸载7个阶段)。 1)加载 加载过程完成以下三件事 通过一个类的全限定名来获取定义此类的二进制字节流 将这个字节流所代表的静态存储结构转化为方法区的运行时存储结构。 在内存中生成一个代表这个类的cass对象,作为方法区这个类的各种数据的访问入口 2)验证 这一阶段的主要目的是为了确保 Class文件的字节流中包含的信息是否符合当前虚拟机的要求 并且不会危害虚拟机自身的安全。 3)准备 准备阶段是正式为类变量分配内存并设置类变量的初始值阶段,即在方法区中分配这些变量所使用的内存空间。 初始值概念,比如一个类变量定义为: public static int v=8080;实际上变量v在准备阶段过后的初始值为0 而不是8080,将ν赋值为8080的ρ outstate指令是程序被编译后,存放于类构造器< client>方法之中,但如果是 public static final intν=8080;在编译阶段会为v生成 Constantvalue属性;在准备阶段虚拟机会根据 Constantvalue属性将ν赋值为8080。 4)解析 解析阶段是指虛拟机将常量池中的符号引用替换为直接引用的过程。符号引用就是c1aS5文件中的: 符号引用和直接引用的概念:符号引用与虚拟机实现的布局无关,引用的目标并不一定要已经加载到内存中。各种虚拟 机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的因为符号引用的字面量形式明确定义在Java 虚拟机规范的 Class文件格式中。直接引用可以是指向目标的指针,相对偏移昰或是一个能间接定位到目标的句柄。如 果有了直接引用,那引用的目标必定已经在内存中存在 5)初始化 初始化阶段是类加载最后一个阶段,前面的类加载阶段之后,除了在加载阶段可以自定义类加载器以外, 其它操作都由MM主导。到了初始阶段,才开始真正执行类中定义的av程序代码。 初始化阶段是执行类构造器< client>方法的过程。< client>方法是由编译器自动收集类中的类变量旳赋值操作和静态 语句块中的语句合并而成的。虛拟机会保证< client>方法执行之前,父类的< client>方法已经执行完毕。 p.s:如果一个类中没有对静态变量赋值也没有静态语句块,那么编译器可以不为这个类生成O方法。 注意以下几种情况不会执行类初始化: 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。 ·定义对象数组,不会触发该类的初始化 ·常量在编译期间会存入调用类的常量池,本质上并没有直接引用定义常量的类,不会触发定义常量所在的 类 ·通过类名获取Cass对象,不会触发类的初始化、 ·通过 Class forname加载指定类时,如果指定参数 Initialize为 false时,也不会触发类初始化,其实这个参数是 告诉虚拟机,是否要对类进行初始化。 ·通过 Classloader默认的 oad class方法,也不会触发初始化动作。 6)类加载器 丿M把加载动作放到MM外部实现,以便让应用程序决定如何获取所需的类,丿ⅥM提供了3种类加载器:启动 类加载器 Bootstrap ClassLoader:负责加载 JAVA_HOMEib目录中的,或通过 Xbootclasspath参数指定 路径中的,且被虚拟机认可(按文件名识别,如 rt jar)的类。扩展类加载器( Extension classloader):负责 加载 AVA HOMEViblext目录中的,或通过 java ext dirs系统变量指定路径中的类库。应用程序类加载器 ( Application ClassLoader):负责加载用户路径( classpath)上的类库。 ·八MM通过双亲委派模型进行类的加载,也可以通过继承 ava. lang ClassLoader实现自定义的类加载器。 9Java内存空间理解 ·堆:堆主要存放类的实例,即」ava在运行过程中new出来的对象,凡是通过new生成的对象都存放在堆中, 对于堆中的对象生命周期的管理由ava虚拟机的垃圾回收机制G<进行回收和统一管理。类的非静态成员变量 也放在堆区,其中基本数据类型是直接保存值,而复杂类型是保存指向对象的引用,非静态成员变量在类的 实例化时开辟空间并且初始化。 σ所有对象实例都在这里分配内存。是垃圾收集的主要区域("GC堆")。现代的垃圾收集器基本都是采 用分代收集算法,主要思想是针对不同的对象采取不同的垃圾回收算法。虚拟机把Java堆分成以下三 块:新生代( Young generation)老年代( old generation)永久代( Permanent generation)当 个对象被创建时,它首先进入新生代,之后有可能被转移到老年代中。新生代存放着大量的生命很短 的对象,因此新生代在三个区域中垃圾回收的频率最高。为了更高效地进行垃圾回收,把新生代继续划 分成以下三个空间:Eden(伊甸园) From survivor(幸存者) To Survivor ·栈:每个线程私有的区域,它的生命冑期与线程相同,一个线程对应一个va栈,每执行一个方法就会往栈 中压入一个元素,这个元素叫栈帧”,而栈帧中包括了方法中的局部变量、用于存放中间状态值的操作栈 方法区:是各个线程共享的内存区域,它用于存储cass二进制文件,包含了虚拟机加载的类信息、常量、静 态变量、即时编译后的代码等数据。它有个名字叫做 Non-Heap(非堆),目的是与ava堆区分开方法区是线程 安全的。包括(常量池、静态域); 1)常量池:常量池是方法区的—部分内存。常量池在编译期间就将-部分数据存放于该区域,包含基本数据 类型如int、long等以fna声明的常量值,和 String字符串、特别注意的是对于方法运行期位于栈中的局部变 量 String常量的值可以通过 String intern方法(节省内存)将该值置入到常量池中。 2)静态域:位于方法区的一块内存。存放类中以 static声明的静态成员变量 ·本地方法栈:用来表示执行本地方法的,本地方法栈存放的方法调用本地方法接口,最终调用本地方法库, 实现与操作系统、硬件交互的目的。 ·PC寄存器:类已经加载,实例对象、方法、静态变量都去了自己改去的地方,程序该怎么执行,哪个方法先 执行,哪个方法后执行,这些指令执行的顺序就是PC寄存器在管,它的作用就是控制程序指令的执行顺序。 ·总结:栈:为即时调用的方法开辟空间,存储局部变量值(基本数据类型),局部变量引用。注意:局部变量 必须手动初始化。堆:存放引用类型的对象,即new出来的对象、数组值、类的非静态成员变量值(基本数据 类型)、非静态成员变量引用。其中非静态成员变量在实例化时开辟空间初始化值。非静态成员变量是放在堆 的对象中。方法区:存放lass二进制文件。包含类信息、静态变量,常量池( String字符串和fina修饰的常量 值等),类的版本号等基本信息。因为是共享的区域,所以如果静态成员变量的值或者常量值( String类型的值 能够非修改,具体请査看博客被修改了直接就会反应到其它类的对象中。 10垃圾回收机制GC( Garbage Collection) ·程序计数器、虚拟机栈和本地方法栈这三个区域属于线程私有的,只存在于线程的生命周期内,线程结束之 后也会消失,因此不需要对这三个区域进行垃圾回收。垃圾回收主要是针对」ava堆和方法区进行。 1)判断一个对象是否存活 ·引用计数算法法:未用,原因,统计计数浪费时间,无法解决循环引用问题 ·可达性分析:通过 GC Roots作为起始点进行搜索,能够到达到的对象都是存活的,不可达的对象可被回 收。2)方法区GC:主要是对常量池的回收和对类的卸载。 3)垃圾收集算法 ·标记-清除将存活的对象进行标记,然后清理掉未被标记的对象。不足:标记和清除过程效率都不高;会产 生大量不连续的内存碎片,导致无法给大对象分配内存。 标记-整理让所有存活的对象都向—端移动,然后直接清理掉端边界以外的內存。 ·复制将内存划分为大小相等的两块,每次只使用其中一块,当这一块内存用完了就将还存活的对象复制到另 一块上面,然后再把使用过的内存空间进行一次清理。主要不足是只使用了内存的一半。@现在的商业虚拟 机都采用这种收集算法来回收新生代,但是并不是将内存划分为大小相等的两块,而是分为一块较大的Eden 空间和两块较小的 Survivor空间,每次使用Eden空间和其中一块 Survivor。在回收时,将Eden和 Survivor 中还存活着的对象一次性复制到另一块 Survivor空间上,最后清理Eden和使用过的那一块 Survivor Hotspot虚拟机的Eden和 Survivor的大小比例默认为8:1,保证了内存的利用率达到90%。如果每次回收 有多于10%的对象存活,那么一块 Survivor空间就不够用了,此时需要依赖于老年代进行分配担保,也就 是借用老年代的空间存储放不下的对象。 分代收集现在的商业虚拟机采用分代收集算法,它根据对象存活周期将內存划分为几块,不同块采用适当的 收集算法。一殷将]ava堆分为新生代和老年代。新生代使用:复制算法考年代使用:标记-清理或者标记 整理算法 11线程池 Thread Poolexecutor、线程间协作方式 ·jdk1.5引入 Executor线程池框架,通过它把任务的提交和执行进行解耦,只需要定义好任务,然后提交给线 程池,而不用关心该任务是如何执行、被哪个线程执行,以及什么时候执行 线程池:多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间」 增加处理器单元的吞吐能力。假设一个服务器完成一项任务所需时间为:T1创建线程时间,T2在线程中执 行任务的时间,T3销毁线程时间。如果:T1+T3远大于T2,则可以采用线程池,以提高服务器性能。 一个线程池包括以下四个基本组成部分 1、线程池管理器( Threadσo):用于创建并管理线程池,创建线程池,销毁线程池,添加新任务; 2、工作线程( Pool worker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务 3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行, 它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等 4、任务队列( taskQueue):用于存放没有处理的任务。提供一种缓冲机制。 线程池技术正是关注如何缩短或调整τ1,T3时间的技术,从而提高服务器程序性能的。 它把T1,T3分别安排在服务器程序的启动和结束的时间段或者一些空闲的时间段, 这样在服务器程序处理客户请求时,不会有T1,T3的开销了 Java线程池的工厂类: Executαrs类,初始化4种类型的线程池: new ThreadPool说明:初始化一个指 定线程数的线程池,其中 corepoolsize= maxiPoolsize,使用 Linked Blocking Quene作为阻塞队列特点 即使当线程池没有可执行任务时,也不会释放线程。 new cached Thread Poo说明:初始化个可以缓存线 程的线程池,默认缓存60s,线程池的线程数可达到 Integer. MAX_VALUE,内部使用 SynchronousQueue作 为阻塞队列;特点:在没有任务执行时,当线程的空闲时间超过 keepAliveTime,会自动释放线程资源;当 提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销;因此,使用时要注意 控制并发的任务数,防止因创建大量的线程导致而降低性能。 newSingle ThreadEXecutor()说明:初始化只 有一个线程的线程池,内部使用 Linked Queue作为阻塞队列。特点:如果该线程异常结束,会重新 创建一个新的线程继续执行任务,唯一的线程可以保证所提交仼务的顺序执行η ew Scheduledthreadpoo( 特点:初始化的线程池可以在指定的时间内周期性的执行所提交的任务,在实际的业务场景中可以使用该线 程池定期的司步数据。 ·总结:除 newScheduledthread Pool外,其它线程池内部都是基于 ThreadPool executor类( Executor的子 类)实现的 当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于 corepoolsize 如果当前线程数为 corepoolsize,继续提交的任务被保存到阻塞队列中,等待被执行 如果阻塞队列满了,那就创建新的线程执行当前仼务;直到线程池中的线程数达到 maxPoolsize 这时再有任务来,只能执行 reject(处理该任务 三种线程间协作方式 synchronized+notify+wait +flag ofag代表一个共享变量 σ notify和wait使用的是 Object方法,所以不能单独的让某个特定的线程收到通知或者让他等待,而在 存在多个线程同时等待时,只能通过 notify来通知所有的线程,不够灵活。 lock condition flag o这种方式是利用了ava5中提供的lock和 condition,利用共享变量fag来实现线程之间的相互通信。 o在一个类中可以创建多个 condition的实例,通过 condition不同的实的sgna和awai方法来标识不 同的两个线程之间相互通信的标识,而不是统一使用 object的noy和Wat方法了。 同时利用ock方法可以利用锁的重入机制实现更加灵活的锁的应用。可以在需要的时候加锁或解锁。 semaphore +flag o semaphore代表一个信号量,它可以指示共享資源的个数,也就是同时访问资源的线程个数 σ通过 semaphore的 acquire和 release实现锁的功能从而实现线程之间的通信 σ利用 semaphore不仅可以实现多个线程协调循环通信,在必要时还可以控制同时间访问資源的个数。 更加的灵活和方便。 12 static. final. voaltile " static方法就是没有this的方法。在 static方法内部不能调用非静态方法,反过来是可以的。而且可以在没有 创建任何对象的前提下,仅仅通过类本身来调用 static方法。这实际上正是 static方法的主要用途。"简而言 之,方便在没有创建对象的情况下来进行调用(方法变量) ·fina关键字有三个用途。第一,它可以用来创建—个已命名常量的等价物。第二,使用fnal(修饰方法)阻 止重载。第三,使用 final(修饰类)阻止继承 · olatile(oad+save) ¤所修饰的变量不保留拷贝,直接访问主内存 解决线程间可见性、不保证原子性 o volatile只是保证从主内存加载到线程工作內存的值是最新的。假设对共亨变量除了赋值外不完成其他操 作,可以将这些共享变量生命为 volatile。 oi+这个操作是非原子性的,假设它里面有三个操作,a:读取的值,b:将增加1,c:写入主存。这样 的话,当我们线程1执行完a操作后,切换到线程2,待线程2的i+操作执行完后,线程1的缓存行失效, 但是因为在线程1,a操作(读取的值经执行过了,无需再去读取的值,所以缓存行是否更新也就无 关紧要了,待线程1执行完b,c操作后,自然会得到错误的答案。 13JAVA访问控制权限 · protected包内所有类可见,包外有继承关系的子类可见(子类对象可调用(可以是不同包,只要有继承关系 就行)) · default表示默认,不仅本类访问,而且是同包可见。 14 Spring bean配置方式 全类名(Cass=") ·静态工厂( factory-method=")与实例工厂( factory-bean=" factory- method=") · Factory Bean配置(实现这个接口,配置该bean,返回的是方法 getobject(返回的实例) 15 synchronized关键字与接囗Lock的区别 区别 1)Lock是个接口,而 synchronized是java关键字, synchronized是内置语言实现 2) synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象的发生;而Lock在发生异常 时,如果没有主动通过 unlock去释放锁,则很有可能造成死敏现象,因此使用Lock时需要在 finally块中释放锁 3)Lock可以让等待锁的线程相应中断;而 synchronized不行,使用 synchronized时,等待的线程会一直等待下 去,不能够响应中断 4)通过LoCk可以知道有没有成功获取锁,而 synchronized却无法办到 5)Lock可以提高多个线程读操作的效率 在性能上来说,如果资源竟争不瀲烈的话,两者的性能是差不多的;而当资源竞争非常激烈(即有大量线程冋时竟 争)时,Lock的性能要远远优于 synchronized synchronized 1.Java中每一个对象都有唯一的一个对象锁与之关联。 2.当 synchronized修饰的成员方法或语句块执行完成或执行过程中抛出异常时,会自动释放锁。无需手动释放 锁(实际上也没有办法手动释放)。 3.无论syη chronized修饰的是成员方法还是语句块,当 synchronized修饰非静态成员方法时, synchronized 锁定的都是对象,而不是某一段代码。当 synchronized修饰静态成员方法时,锁定的对象是当前调用这个方 法的对象对应类的Clas类的对象,也就是说和 synchronized(类名cass)锁定的是同一个对象

...展开详情
试读 22P 18年秋招JAVA面经精心整理总结
立即下载 低至0.43元/次 身份认证VIP会员低至7折
一个资源只可评论一次,评论内容不能少于5个字
您会向同学/朋友/同事推荐我们的CSDN下载吗?
谢谢参与!您的真实评价是我们改进的动力~
上传资源赚钱or赚积分
最新推荐
18年秋招JAVA面经精心整理总结 3积分/C币 立即下载
1/22
18年秋招JAVA面经精心整理总结第1页
18年秋招JAVA面经精心整理总结第2页
18年秋招JAVA面经精心整理总结第3页
18年秋招JAVA面经精心整理总结第4页
18年秋招JAVA面经精心整理总结第5页

试读结束, 可继续读2页

3积分/C币 立即下载 >