《JAVA100题面试题资料.pdf》由会员分享,可在线阅读,更多相关《JAVA100题面试题资料.pdf(82页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、java基础面向对象什么是面向对象?对比面向过程,是两种不同的处理问题的角度面向过程更注重事情的每一个步骤及顺序,面向对象更注重事情有哪些参与者(对象)、及各自需要做什么比如:洗衣机洗衣服面向过程会将任务拆解成一系列的步骤(函数),1、打开洗衣机-2、放衣服-3、放洗衣粉-4、清洗-5、烘干面向对象会拆出人和洗衣机两个对象:人:打开洗衣机放衣服放洗衣粉洗衣机:清 洗 烘 干从以上例子能看出,面向过程比较直接高效,而面向对象更易于复用、扩展和维护面向对象封装:封装的意义,在于明确标识出允许外部使用的所有成员函数和数据项内部细节对外部调用透明,外部调用无需修改或者关心内部实现1、javabean的
2、属性私有,提供getset对外访问,因为属性的赋值或者获取逻辑只能由javabean本身决定。而不能由外部胡乱修改private String name;public void setName(S tring name)th is.name=tuling.+name;)该name有自己的命名规则,明显不能由外部直接赋值2、orm框架操作数据库,我们不需要关心链接是如何建立的、sql是如何执行的,只需要引入mybatis,调方法即可继承:继承基类的方法,并做出自己的改变和/或扩展子类共性的方法或者属性直接使用父类的,而不需要自己再定义,只需扩展自己个性化的多态:基于对象所属类的不同,外部对同一个
3、方法的调用,实际执行的逻辑不同。继承,方法重写,父类引用指向子类对象父类类型 变 量 名=new 子 类 对 象;变量名.方法名();无法调用子类特有的功能JDKJREJVMJDK:Java Develpment Kit java 开发工具JRE:Java Runtime Environment java运行时环境JVM:java Virtual Machine java 虚拟机=fflequalsfctK=对比的是栈中的值,基本数据类型是变量值,引用类型是堆中内存对象的地址equals:object中默认也是采用=二 比较,通常会重写Objectpublic boolean equals(O
4、bject obj)return(th is =o b j);)Stringpublic boolean equals(Object anobject)i f (th is =anobject)return true;)i f (anobject 1nstanceof S tring)S tring anotherString=(S tring)anObject;in t n=value.length;i f (n=anotherS tring.value.1ength)char v l=value;char v2=anotherS tring.value;in t i=0;while(n-!
5、=0)i f !=v 2 i)return false;i+;)return true;)return false;)上述代码可以看出,String类中被复写的equals。方法其实是比较两个字符串的内容。public class StringDemo public s ta tic void m ain(String a rg s)S tring s t r i=H ello;S tring str2=new S tringCHello );S tring str3=s tr2;/引用传递System.o u t.p rin tln(s tr i=s tr2);/falseSystem.o
6、u t.p rin tln(s tr l=s tr3);/falseSystem.out.p rin tln(s tr2 =s tr3);/trueSystem.o u t.p rin tln(s tri.e q u a ls(s tr2);/trueSystem.o u t.p rin tln(s tri.e q u a ls(s tr3);/trueSystem.o u t.p rin tln(s tr2.equals(str3);/true)hashCode 与equalshashCode 介绍:hashCode。的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码
7、的作用是确定该对象在哈希表中的索引位置。hashCodeO定义在JDK的Object.java中,Java中的任何类都包含有hashCode()函数。散列表存储的是键值对伙ey-value),它的特点是:能根据 键”快速的检索出对应的 值。这其中就利用到了散列码!(可以快速找到所需要的对象)为什么要有hashCode:以“HashSet如何检查重复”为例子来说明为什么要有hashCode:对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,看该位置是否有值,如果没有、HashSet会假设对象没有重复出现。但是如果发现有值,这时会调用equals()方法
8、来检直两个对象是否真的相同。如果两者相同,HashSet就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样就大大减少了equals的次数,相应就大大提高了执行速度。如果两个对象相等,则hashcode一定也是相同的 两个对象相等,对两个对象分别调用equals方法都返回true 两个对象有相同的hashcode值,它们也不一定是相等的 因此,equals方法被覆盖过,则hashCode方法也必须被覆盖 hashCode。的默认行为是对堆上的对象产生独特值。如果没有重写hashCode。,则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)final最终的
9、修饰类:表示类不可被继承 修饰方法:表示方法不可被子类覆盖,但是可以重载 修饰变量:表示变量一旦被赋值就不可以更改它的值。(1)修饰成员变量 如果final修饰的是类变量,只能在静态初始化块中指定初始值或者声明该类变量时指定初始值。如果fina修饰的是成员变量,可以在非静态初始化块、声明该变量或者构造器中执行初始值。(2)修饰局部变量系统不会为局部变量进行初始化,局部变量必须由程序员显示初始化。因此使用final修饰局部变量时,即可以在定义时指定默认值(后面的代码不能对变量再赋值),也可以不指定默认值,而在后面的代码中对final变量赋初值(仅一次)public cl ass Fi nalva
10、r fin a l s ta tic in t a=0;/再声明的时候就需要赋值 或者静态代码块赋值/*s ta tic a=0;*/fin a l in t b=0;/再声明的大中赋值 或者构造器赋值/*b=0;*/public s ta tic void main(S trin g args)fin a l in t local A;局部变量只声明没有初始化,不公报铅,与fin a l无关。localA=0;/在使用之前一定耍赋值/lo c a lA =1;但是不允许第二次赋值)(3)修饰基本类型数据和引用类型数据如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型
11、的变量,则在对其初始化之后便不能再让其指向另一个对象。但是引用的值是可变的。public class FinalReferenceTestpublic s ta tic void m ain()fin a l in t iA rr=l,2,3,4);iArr2=-3;合法iA rr=n u ll;/非法,对iA rr不能重新赋值fin a l Person p=new Person(25);p.setAge(24);/合法p=null;非法)为什么局部内部类和匿名内部类只能访问局部final变量?编译之后会生成两个dass文件,Test.class Testi.classpublic clas
12、s Test public s ta tic void main(S trin g args)局部fin a l变量a,bpublic void te s t(fin a l in t b)/jd k 8在这里做了优化,不用写,语法糖,但实际上也是有的,也不能修改fin a l in t a=10;/匿名内部类new Thread()public void run()System.out.p rin tln(a);S ystem.out.printin(b););.s ta rt();)class Outclass private in t age=12;public void o u tP
13、rin t(f inal in t x)class inClass public void ln P rin t()System.o u t.p ri n tln(x);System.o u t.p ri n tln(a g e);)new inClassO .inP ri n t();)首先需要知道的一点是:内部类和外部类是处于同一个级别的,内部类不会因为定义在方法中就会随着方法的执行完毕就被销毁。这里就会产生问题:当外部类的方法结束时,局部变量就会被销毁了,但是内部类对象可能还存在(只有没有人再引用它时,才会死亡)。这里就出现了一个矛盾:内部类对象访问了一个不存在的变量。为了解决这个问题,
14、就将局部变量复制了一份作为内部类的成员变量,这样当局部变量死亡后,内部类仍可以访问它,实际访问的是局部变量的“copy。这样就好像延长了局部变量的生命周期将局部变量复制为内部类的成员变量时,必须保证这两个变量是一样的,也就是如果我们在内部类中修改了成员变量,方法中的局部变量也得跟着改变,怎么解决问题呢?就将局部变量设置为fin a l,对它初始化后,我就不让你再去修改这个变量,就保证了内部类的成员变量和方法的局部变量的一致性.这实际上也是一种妥协。使得局部变量与内部类内建立的拷贝保持一致。String、StringBuffer StringBuilderString是final修饰的,不可变,
15、每次操作都会产生新的String对象StringBuffernstringBuilde r都是在原对象上操作StringBuffer是线程安全的,StringBuilder线程不安全的StringBuffer方 法都是 synchronized 修饰的性能:StringBuilder StringBuffer String场景:经常需要改变字符串内容时使用后面两个优先使用StringBuilder,多线程使用共享变量时使用StringBuffer重载和重写的区别重载:发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。重写:发生在父
16、子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为private则子类就不能重写该方法。public in t add(i nt a,S tring b)public S tring add(int a,S tring b)/编译报错接口和抽象类的区别 抽象类可以存在普通成员函数,而接口中只能存在public abstract方法。抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的。抽象类只能继承一个,接口可以实现多个。接口的设计目的,是对类的行为进行约
17、束(更准确的说是一种“有 约束,因为接口不能规定类不可以有什么行为),也就是提供一种机制,可以强制要求不同的类具有相同的行为。它只约束了行为的有无,但不对如何实现行为进行限制。而抽象类的设计目的,是代码复用。当不同的类具有某些相同的行为(记为行为集合A),且其中一部分行为的实现方式一致时(A的非真子集,记为B),可以让这些类都派生于一个抽象类.在这个抽象类中实现了 B,避免让所有的子类来实现B,这就达到了代码复用的目的。而A减B的部分,留给各个子类自己实现。正是因为A-B在这里没有实现,所以抽象类不允许实例化出来(否则当调用到A-B时,无法执行)。抽象类是对类本质的抽象,表达的是is a的关系
18、,比如:BMW is a c a r.抽象类包含并实现子类的通用特性,将子类存在差异化的特性进行抽象,交由子类去实现。而接口是对行为的抽象,表达的是like a的关系。比如:Bird like a A ircraft(像飞行器一样可以飞),但其本质上is a B ird,接口的核心是定义行为,即实现类可以做什么,至于实现类主体是谁、是如何实现的,接口并不关心。使用场景:当你关注一个事物的本质的时候,用抽象类;当你关注一个操作的时候,用接口.抽象类的功能要远超过接口,但是,定义抽象类的代价高。因为高级语言来说(从实际设计上来说也是)每个类只能继承一个类。在这个类中,你必须继承或编写出其所有子类的
19、所有共性。虽然接口在功能上会弱化许多,但是它只是针对一个动作的描述。而且你可以在一个类中同时实现多个接口。在设计阶段会降低难度List和Set的区别 List:有序,按对象进入的顺序保存对象,可重复,允许多个Null元素对象,可以使用Iterator取出所有元素,在逐一遍历,还可以使用get(int index)获取指定下标的元素 S et:无序,不可重复,最多允许有一个Null元素对象,取元素时只能用Iterator接口取得所有元素,在逐一遍历各个元素ArrayListfO Linked ListlZgiArrayList:基于动态数组,连续内存存储,适合下标访问(随机访问),扩容机制:因为
20、数组长度固定,超出长度存数据时需要新建数组,然后将老数组的数据拷贝到新数组,如果不是尾部插入数据还会涉及到元素的移动(往后复制一份,插入新元素),使用尾插法并指定初始容量可以极大提升性能、甚至超过linkedList(需要创建大量的node对象)LinkedList:基于链表,可以存储在分散的内存中,适合做数据插入及删除操作,不适合查询:需要逐一遍历遍历LinkedList必须使用iterator不能使用for循环,因为每次for循环体内通过get(i)取得某一元素时都需要对list重新进行遍历,性能消耗极大。另外不要试图使用indexOf等返回元素索引,并利用其进行遍历,使用index仅 寸
21、list进行了遍历,当结果为空时会遍历整个列表。HashMap和HashTable有什么区别?其底层实现是什么?区 别:(1)HashM叩方法没有synchronized修饰,线程非安全,HashTable线程安全;(2)HashMap允许key和value为n u ll,而HashTable不允许2.底层实现:数组+链表实现jdk8开始链表高度到8、数组长度超过6 4,链表转变为红黑树,元素以内部类Node节点存在 计算key的hash值,二次hash然后对数组长度取模,对应到数组下标,如果没有产生hash冲突(下标位置没有元素),则直接创建Node存入数组,如果产生hash冲突,先进行eq
22、ual比较,相同则取代该元素,不同,则判断链表高度插入链表,链表高度达到8,并且数组长度到64则转变为红黑树,长度低于6则将红黑树转回链表 key为n u ll,存在下标0的位置数组扩容ConcurrentHashMap原理,上 117矛 用 11 编译器jvm可执彳丁的Java字节码(即虚拟指令)-jvm-jvm中解释器 机器可执行的二进制机器码一,程序运行。采用字节码的好处:Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以Java程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译
23、便可在多种不同的计算机上运行。Java中的异常体系Java中的所有异常都来自顶级父类Throwable。Throwable 下有两个子类 Exception 和 Error。Error是程序无法处理的错误,一旦出现这个错误,则程序将被迫停止运行。Exception不会导致程序停止,又分为两个部分RunTimeException运行时异常和CheckedException检杳异常。RunTimeException常常发生在程序运行过程中,会导致程序当前线程执行失败。CheckedException常常发生在程序编译过程中,会导致程序编译不通过。Java类加载器JDK自带有三个类加载器:boots
24、trap ClassLoader.ExtClassLoader.AppClassLoader0BootStrapCIassLoader是ExtClassLoader的父类力口载器,默认负责加载JAVA_HOME%lib下的jar包和class文件。ExtQassLoader是AppCIassLoader的父类力口载器,负责加载状6_1_101/1%川13/6乂1:文件夹下的jar包和class 类。AppCIassLoader是自定义类加载器的父类,负责加载classpath下的类文件。系统类加载器,线程上下文加载器继承QassLoader实现自定义类加载器双亲委托模型双亲委派模型的好处:主要
25、是为了安全性,避免用户自己编写的类动态替换Java的一些核心类,比如String。同时也避免了类的重复加载,因为JVM中区分不同类,不仅仅是根据类名,相同的class文件被不同的ClassLoader加载就是不同的两个类GC如何判断对象可以被回收 引用计数法:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收,可达性分析法:从 GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GCRoots没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机就判断是可回收对象。引用计数法,可能会出现A 引用了 B,B 又引用了 A,这时候就算他们
26、都不再使用了,但因为相互引用计数器=1永远无法被回收.GC Roots的对象有:虚拟机栈(栈帧中的本地变量表)中引用的对象 方法区中类静态属性引用的对象 方法区中常量引用的对象 本地方法栈中JNI(即一般说的Native方法)引用的对象可达性算法中的不可达对象并不是立即死亡的,对象拥有一次自我拯救的机会。对象被系统宣告死亡至少要经历两次标记过程:第一次是经过可达性分析发现没有与GC Roots相连接的引用链,第二次是在由虚拟机自动建立的Finalizer队列中判断是否需要执行finalize。方法。当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖
27、,则直接将其回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由T 氐优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象 复活每个对象只能触发一次finalize。方法由于finalize。方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,不推荐大家使用,建议遗忘它。线程、并发相关线程的生命周期?线程有几种状态1.线程通常有五种状态,创建,就绪,运行、阻塞和死亡状态。2.阻塞的情况又分为三种:(1)、等待阻塞:运行的线程执行wait方法,该线程会释放占用的所有资源,J
28、VM会把该线程放入 等待池”中.进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify或notifyAII方法才能被唤醒,wait是object类的方法(2)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。、其他阻塞:运行的线程执行sleep或join方法,或者发出了I/O请求时,JVM会把该线程g为阻塞状态。当sleep状态超时、join等待线程终止或者超时、或者I/。处理完毕时,线程重新转入就绪状态.sleep是Thread类的方法1新 建 状 态(N ew):新创建了f 线程对象。2.就绪状态(Runnable):线程对
29、象创建后,其他线程调用了该对象的start方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。3.运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。4.阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。5.死亡状态(Dead):线程执行完了或者因异常退出了run方法,该线程结束生命周期.sleep。、wait。、join。、yield。的区别1.锁池所有需要竞争同步锁的线程都会放在锁池当中,比如当前对象的锁已经被其中一个线程得到,则其他线程需要在这个锁池进行等待,当前面的线
30、程释放同步锁后锁池中的线程去竞争同步锁,当某个线程得到后会进入就绪队列进行等待cpu资源分配。2.等待池当我们调用wait()方法后,线程会放到等待池当中,等待池的线程是不会去竞争同步锁。只有调用了notify()或notifyAII()后等待池的线程才会开始去竞争锁,notify()是随机从等待池选出一个线程放到锁池,而notifyAI 1(堤将等待池的所有线程放到锁池当中1、sleep是Thread类的静态本地方法,wait则是。bject类的本地方法。2、sleep方法不会释放lock,但是wait会释放,而且会加入到等待队列中.sleep就是把cpu的执行资格和执行权释放出去,不再运行
31、此线程,当定时时间结束再取回cpu资源,参与cpu的调度,获取到cpu资源后就可以继续运行了。而如果sleep时该线程有锁,那么sleep不会释放这个锁,而是把锁带着进入了冻结状态,也就是说其他需要这个锁的线程根本不可能获取到这个锁.也就是说无法执行程序。如果在睡眠期间其他线程调用了这个线程的interrupt方法,那么这个线程也会抛出interruptexception异常返回,这点和wait是一样的。3、sleep方法不依赖于同步器synchronized,但是wait需要依赖synchronized关健字。4、sleep不需要被唤醒(休眠之后推出阻塞),但是wait需要(不指定时间需要被
32、别人中断)。5、sleep一般用于当前线程休眠,或者轮循暂停操作,wait则多用于多线程之间的通信。6、sleep会让出CPU执行时间且强制上下文切换,而wait则不一定,wait后可能还是有机会重新竞争到锁继续执行的。yield()执行后线程直接进入就绪状态,马上释放了cpu的执行权,但是依然保留了cpu的执行资格,所以有可能cpu下次进行线程调度还会让这个线程获取到执行权继续执行join()执行后线程进入阻塞状态,例如在线程B中调用线程A的join(),那线程B会进入到阻塞队列,直到线程A结束或中断线程public static void main(String args)throws i
33、nterruptedException Thread tl=new Thread(new Runnable()Overridepublic void run()try(Thread.sleep(3000);catch(InterruptedException e)e.pri ntStackTrace();)System.out.pri n tln(H22222222););tl.s t a r tf);t l.joinQ ;/这行代码必须要等t l全部执行完毕,才会执行System.out.pr i nt l;)222222221111对线程安全的理解不是线程安全、应该是内存安全,堆是共享内存
34、,可以被所有线程访问当多个线程访问一个对象时,如果不用进行额外的同步控制或其他的协调操作,调用这个对象的行为都可以获得正确的结果,我们就说这个对象是线程安全的堆是进程和线程共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是用完了要还给操作系统,要不然就是内存泄漏。在Java中,堆是Java虚拟机所管理的内存中最大的一块,是所有线程共享的一块内存区域,在虚拟机启动时创建。堆所存在的内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。栈是每个线程独有的,保存其运
35、行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立,因此,栈是线程安全的。操作系统在切换线程的时候会自动切换栈。栈空间不需要在高级语言里面显式的分配和释放.目前主流操作系统都是多任务的,即多个进程同时运行。为了保证安全,每个进程只能访问分配给自己的内存空间,而不能访问别的进程的,这是由操作系统保障的。在每个进程的内存空间中都会有一块特殊的公共区域,通常称为堆(内存)。进程内的所有线程都可以访问到该区域,这就是造成问题的潜在原因。Thread、Runable的区别Thread和Runnable的实质是继承关系,没有可比性。无论使用Runnable还是Thread,都会newT
36、hread,然后执行run方法。用法上,如果有复杂的线程操作需求,那就选择继承Thread,如果只是简单的执行一个任务,那就实现runnable。/会卖出多一倍的票public class Test public sta tic void main(S tring args)/TODO Auto-generated method stubnew MyThread().s ta rt();new MyThread().s ta rt();)sta tic class MyThread extends Threadprivate in t ticke t=5;public void run()w
37、hile(true)System.out.p rintlnCThread ticke t=+tic k e t-);i f(ticke t 0)break;)/正常卖出public class Test2 public sta tic void main(S tring args)/TODO Auto-generated method stubMyThread2 mt=new MyThread2();new Thread(mt).s ta rt();new Thread(mt).s ta rt();s ta tic class MyThread2 implements Runnablepriv
38、ate in t ticke t=5;public void run()w hile(true)System.out.p rin tlnCRunnable ticke t=+tic k e t-);i f(ticke t 0)break;)原因是:MyThread创建了两个实例,自然会卖出两倍,属于用法错误对守护线程的理解守护线程:为所有非守护线程提供服务的线程;任何一个守护线程都是整个JVM中所有非守护线程的保姆;守护线程类似于整个进程的一个默默无闻的小喽喽;它的生死无关重要,它却依赖整个进程而运行;哪天其他线程结束了,没有要执行的了,程序就结束了,理都没理守护线程,就把它中断了;注意:由于
39、守护线程的终止是自身无法控制的,因此千万不要把10、File等重要操作逻辑分配给它;因为它不靠谱;守护线程的作用是什么?举例,GC垃圾回收线程:就是一个经典的守护线程,当我们的程序中不再有任何运行的Th read,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是JVM上仅剩的线程时,垃圾回收线程会自动离开。它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。应用场景:(1)来为其它线程提供服务支持的情况;(2)或者在任1可情况下,程序结束时,这个线程必须正常且立刻关闭,就可以作为守护线程来使用;反之,如果一个正在执行某个操作的线程必须要正确地关闭掉否则就会出现不
40、好的后果的话,那么这个线程就不能是守护线程,而是用户线程。通常都是些关键的事务,比方说,数据库录入或者更新,这些操作都是不能中断的。thread.setDaemon(true)必须在thread.start。之前设置,否则会跑出一个HlegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。在Daemon线程中产生的新线程也是Daemon的。守护线程不能用于去访问固有资源,比如读写操作或者计算逻辑。因为它会在任何时候甚至在一个操作的中间发生中断。Java自带的多线程框架,比如ExecutorService,会将守护线程转换为用户线程,所以如果要使用后台线
41、程就不能用Java的线程池。ThreadLocal的原理和使用场景每一个Thread对象均含有一个ThreadLocalMap类型的成员变量threadLocal s,它存储本线程中所有ThreadLocal对象及其对应的值ThreadLocalMap由一个个Entry对象构成Entry 继承自 weakReferenceThreadLoca1,一个 Entry 由 ThreadLocal 对象和 Object 构成。由此可见,Entry的key是ThreadLocal对象,并且是一个弱引用。当没指向key的强引用后,该key就会被垃圾收集器回收当执行set方法时,Thread Local首先
42、会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为k e y,将值存储进ThreadLocalMap对象中。get方法执行过程类似。Thread Loca I首先会获取当前线程对象,然后获取当前线程的Thread Local M叩对象。再以当前ThreadLocal对象为k e y,获取对应的value。由于每一条线程均含有各自私有的ThreadLocalMap容器,这些容器相互独立互不影响,因此不会存在线程安全性问题,从而也无需使用同步机制来保证多条线程访问容器的互斥性.使用场景:1、在进行对象跨层传递的时候,使用ThreadLoca
43、l可以避免多次传递,打破层次间的约束。2、线程间数据隔离3、进行事务操作,用于存储线程事务信息。4、数据库连接,Session会话管理。Spring框架在事务开始时会给当前线程绑定一个Jdbc Connect!on,在整个事务过程都是使用该线程绑定的connection来执行数据库操作,实现了事务的隔离性。Spring框架里面就是用的ThreadLocal来实现这种隔离ThreadLocal内存泄露原因,如何避免内存泄露为程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光,不再会被使用的对象或者变量占用的内存不能被回收,就
44、是内存泄露。强引用:使用最普遍的引用(new),一个对象具有强引用,不会被垃圾回收器回收。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryErro储 误,使程序异常终止,也不回收这种对象。如果想取消强引用和某个对象之间的关联,可以显式地将引用赋值为n u ll,这样可以使|VM在合适的时间就会回收该对象。弱引用:JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.Iang.ref.WeakReference类来表示。可以在缓存中使用弱引用。Th read Local的实现原理,每一个Thread维护一个ThreadLocalM叩,key为使
45、用弱弓|用的Thread Local实例,value为线程变量的副本hreadLocalM叩使用ThreadLocal的弱弓|用作为key,如果HhreadLocal不存在外部强引用时,Key(ThreadLocal)势必会被GC回收,这样就会导致ThreadLocalM叩中key为n u ll,而value还存在着强引用,只有thead线程退出以后,value的强引用链条才会断掉,但如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链(红色链条)key使用强引用当hreadLocalM叩的key为强引用回收ThreadLocal时,因为Threa
46、dLocalM叩还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。key使用弱引用当仆013211/的1 core)的情况下,工厂领导(线程池)不是首先扩招工人,还是这10人,但是任务可以稍微积压一下,即先放到队列去(代价低)。10个正式工慢慢干,迟早会干完的,要是任务还在继续增加,超过正式工的加班忍耐极限了(队列满了),就的招外包帮忙了(注意是临时工)要是正式工加上外包还是不能完成任务,那新来的任务就会被领导拒绝了(线程池的拒绝策略)。线程池中线程复用原理线程池将线程和任务进行解耦,线程是线程,任务是任务,摆脱了之前通过Thre
47、ad创建线程时的一个线程必须对应一个任务的限制。在线程池中,同一个线程可以从阻塞队列中不断获取新任务来执行,其核心原理在于线程池对Thread进行了封装,并不是每次执行任务都会调用Thread.start()来创建新线程,而是让每个线程去执行一个“循环任务,在这个 循环任务”中不停检查是否有任务需要被执行,如果有则直接执行,也就是调用任务中的run方法,将run方法当成一个普通的方法执行,通过这种方式只使用固定的线程就将所有任务的run方法串联起来。spring如何实现一个IOC容器1、配置文件配置包扫描路径2、递归包扫描获取class文件3、反射、确定需要交给I0C管理的类4、对需要注入的类
48、进行依赖注入 配置文件中指定需要扫描的包路径 定义一些注解,分别表示访问控制层、业务服务层、数据持久层、依赖注入注解、获取配置文件注解 从配置文件中获取需要扫描的包路径,获取到当前路径下的文件信息及文件夹信息,我们将当前路径下所有以.class结尾的文件添加到一个Set集合中进行存储 遍历这个set集合,获取在类上有指定注解的类,并将其交给I0C容器,定义一个安全的Map用来存储这些对象 遍历这个I0C容器,获取到每一个类的实例,判断里面是有有依赖其他的类的实例,然后进行递归注入spring是什么?轻量级的开源的J2EE框架。它是一个容器框架,用来装javabean(java对象),中间层框架
49、(万能胶)可以起一个连接作用,比如说把Struts和hibernate粘合在一起运用,可以让我们的企业开发更快、更简:士/口Spring是一个轻量级的控制反转(1。和面向切面(AOP)的容器框架-从大小与开销两方面而言Spring都是轻量级的。-通过控制反转(1。0的技术达到松耦合的目的-提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务进行内聚性的开发-包含并管理应用对象(Bean)的配置和生命周期,这个意义上是一个容器。-将简单的组件配置、组合成为复杂的应用,这个意义上是一个框架。谈谈你对AOP的理解系统是由许多不同的组件所组成的,每一个组件各负责一块特定功能。除了实现自
50、身核心功能之外,这些组件还经常承担着额外的职责。例如日志 事务管理和安全这样的核心服务经常融入到自身具有核心业务逻辑的组件中去。这些系统服务经常被称为横切关注点,因为它们会跨越系统的多个组件。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系.例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。AO P:将程序中的交叉业务逻辑(比如安全,日志,事务等),封装成一个切面,然后注入到目标对象(具体业务逻辑)中去