一、Java篇

1.1 Java基础篇

1、请你讲讲&和&&的区别?

&运算符有两种用法:(1)按位与;(2)逻辑与。
&&运算符是短路与运算。
逻辑与跟短路与的差别是非常巨大的,虽然二者都要求运算符左右两端的布尔值都是true整个表达式的值才是true。&&之所以称为短路运算是因为,如果&&左边的表达式的值是false,右边的表达式会被直接短路掉,不会进行运算。很多时候我们可能都需要用&&而不是&,例如在验证用户登录时判定用户名不是null而且不是空字符串,应当写为:username != null &&!username.equals(""),二者的顺序不能交换,更不能用&运算符,因为第一个条件如果不成立,根本不能进行字符串的equals比较,否则会产生NullPointerException异常。

2、int和Integer有什么区别?

Java是一个近乎纯洁的面向对象编程语言,但是为了编程的方便还是引入了基本数据类型,但是为了能够将这些基本数据类型当成对象操作,Java为每一个基本数据类型都引入了对应的包装类型(wrapper class),int的包装类就是Integer,从Java 5开始引入了自动装箱/拆箱机制,使得二者可以相互转换。
Java 为每个原始类型提供了包装类型:
● 原始类型: boolean,char,byte,short,int,long,float,double
● 包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double

3、String、StringBuffer、StringBulilder的区别

4、请你讲讲数组(Array)和列表(ArrayList)的区别?

Array和ArrayList的不同点:

  • Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。

  • Array大小是固定的,ArrayList的大小是动态变化的。

  • ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。

  • 对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。

5、请你解释一下什么是值传递?什么是引用传递?

值传递: 是对基本变量而言的,传递的是该变量的一个副本,改变副本不影响原变量,(注意String作为参数时也不会改变原变量)
引用传递: 一般是对于对象型变量而言的,传递的是该对象地址的一个副本、并不是原对象本身。所以对引用对象进行操作会同时改变原对象。

public   class AbtractClassTest {String str="aaa";int a=111;Integer b=222;public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {AbtractClassTest test = new AbtractClassTest();User user=new User();user.setId("10001");test.StringChuandi(test.str);System.out.println(test.str);test.intChuandi(test.a);System.out.println(test.a);test.classChuandi(user);System.out.println(user);}public String  StringChuandi(String str){str="bbb";return str;}public int  intChuandi(int a){a=1111;return a;}public void  classChuandi(User user){user.setId("10002");}
}

结果:

aaa
111
User{name='null', id='10002'}

6、请你解释Object若不重写hashCode()的话,hashCode()如何计算出来的?

Object 的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法直接返回对象的 内存地址。

7、请你解释为什么重写equals还要重写hashcode?

HashMap中,如果要比较key是否相等,要同时使用这两个函数!因为自定义的类的hashcode()方法继承于Object类,其hashcode码为默认的内存地址。HashMap中的比较key是这样的,先求出key的hashcode(),比较其值是否相等,若相等再比较equals(),若相等则认为他们是相等的。若equals()不相等则认为他们不相等。如果只重写hashcode()不重写equals()方法,当比较equals()时只是看他们是否为同一对象(即进行内存地址的比较),所以必定要两个方法一起重写。

8、请你谈谈关于Synchronized和lock

synchronized是Java的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。JDK1.5以后引入了自旋锁、锁粗化、轻量级锁,偏向锁来优化关键字的性能。
Lock是一个接口,而synchronized是Java中的关键字;synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

9、请你介绍一下volatile?

volatile关键字是用来保证有序性和可见性的。
有序性:这跟Java内存模型有关。比如我们所写的代码,不一定是按照我们自己书写的顺序来执行的,编译器会做重排序,CPU也会做重排序的,这样的重排序是为了减少流水线的阻塞,提高CPU的执行效率。happens-before规则,其中有条就是volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;
● 当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见,在其后面的操作肯定还没有进行;
可见性:首先Java内存模型分为,主内存,工作内存。比如线程A从主内存把变量从主内存读到了自己的工作内存中,做了加1的操作,但是此时没有将i的最新值刷新会主内存中,线程B此时读到的还是i的旧值。加了volatile关键字的代码生成的汇编代码发现,会多出一个lock前缀指令。Lock指令对Intel平台的CPU,早期是锁总线,这样代价太高了,后面提出了缓存一致性协议,来保证了多核之间数据不一致性问题。

10、请你介绍一下Syncronized锁的类锁和对象锁的场景

synchronized修饰静态方法以及同步代码块的synchronized (类.class)用法锁的是类,线程想要执行对应同步代码,需要获得类锁。
synchronized修饰成员方法,线程获取的是当前调用该方法的对象实例的对象锁。
● 非静态方法:
给对象加锁(可以理解为给这个对象的内存上锁,注意 只是这块内存,其他同类对象都会有各自的内存锁),这时候 在其他一个以上线程中执行该对象的这个同步方法(注意:是该对象)就会产生互斥
● 静态方法:
相当于在类上加锁(*.class 位于代码区,静态方法位于静态区域,这个类产生的对象公用这个静态方法,所以这块内存,N个对象来竞争), 这时候,只要是这个类产生的对象,在调用这个静态方法时都会产生互斥

11、请你谈一下面向对象的"六原则一法则"

  • 单一职责原则
    一个类只做它该做的事情。
    单一职责原则想表达的就是"高内聚",所谓的高内聚就是一个代码模块只完成一项功能,在面向对象中,如果只让一个类完成它该做的事,而不涉及与它无关的领域就是践行了高内聚的原则,这个类就只有单一职责。说简单点就是分工明确,各司其职。
  • 开闭原则
    软件实体应当对扩展开放,对修改关闭。
    在理想的状态下,当我们需要为一个软件系统增加新功能时,只需要从原来的系统派生出一些新类就可以,不需要修改原来的任何一行代码。 要做到开闭有两个要点:
    抽象是关键,一个系统中如果没有抽象类或接口,系统就没有扩展点;
    封装可变性,将系统中的各种可变因素封装到一个继承结构中,如果多个可变因素混杂在一起,系统将变得复杂而换乱,如果不清楚如何封装可变性,可以参考《设计模式精解》一书中对桥梁模式的讲解的章节。
  • 依赖倒转原则
    面向接口编程,多态。
    该原则说得直白和具体一些就是声明方法的参数类型、方法的返回类型、变量的引用类型时,尽可能使用抽象类型而不用具体类型,因为抽象类型可以被它的任何一个子类型所替代,请参考下面的里氏替换原则。
  • 里氏替换原则
    任何时候都可以用子类型替换掉父类型。
    简单的说就是能用父类型的地方就一定能使用子类型。需要注意的是:子类一定是增加父类的能力而不是减少父类的能力,因为子类比父类的能力更多,把能力多的对象当成能力少的对象来用当然没有任何问题。
  • 接口隔离原则
    接口要小而专,绝不能大而全。
    臃肿的接口是对接口的污染,既然接口表示能力,那么一个接口只应该描述一种能力,接口也应该是高度内聚的。Java中的接口代表能力、代表约定、代表角色,能否正确的使用接口一定是编程水平高低的重要标识。
  • 迪米特法则
    迪米特法则又叫最少知识原则,一个对象应当对其他对象有尽可能少的了解。
    再复杂的系统都可以为用户提供一个简单的门面,Java Web开发中作为前端控制器的Servlet或Filter不就是一个门面吗,浏览器对服务器的运作方式一无所知,但是通过前端控制器就能够根据你的请求得到相应的服务。调停者模式也可以举一个简单的例子来说明,例如一台计算机,CPU、内存、硬盘、显卡、声卡各种设备需要相互配合才能很好的工作,但是如果这些东西都直接连接到一起,计算机的布线将异常复杂,在这种情况下,主板作为一个调停者的身份出现,它将各个设备连接在一起而不需要每个设备之间直接交换数据,这样就减小了系统的耦合度和复杂度。

12、请说明重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?

方法的重载和重写都是实现多态的方式,重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求(重载方法的返回类型可以不一致)。

13、请说明JAVA语言如何进行异常处理,关键字:throws,throw,try,catch,finally分别代表什么意义?

Java 通过面向对象的方法进行异常处理,把各种不同的异常进行分类,并提供了良好的接口。在Java中,每个异常都是一个对象,它是Throwable类或其它子类的实例。当一个方法出现异常后便抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这个异常并进行处理。Java的异常处理是通过5个关键词来实现的:try、catch、throw、throws和finally。
● 一般情况下是用try来执行一段程序,如果出现异常,系统会抛出(throws)一个异常,这时候你可以通过它的类型 来捕捉(catch)它,或最后(finally)由缺省处理器来处理。
● 紧跟在try程序后面,应包含一个catch子句来指定你想要捕捉的”异常”的类型。
● throw语句用来明确地抛出一个”异常”。
● throws用来标明一个成员函数可能抛出的各种”异常”。
● Finally为确保一段代码不管发生什么”异常”都被执行一段代码。

14、请你讲讲abstract class和interface有什么区别?

1.抽象类可以有构造方法,接口中不能有构造方法。
2.抽象类中可以有普通成员变量,接口中没有普通成员变量
3.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的。
4. 抽象类中的抽象方法的访问类型可以是public,protected,但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。
5. 抽象类中可以包含静态方法,接口中不能包含静态方法
6. 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。
7. 一个类可以实现多个接口,但只能继承一个抽象类
8、抽象类中的方法可以不被实现,但接口的中的方法要全部实现

15、请说明一下final, finally, finalize的区别

● final 用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。
● finally是异常处理语句结构的一部分,表示总是执行。
● finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。

16、请说明面向对象的特征有哪些方面

(1)抽象:
抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。抽象并不打算了解全部问题,而只是选择其中的一部分。
(2)继承:
对象的一个新类可以从现有的类中派生,这个过程称为类继承。新类继承了原始类的特性,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类)。派生类可以从它的基类那里继承方法和实例变量,并且类可以修改或增加新的方法使之更适合特殊的需要。
(3)封装:
封装是把过程和数据包围起来,对数据的访问只能通过已定义的界面。这些对象通过一个受保护的接口来访问其他对象。
(4) 多态性:
多态性是指允许不同类的对象对同一消息作出响应。多态性包括参数化多态性和包含多态性。多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好的解决了应用程序函数同名问题。

17、请你谈谈如何通过反射创建对象?

● 方法1:通过类对象调用newInstance()方法,例如:String.class.newInstance()
● 方法2:通过类对象的getConstructor()或getDeclaredConstructor()方法获得构造器(Constructor)对象并调用其newInstance()方法创建对象,例如:

String.class.getConstructor(String.class).newInstance("Hello");
package stu01.com.bean;import java.lang.reflect.InvocationTargetException;public   class AbtractClassTest {public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {//方法一:通过Class.class.newInstance();User user =User.class.newInstance();user.setId("1001");user.setName("张三");System.out.println(user);//方法二:通过获取构造器String str = String.class.getConstructor(String.class).newInstance("hello");System.out.println(str);}
}

18、请你解释一下类加载机制,双亲委派模型,好处是什么?

当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。

类加载器的类别
● BootstrapClassLoader(启动类加载器)
c++编写,加载java核心库 java.*,构ExtClassLoader和AppClassLoader。由于引导类加载器涉及到虚拟机 本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作
● ExtClassLoader (标准扩展类加载器)
java编写,加载扩展库,如classpath中的jre ,javax.*或者
java.ext.dir 指定位置中的类,开发者可以直接使用标准扩展类加载器。
● AppClassLoader(系统类加载器)
java编写,加载程序所在的目录,如user.dir所在的位置的class
● CustomClassLoader(用户自定义类加载器)
java编写,用户自定义的类加载器,可加载指定路径的class文件
双亲委派机制的作用
1、防止重复加载同一个.class。通过委托去向上级询问,如果加载过了,就不用再加载一遍。保证数据安全。
2、保证核心.class不能被篡改。通过委托方式,不会去篡改核心.class,即使篡改也不会去加载,即使加载也不会是同一个.class对象了。不同的加载器加载同一个.class也不是同一个Class对象。这样保证了Class执行安全。

19、请列举你所知道的Object类的方法并简要说明

● clone() 创建并返回此对象的一个副本。
● equals(Object obj) 指示某个其他对象是否与此对象“相等”。
● finalize()当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。
● getClass()返回一个对象的运行时类。
● hashCode()返回该对象的哈希码值。
● notify()唤醒在此对象监视器上等待的单个线程。
● notifyAll()唤醒在此对象监视器上等待的所有线程。
● toString()返回该对象的字符串表示。
● wait()导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。

20、请说明类和对象的区别

1.类是对某一类事物的描述,是抽象的;而对象是一个实实在在的个体,是类的一个实例。
比如:“人”是一个类,而“教师”则是“人”的一个实例。
2.对象是函数、变量的集合体;而类是一组函数和变量的集合体,即类是一组具有相同属性的对象集合体。

21、请解释一下String为什么不可变?


String不可变很简单,如上图,给 一个已有字符串"abcd"第二次赋值成“abcdel",不是在原地址上修改数据,而是重新指向一个新的对象,新地址。
1、String为什么不可变?
首先String类是用final关键字修饰的,这说明String不可继承。再看下面,String类的存储字段value是个char[]数组,而且也是用final修饰的。final修饰的字段创建以后就不可改变。
2、字符串常量池
字符串恐怕是Java中最常用的数据形式了,因此把字符串缓存起来,并且重复使用他们会节省大量堆空间(堆内存用来存储Java中的对象,无论是成员变量、局部变量、还是类变量,他们指向的对象都存储在堆内存中),因为不同的字符串变量引用的是字符串常量池中的同一个对象。这也正是字符串常量池存在的目的。

String s1 = "沉默王二";
String s2 = "沉默王二";System.out.println(s1 == s2); // true

由于字符串常量池的存在,所以两个不同的变量都指向了池中同一个字符串对象,从而节省了稀缺的内存资源。如果是通过 new 关键字创建的对象,则需要新的堆空间。
3、线程安全
因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享,这样便不用因为线程安全问题而使用同步,字符串自己便是线程安全的;

1.2 Java集合篇

1、请说明List、Map、Set三个接口存取元素时,各有什么特点?

List以特定索引来存取元素,可以有重复元素。
Set不能存放重复元素(用对象的equals()方法来区分元素是否重复)。
Map保存键值对(key-value)映射,映射关系可以是一对一或多对一。S
et和Map容器都有基于哈希存储和排序树的两种实现版本,基于哈希存储的版本理论存取时间复杂度为O(1),而基于排序树版本的实现在插入或删除元素时会按照元素或元素的键(key)构成排序树从而达到排序和去重的效果。

2、阐述ArrayList、Vector、LinkedList的存储性能和特性

ArrayList 和Vector(/ˈvektə®/)都是使用数组方式存储数据,它们都允许直接按索引来查找元素,但是当插入元素时要涉及数组元素移动等内存的操作,所以查询数据快而插入数据慢,Vector中的方法由于添加了synchronized关键字,因此Vector是线程安全的容器,但性能上较ArrayList差,因此已经是Java中的遗留容器。
LinkedList使用双向链表实现存储(将内存中零散的内存单元通过引用关联起来,形成一个可以按序号索引的线性结构,这种链式存储方式与数组的连续存储方式相比,内存的利用率更高),按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。
由于ArrayList和LinkedListed都是非线程安全的,如果遇到多个线程操作同一个容器的场景,则可以使用下面两种方式。

//方式一:使用 CopyOnWriteArrayList类CopyOnWriteArrayList list = new CopyOnWriteArrayList();list.add(1);list.add(2);list.add(3);Iterator iterator = list.iterator();while (iterator.hasNext()){System.out.println(iterator.next());}
//方式二:使用Collections工具类的synchronizedList()方法将ArrayList转为线程安全的集合类
//第二种的性能较高List<Object> list2 = Collections.synchronizedList(new ArrayList<Object>());list2.add(1);list2.add(2);list2.add(3);

3、判断List、Set、Map是否继承自Collection接口?

List、Set 是,Map 不是。Map是键值对映射容器,与List和Set有明显的区别,而Set存储的零散的元素且不允许有重复元素(数学中的集合也是如此),List是线性结构的容器,适用于按数值索引访问元素的情形。

4、请说明Collection 和 Collections的区别

● Collection是集合类的上级接口,继承与他的接口主要有Set 和List.
● Collections是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。

5、请说明ArrayList和LinkedList的区别?

ArrayList和LinkedList都实现了List接口,他们有以下的不同点:
ArrayList是基于索引的数据接口,它的底层是数组。它可以以O(1)时间复杂度对元素进行随机访问。与此对应,LinkedList是以元素列表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)。
相对于ArrayList,LinkedList的插入,添加,删除操作速度更快,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者是更新索引。
LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。

6、请你说明HashMap和Hashtable的区别?

HashMap和Hashtable都实现了Map接口,因此很多特性非常相似。但是,他们有以下不同点:

  • HashMap允许键和值是null,而Hashtable不允许键或者值是null。
  • Hashtable是同步的,而HashMap不是。因此,HashMap更适合于单线程环境,而- Hashtable适合于多线程环境。
  • 一般认为Hashtable是一个遗留的类(现在并发场景下更多使用的是ConcurrentHashMap)

7、请说说快速失败(fail-fast)和安全失败(fail-safe)的区别?

快速失败:在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception(并发修改异常)。
原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expected(/ɪkˈspektɪd/)modCount值,是的话就返回遍历;否则抛出异常,终止遍历。
安全失败:采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception。
缺点:基于拷贝内容的优点是避免了Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。

8、请你说说Iterator和ListIterator的区别?

Iterator和ListIterator的区别是:
● Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。
● Iterator对集合只能是前向遍历,ListIterator既可以前向也可以向后。
● ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。

9、请简单说明一下什么是迭代器?

Iterator提供了统一遍历操作集合元素的统一接口, Collection接口实现Iterable接口,每个集合都通过实现Iterable接口中iterator()方法返回Iterator接口的实例, 然后对集合的元素进行迭代操作.
有一点需要注意的是:在迭代元素的时候不能通过集合的方法删除元素, 否则会抛出Concurrent (/kənˈkʌrənt/
)ModificationException(并发修改) 异常. 但是可以通过Iterator接口中的remove()方法进行删除.

10、什么是Hash?

1、核心理论:
Hash也称散列、哈希,对应的英文都是Hash,基本原理都是把任意长度的输入,通过Hash算法变成固定长度的输出,这个映射的规则就是对应的Hash算法,而原始数据映射后的二进制串就是哈希值。
2、Hash的特点:

  • 1、从hash值不可以反向推导出原始的数据
  • 2、输入数据的微小变化会得到完全不同的hash值,相同的数据会得到相同的值
  • 3、哈希算法的执行效率要高效,长的文本也能快速地计算出哈希值
  • 4、哈希算法的冲突概率要小
  • 5、由于hash的原理是将输入空间的值映射成hash空间内,而hash值的空间远小于输入的空间,根据抽屉原理,一定会存在不同的输入被映射成相同输出的情况。

3、 实现一个Hash算法
如何实现一个Hash算法来实现,输入一个内容,从而得到一个在0-255之间的Hash值,输入相同的内容得到相同的Hash值,并且尽量减少哈希冲突。

  • 1、通过得到对象的hashcode值
  • 2、因为255的二进制是8位(11111111),所以将Hashcode值右移8位得到新的值
  • 3、将左移的值与原hashcode值进行异或运算得到的值
  • 4、将异或得到值与255的二进制进行与运算就是最终的Hash值了
String str="我们的梦想";
System.out.println(str.hashCode());
System.out.println(str.hashCode()>>8);
int yihuo=str.hashCode()>>8^str.hashCode();
System.out.println(str.hashCode()>>8^str.hashCode());
System.out.println(yihuo&255);
输出结果:
字符串的hashcode值: -1952899658
字符串的hashcode值右移8位值: -7628515
字符串的hashcode值与右移8位后的值进行异或运算: 1947370667
得到的异或值与255进行与运算: 171

提示: 与运算:两者都为1的时候结果才为1 异或运算:两者相同结果为0,两者不同结果为1

11、CurrentHashMap的优点:

   在多线程的情况下,HashMap的操作会引起死循环,导致CPU的占有量达到100%,所以在并发的情况下,我们不会使用HashMap.至于为什么会引起死循环,大概是因为HashMap的Entry链表会形成链式的结构,一旦形成了Entry的链式结构,链表中的next指针就会一直不为空,这样就会导致死循环

不使用HashTable的原因?
因为HashTable是使用Synchronize锁来保证线程安全的,并且其中的方法都被Synchronize加了锁,即当有一个线程拥有锁的时候,其他的线程都会进入阻塞或者轮询状态,这样会使得效率越来越低
使用currentHashMap的锁分段技术可以有效的提高并发访问率
HashTable访问效率低下的原因,就是因为所有的线程在竞争同一把锁.如果容器中有多把锁,不同的锁锁定不同的位置,这样线程间就不会存在锁的竞争,这样就可以有效的提高并发访问效率,这就是currentHashMap所使用的锁分段技术
将数据一段一段的存储,然后为每一段都配一把锁,当一个线程只是占用其中的一个数据段时,其他段的数据也能被其他线程访问

12、为什么map的加载因子是0.75?

提高空间利用率和减少查询成本的折中,主要是泊松分布,0.75的话碰撞最小,
HashMap有两个参数影响其性能:初始容量和加载因子。容量是哈希表中桶的数量,初始容量只是哈希表在创建时的容量。加载因子是哈希表在其容量自动扩容之前可以达到多满的一种度量。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行扩容、rehash操作(即重建内部数据结构),扩容后的哈希表将具有两倍的原容量。
通常,加载因子需要在时间和空间成本上寻求一种折衷。
加载因子过高,例如为1,虽然减少了空间开销,提高了空间利用率,但同时也增加了查询时间成本;
加载因子过低,例如0.5,虽然可以减少查询时间成本,但是空间利用率很低,同时提高了rehash操作的次数。
在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少rehash操作次数,所以,一般在使用HashMap时建议根据预估值设置初始容量,减少扩容操作。
选择0.75作为默认的加载因子,完全是时间和空间成本上寻求的一种折衷选择。

13、HashMap的扩容为何是2次幂

在hashmap的源码中。put方法会调用indexFor(int h, int length)方法,这个方法主要是根据key的hash值找到这个entry在Hash表数组中的位置,源码如下:

/**
* Returns index for hash code h.
*/
static int indexFor(int h, int length) {// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";return h & (length-1);
}

上述代码也相当于对length求模.
注意最后return的是h&(length-1)。如果length不为2的幂,比如15。那么length-1的2进制就会变成1110。在h为随机数的情况下,和1110做&操作。尾数永远为0。那么0001、1001、1101等尾数为1的位置就永远不可能被entry占用。这样会造成浪费,不随机等问题。 length-1 二进制中为1的位数越多,那么分布就平均。

14、集合和数组的区别?

数组存储的特点:
● 一旦初始化之后,其长度就确定了
● 数组一旦定义好,其元素的类型也就确定了,我们也就只能操作指定类型的数据了。
比如:String[] arr ; int[] arr1; Object [] arr2
数组的弊端:
● 一旦初始化以后,其长度就不可更改。
● 数组中提供的方法非常有限,对于添加、删除、插入数据等操作,非常不便,同时效率不高。
● 获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用。

1.3 Java线程篇

1、如何保证线程安全

● 通过合理的时间调度,避开共享资源的存取冲突。
● 保证任务与任务之间不存在共享资源。
● 保证在同一时刻只有一个线程能够访问共享资源

2、请你简要说明一下线程的基本状态以及状态之间的关系?

线程在一定条件下,状态会发生变化。线程一共有以下几种状态:
1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待获取CPU的使用权。即在就绪状态的进程除CPU之外,其它的运行所需资源都已全部获得。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
阻塞的情况分三种:
● 等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒,
● 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。
● 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

线程变化的状态转换图如下:

注:拿到对象的锁标记,即为获得了对该对象(临界区)的使用权限。即该线程获得了运行所需的资源,进入“就绪状态”,只需获得CPU,就可以运行。因为当调用wait()后,线程会释放掉它所占有的“锁标志”,所以线程只有在此获取资源才能进入就绪状态。

3、创建线程类的两种方式

1、线程的实现有两种方式,一是继承Thread类,二是实现Runnable接口,但不管怎样, 当我们new了这个对象后,线程就进入了初始状态;
2、当该对象调用了start()方法,就进入就绪状态;
3、进入就绪后,当该对象被操作系统选中,获得CPU时间片就会进入运行状态;
4、进入运行状态后情况就比较复杂了
● run(): run()方法或main()方法结束后,线程就进入终止状态;
● sleep() :当线程调用了自身的sleep()方法或其他线程的join()方法,进程让出CPU,然后就会进入阻塞状态(该状态既停止当前线程,但并不释放所占有的资源即调用sleep ()函数后,线程不会释放它的“锁标 志”。)。当sleep()结束或join()结束后,该线程进入就绪状态,继续等待OS(操作系统)分配CPU时间片。
● yield()方法:线程调用了yield()方法,意思是放弃当前获得的CPU时间片,回到就绪状态,这时与其他进程处于同等竞争状态,OS有可能会接着又让这个进程进入运行状态; 调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间片从而需要转到另一个线程。yield()只是使当前线程重新回到就绪状态,所以执行yield()的线程有可能在进入到就绪状态后马上又被执行。
● synchroniza(同步):当线程刚进入可运行状态(注意,还没运行),发现将要调用的资源被synchroniza(同步),获取不到锁标记,将会立即进入锁池状态,等待获取锁标记(这时的锁池里也许已经有了其他线程在等待获取锁标记,这时它们处于队列状态,既先到先得),一旦线程获得锁标记后,就转入就绪状态,等待OS分配CPU时间片;
● wait()和 notify() 方法:当线程调用wait()方法后会进入等待队列(进入这个状态会释放所占有的所有资源,与阻塞状态不同),进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒(由于notify()只是唤醒一个线程,但我们由不能确定具体唤醒的是哪一个线程,也许我们需要唤醒的线程不能够被唤醒,因此在实际使用时,一般都用notifyAll()方法,唤醒有所线程),线程被唤醒后会进入锁池,等待获取锁标记。
wait() 使得线程进入阻塞状态,它有两种形式:
一种允许指定以毫秒为单位的一段时间作为参数;另一种没有参数。前者当对应的 notify()被调用或者超出指定时间时线程重新进入可执行状态即就绪状态,后者则必须对应的 notify()被调用。当调用wait()后,线程会释放掉它所占有的“锁标志”,从而使线程所在对象中的其它synchronized数据可被别的线程使用。

4、请你解释一下什么是线程池(thread pool)?

   在面向对象编程中,创建和销毁对象是很费时间的。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁,这就是”池化资源”技术产生的原因。线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。

4.1 下面是4种常见的线程池:
● newSingleThreadExecutor( /ɪɡˈzektə®/):创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
● newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
● newCachedThreadPool(推荐):创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小
● newScheduled( /ˈskedʒuːld/)ThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

4.2 如何使用线程池

public class JucTest {public static void main(String[] args) {//创建一个单线程线程池ExecutorService executorService = Executors.newSingleThreadExecutor();Thread t1 = new Thread(new Work());Thread t2 = new Thread(new Work());Thread t3 = new Thread(new Work());executorService.submit(t1);executorService.submit(t2);executorService.submit(t3);executorService.shutdown();}
}class Work implements Runnable{@Overridepublic void run() {String name = Thread.currentThread().getName();System.out.println(name+"执行完毕!");}
}

4.3 自定义线程池

如何实现自定义线程池 可点击传送门自定义线程池

4.4 线程池的几种拒绝策略:

当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:

  1. AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
  2. DiscardPolicy:也是丢弃任务,但是不抛出异常。
  3. DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复该过程)。
  4. CallerRunsPolicy:由调用线程处理该任务。

4.5 线程池有哪些参数?各个参数的作用?

corePoolSize(核心工作线程):当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,知道创建的线程数大于或等于corePoolSize时。
maxmumPoolSize(最大线程数):线程池所允许的最大线程个数。当队列满了,且已创建的线程数小于maxmumPoolSize(最大线程数),则线程池会创建新的线程来执行任务。另外,对于无界队列,可忽略该参数。
KeepAliveTime(多余线程存活时间):当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,知道线程池中的线程数小于等于核心线程数。
workQueue(队列):用于传输和保存等待执行任务的阻塞队列。
ThreadFactor(线程创建工厂):用于创建新线程。threadFactor创建的线程也是采用new Thread()方式,threadFactor创建的线程名都具有统一的风格:pool-m-thread-n(m:为线程池的编号,n为线程池内的线程编号)。
handler (拒绝策略): 当线程池和队列都满了,再加入线程会执行此策略

5、请介绍一下线程同步和线程调度的相关方法

● wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
● sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常;
● notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;
● notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;
● Lock(): 通过Lock接口提供了显式的锁机制,增强了灵活性以及对线程的协调。Lock接口中定义了加锁(lock())和解锁(unlock())的方法;
● semaphore: 此外,Java 5还提供了信号量机制semaphore( /ˈseməfɔː®/) ,信号量可以用来限制对某个共享资源进行访问的线程的数量。在对资源进行访问之前,线程必须得到信号量的许可(调用Semaphore对象的acquire【 /əˈkwaɪə®/】()方法);在完成对资源的访问后,线程必须向信号量归还许可(调用Semaphore对象的release()【 /rɪˈliːs/】方法)。

6、synchronized与lock()的区别

 synchronized是Java的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。JDK1.5以后引入了自旋锁、锁粗化、轻量级锁,偏向锁来优化关键字的性能。
Lock是一个接口,而synchronized是Java中的关键字;synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

7、锁的种类

● 公平锁/非公平锁
● 可重入锁
● 独享锁/共享锁
● 互斥锁/读写锁
● 乐观锁/悲观锁
● 分段锁
● 偏向锁/轻量级锁/重量级锁
● 自旋锁
上面是很多锁的名词,这些分类并不是全是指锁的状态,有的指锁的特性,有的指锁的设计,下面总结的内容是对每个锁的名词进行一定的解释。

8、公平锁与非公平锁

  1. 公平锁是指多个线程按照申请锁的顺序来获取锁。
  2. 非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能会造成优先级反转或者饥饿现象。
  3. 对于Java ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。
  4. 对于synchronized而言,也是一种非公平锁。由于其并不像ReentrantLock是通过AQS的来实现线程调度,所以并没有任何办法使其变成公平锁。

9、可重入锁(递归锁)

可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。对于Java ReentrantLock而言, 其名字是Re entrant Lock即是重新进入锁。对于synchronized而言,也是一个可重入锁。可重入锁的一个好处是可一定程度避免死锁。
synchronized void setA() throws Exception{
Thread.sleep(1000);
setB();
}

synchronized void setB() throws Exception{
Thread.sleep(1000);
}
上面的代码就是一个可重入锁的一个特点,如果不是可重入锁的话,setB可能不会被当前线程执行,可能造成死锁。

10、独享锁/共享锁

独享锁是指该锁一次只能被一个线程所持有;共享锁是指该锁可被多个线程所持有。
对于Java ReentrantLock而言,其是独享锁。但是对于Lock的另一个实现类ReadWriteLock,其读锁是共享锁,其写锁是独享锁。读锁的共享锁可保证并发读是非常高效的,读写、写读 、写写的过程是互斥的。独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。对于synchronized而言,当然是独享锁。

11、互斥锁/读写锁

上面说到的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是具体的实现。互斥锁在Java中的具体实现就是ReentrantLock;读写锁在Java中的具体实现就是ReadWriteLock。

//读写锁测试
package com;import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;public class Test {public static void main(String[] args){for(int i = 0; i < 6; i++){new Thread(new Runnable() {@Overridepublic void run() {Cache.put("key", new String(Thread.currentThread().getName() + " joke"));}}, "threadW-"+ i).start();new Thread(new Runnable() {@Overridepublic void run() {Cache.get("key");}}, "threadR-"+ i).start();}}
}class Cache {static Map<String, Object> map = new HashMap<String, Object>();static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();static Lock r = rwl.readLock();static Lock w = rwl.writeLock();// 获取一个key对应的valuepublic static final Object get(String key) {r.lock();System.out.println(Thread.currentThread().getName()+"获取读锁");try {//            System.out.println("get " + Thread.currentThread().getName());return map.get(key);} finally {System.out.println(Thread.currentThread().getName()+"释放读锁");r.unlock();}}// 设置key对应的value,并返回旧有的valuepublic static final Object put(String key, Object value) {w.lock();System.out.println(Thread.currentThread().getName()+"获取写锁");try {//            System.out.println("put " + Thread.currentThread().getName());return map.put(key, value);} finally {System.out.println(Thread.currentThread().getName()+"释放写锁");w.unlock();}}}
结果:

12、乐观锁/悲观锁

乐观锁与悲观锁不是指具体的什么类型的锁,而是指看待并发同步的角度。
悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。比如Java里面的同步原语synchronized关键字的实现就是悲观锁。
乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS(Compare and Swap 比较并交换)实现的。

13、分段锁

分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作,ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。当需要put元素的时候,并不是对整个HashMap进行加锁,而是先通过hashcode来知道他要放在那一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。但是,在统计size的时候,就是获取HashMap全局信息的时候,就需要获取所有的分段锁才能统计。
分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作,加快了程序的执行效率。

14、偏向锁/轻量级锁/重量级锁

这三种锁是指锁的状态,并且是针对synchronized。在Java 5通过引入锁升级的机制来实现高效synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。
● 偏向锁:是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。
● 轻量级锁:是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。
● 重量级锁:是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。

15、什么是AQS?

AbstractQueuedSynchronized [ /kjuːd/] 抽象的队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock

AQS维护了一个volatile /ˈvɒlətaɪl/ int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)。state的访问方式有三种:
● getState()
● setState()
● compareAndSetState()

AQS定义两种资源共享方式:Exclusive [/ɪkˈskluːsɪv/](独占,只有一个线程能执行,如ReentrantLock [riːˈɛntrənt])和Share(共享,多个线程可同时执行,如ReadWriteLock。

不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。自定义同步器实现时主要实现以下几种方法:
● isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
● tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
● tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
● tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
● tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。

以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。
再以CountDownLatch以例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。
一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock。

16、什么是CAS?

CAS(Compare and Swap 【/swɒp/】比较并交换)是乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。
CAS操作中包含三个操作数——需要读写的内存位置(V)、进行比较的预期原值(A)和拟写入的新值(B)。如果内存位置V的值与预期原值A相匹配,那么处理器会自动将该位置值更新为新值B,否则处理器不做任何操作。无论哪种情况,它都会在CAS 指令之前返回该位置的值(在CAS的一些特殊情况下将仅返回CAS是否成功,而不提取当前值)。

17、JAVA对CAS的支持:

在JDK1.5中新增java.util.concurrent包就是建立在CAS之上的。相对于synchronized 这种阻塞算法,CAS是非阻塞算法的一种常见实现。所以java.util.concurrent在性能上有了很大的提升。
以java.util.concurrent包中的AtomicInteger为例,看一下在不使用锁的情况下是如何保证线程安全的。主要理解 getAndIncrement方法,该方法的作用相当于 ++i 操作。

public class AtomicInteger extends Number implements java.io.Serializable {  private volatile int value; public final int get() {  return value;  }  public final int getAndIncrement() {  for (;;) {  int current = get();  int next = current + 1;  if (compareAndSet(current, next))  return current;  }  }  public final boolean compareAndSet(int expect, int update) {  return unsafe.compareAndSwapInt(this, valueOffset, expect, update);  }
}

18、介绍下ThreadLocal和它的应用场景

  ThreadLocal顾名思义是线程私有的局部变量存储容器,可以理解成每个线程都有自己专属的存储容器,它用来存储线程私有变量,其实它只是一个外壳,内部真正存取是一个Map。总之记住一句话:ThreadLocal存储的变量属于当前线程。ThreadLocal经典的使用场景是为每个线程分配一个 JDBC 连接 Connection,这样就可以保证每个线程的都在各自的 Connection 上进行数据库的操作,不会出现 A 线程关了 B线程正在使用的 Connection。 另外ThreadLocal还经常用于管理Session会话,将Session保存在ThreadLocal中,使线程处理多次处理会话时始终是同一个Session。

19、如何保证线程按一定规律执行?

下面说明4种方式实现
● 使用线程自带的join()方法 :JOIN()方法的作用是调用线程等待该线程完成后,才能继续用下运行。例如线程B中的run()方法调用了线程A的join()方法,那么线程B就会等到线程A执行完后再执行
● 使用CountDownLatch :这个类使一个线程等待其他线程各自执行完毕后再执行。
● 使用单线程的线程池(newSingleThreadExecutor):每次只会创建一个可执行的线程执行任务。
● 使用CompletableFuture任务回调来实现

import java.util.concurrent.CompletableFuture;public class ThreadDemo {public static void main(String[] args)  {Thread t1 = new Thread(new Work(),"线程1");Thread t2 = new Thread(new Work(),"线程2");Thread t3 = new Thread(new Work(),"线程3");CompletableFuture.runAsync(()-> t1.start()).thenRun(()->t2.start()).thenRun(()->t3.start());}static class Work implements Runnable{@Overridepublic void run() {System.out.println("执行 : " + Thread.currentThread().getName());}}
}

这四种的具体使用方式及步骤请点击传送保证多线程顺序执行

20、如何实现线程同步?

● 同步方法:即有synchronized关键字修饰的方法,由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。需要注意, synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类。
● 同步代码块:即有synchronized关键字修饰的语句块,被该关键字修饰的语句块会自动被加上内置锁,从而实现同步。需值得注意的是,同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。
● ReentrantLock:Java 5新增了一个java.util.concurrent包来支持同步,其中ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。需要注意的是,ReentrantLock还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,因此不推荐使用。
● volatile:volatile关键字为域变量的访问提供了一种免锁机制,使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新,因此每次使用该域就要重新计算,而不是使用寄存器中的值。需要注意的是,volatile不会提供任何原子操作,它也不能用来修饰final类型的变量。
● 原子变量:在java的util.concurrent.atomic /əˈtɒmɪk/ 包中提供了创建了原子类型变量的工具类,使用该类可以简化线程同步。例如AtomicInteger 表可以用原子方式更新int的值,可用在应用程序中(如以原子方式增加的计数器),但不能用于替换Integer。可扩展Number,允许那些处理机遇数字类的工具和实用工具进行统一访问。

21、线程实现通信的方式?

方式一:使用 volatile 关键字
基于 volatile 关键字来实现线程间相互通信是使用共享内存的思想,大致意思就是多个线程同时监听一个变量,当这个变量发生变化的时候 ,线程能够感知并执行相应的业务。这也是最简单的一种实现方式

public class TestSync {// 定义一个共享变量来实现通信,它需要是volatile修饰,否则线程不能及时感知static volatile boolean notice = false;public static void main(String[] args) {List<String>  list = new ArrayList<>();// 实现线程AThread threadA = new Thread(() -> {for (int i = 1; i <= 10; i++) {list.add("abc");System.out.println("线程A向list中添加一个元素,此时list中的元素个数为:" + list.size());try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}if (list.size() == 5)notice = true;}});// 实现线程BThread threadB = new Thread(() -> {while (true) {if (notice) {System.out.println("线程B收到通知,开始执行自己的业务...");break;}}});// 需要先启动线程BthreadB.start();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 再启动线程AthreadA.start();}
}

方式二:使用Object类的wait() 和 notify() 方法
众所周知,Object类提供了线程间通信的方法:wait()、notify()、notifyaAl(),它们是多线程通信的基础,而这种实现方式的思想自然是线程间通信。
注意: wait和 notify必须配合synchronized使用,wait方法释放锁,notify方法不释放锁
方式三:join方式
join其实合理理解成是线程合并,当在一个线程调用另一个线程的join方法时,当前线程阻塞等待被调用join方法的线程执行完毕才能继续执行,所以join的好处能够保证线程的执行顺序,但是如果调用线程的join方法其实已经失去了并行的意义,虽然存在多个线程,但是本质上还是串行的,最后join的实现其实是基于等待通知机制的。
。。。 还有一些其他的方式省略

22、阻塞线程的方式有哪些?

当发生如下情况时,线程会进入阻塞状态:
● 线程调用sleep()方法主动放弃所占用的处理器资源;
● 线程调用了一个阻塞式IO方法,在该方法返回之前,该线程被阻塞;
● 线程试图获得一个同步监视器(锁,volailte,原子变量等…)但该同步器正被其他线程所持有;
● 调用wait()方法后,线程正在等待某个通知(notify);

23、synchronized 用的锁是存在Java对象头里的,那么什么是对象头呢?

我们以 Hotspot 虚拟机为例进行说明,Hopspot 对象头主要包括两部分数据:Mark Word(标记字段) 和 Klass Pointer(类型指针)。
● Mark Word:默认存储对象的HashCode,分代年龄和锁标志位信息。这些信息都是与对象自身定义无关的数据,所以Mark Word被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据。它会根据对象的状态复用自己的存储空间,也就是在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化。
● Class Point:对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

1.4 JVM篇

1、请简单描述一下JVM加载class文件的原理是什么?

JVM中类的装载是由ClassLoader和它的子类来实现的,Java ClassLoader 是一个重要的Java运行时系统组件。它负责在运行时查找和装入类文件的类。
Java中的所有类,都需要由类加载器装载到JVM中才能运行。类加载器本身也是一个类,而它的工作就是把class文件从硬盘读取到内存中。在写程序的时候,我们几乎不需要关心类的加载,因为这些都是隐式装载的,除非我们有特殊的用法,像是反射,就需要显式的加载所需要的类。
类装载方式,有以下两种:
● 隐式装载,程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中
● 显式装载,通过class.forname()等方法,显式加载需要的类 ,隐式加载与显式加载的区别:两者本质是一样的。
按需加载:Java类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类(像是基类)完全加载到jvm中,至于其他类,则在需要的时候才加载。这当然就是为了节省内存开销。

2、什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”?

Java虚拟机是一个可以执行Java字节码的虚拟机进程。Java源文件被编译成能被Java虚拟机执行的字节码文件。
Java被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译,Java虚拟机让这个变为可能。

3、jvm最大内存限制多少?

对内存分配:
JVM初始化分配的内存由-Xms指定,默认的内存就是物理内存的1/64;JVM最大分配的内存由-Xmx指定,默认是1/4。默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。因此服务器一般设置-Xms、-Xmx相等以避免在每次GC后调整堆的大小。
非堆内存分配
JVM使用-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;由XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。

4、在Java虚拟机中,哪些对象可作为root对象?

● 虚拟机栈中引用的对象
● 本地方法栈内引用的对象
● 方法区中类静态属性引用的对象
● 方法区中常量引用的对象
比如:字符串常量池(String Table)中的引用
● 所有被同步锁synchronized持有的对象
● Java虚拟机内部的引用
● 基本数据类型对象对应的Class对象,一些常驻的异常对象(如:NullpointerException、OutOfMemmoryError),系统类加载器
● 反映java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。

5、如何判断对象是否需要回收、

● 即便在可达性算法中不可达对象,也并非是”非回收不可的“,这时候他们暂时处于”等待“阶段,要真正回收一个对象,至少要经历两次标记回收的过程:如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”。(即意味着直接回收)
● finalize()方法是对象逃脱回收的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模的标记,如果对象要在finalize()中跳出回收——只要重新与引用链上的任何一个对象建立关联即可,譬如把自己(this关键字)赋值给某个类变量或者对象的成员变量,那在第二次标记时它将被移除出“即将回收”的集合;如果对象这时候还没有逃脱,那基本上它就真的被回收了。

6、请说明一下eden和survial区的含义以及工作原理

目前主流的虚拟机实现都采用了分代收集的思想,把整个堆区划分为新生代和老年代;新生代又被划分成Eden空间、From Survivor【/səˈvaɪvə®/】 和To Survivor三块区域;
我们把Eden :From:To 空间大小设置为8:1:1,对象总是在Eden区出生,From Survivor保存当前的幸存对象,To Srvivoru为空。
一次GC发生后:
● Eden区活着的对象+From Survivor存储的对象复制到To Survivor;
● 清空Eden和From Survivor;
● 颠倒From Survivor和To Survivor的逻辑关系
可以看出,只有Eden空间快满的时候才会触发Minor GC.而Eden空间占新生代的绝大部分,所以Minor GC的频率得以降低。当然,使用两个Survivor这种方式我们也付出了一定的代价,如10%的空间浪费、复制对象的开销等。

7、请简单描述下JVM分区都有哪些?

java内存通常被划分为5个区域:程序计数器(Program Count Register)、本地方法栈(Native Stack)、方法区(Methon Area)、栈(Stack)、堆(Heap)。
关于更详细的运行时数据区的介绍可点击传送门JVM从入门到精通之运行时数据区分析

8、简单描述下类的加载过程

加载:
加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据入口。注意:这里不一定非要从一个Class文件获取,这里即可以从zip包中读取(比如:从jar包和war包中读取)
链接-验证:
这一阶段的主要目的是为了确保Class文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
链接-准备:
准备阶段是正式为类变量分配内存并设置类变量的初始值阶段,即在方法区中分配这些变量所使用的内存空间。注意这里所说的初始值概念,比如一个类变量定义为:
public static int v=8080;
实际上变量v在准备阶段过后的初始值为0,而不是8080,将赋值为8080的put static指令是程序被编译后,存放于类构造器方法之中。
但是注意如果声明为:
public static final int v=8080;
在编译阶段会为v生成ConstantValue属性,在准备阶段虚拟机会根据constantValue属性将v赋值为8080。
链接-解析
解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。符号引用就是class文件中的:
● CONSTANT_Class_info
● CONSTANT_Field_info
● CONSTANT_Method_info等类型的常量
初始化
初始化阶段是类加载最后一个阶段,前面的类加载阶段之后,除了在加载阶段可以自定义类加载器以外,其他操作都有JVM主导。到了初始化阶段,才开始真正执行类中定义的Java程序代码。
初始化阶段是执行类构造器方法的过程。方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句合并而成的。虚拟机会保证方法执行之前,父类的方法已经执行完毕。

9、JVM的有哪些回收算法?CMS采用那种回收算法?使用CMS怎样解决内存碎片的问题呢?

垃圾回收算法

● 标记-清除
标记-清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。在标记阶段首先通过根节点,标记所有根节点开始的对象,未被标记的对象就是未被引用的垃圾对象。然后,在清除阶段,清除所有未被标记的对象。标记-清除算法带来的一个问题就是会存在大量的空间碎片,因为回收后的空间是不连续的,这样给打对象分配内存的时候可能会提前触发full gc。
● 复制算法
将现有的内存空间分为两块,每次使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收
现在的商业虚拟机都采用这种垃圾收集算法来回收新生代,IBM研究表明新生代中的对象98%是朝生夕死的,所以并不需要按照1:1的比例划分内存空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中的一块Survivor。当回收时,将Eden和Survivor中还存活着的对象一次性地拷贝到另外一个Survivor空间上,最后清理掉Eden和刚才用过的Survivor的空间。
● 标记-整理
复制-算法的高效性是建立在存活对象少、回收对象多的前提下的。这种情况在新生代经常发生,但是在老年代更常见的情况是大部分对象都是存活对象。如果依然使用复制算法,由于存活的对象较多,复制的成本也将很高。
标记-整理算法是一种老年代的回收算法,它在标记-清除的算法的基础上做了一些优化。首先也需要从根节点开始对所有可达对象做一次标记,但之后,它并不简单地清理未标记的对象,而是将所有的存活对象压缩到内存的一段。之后,清理边界外所有的空间。这种方法即避免了碎片的产生,又不需要两块相同的内存空间,因此,其性价比较高。

垃圾回收器
关于垃圾回收器可以点击传送门JVM从入门到精通之各垃圾回收器性能分析

10、什么是内存泄漏、内存溢出?

内存溢出: out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个intrger,但给它存了个long才能存下的数,那就是内存溢出。
内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出。
原因:
● 内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
● 集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
● 代码中存在死循环或循环产生过多重复的对象实体;
● 启动参数内存值设定的过小
解决方法:
● 第一步,修改JVM启动参数,直接增加内存。(-Xms,-Xmx参数一定不要忘记加。)
● 第二步,检查错误日志,查看“OutOfMemory”错误前是否有其它异常或错误。
● 第三步,对代码进行走查和分析,找出可能发生内存溢出的位置。
1、 检查对数据库查询中,是否有一次获得全部数据的查询
2、 检查代码中是否有死循环或递归调用
3、 检查是否有大循环重复产生新对象实体。
4、检查List、MAP等集合对象是否有使用完后,未清除的问题。List、 MAP等集合对象会始终存有对对象的引用。
● 第四步,使用内存查看工具动态查看内存使用情况

内存泄露 memory leak: 是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
memory leak会最终会导致out of memory!

1.5 Java IO篇

1、请问什么是序列化?以及如何实现Java序列化

序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可以将流化后的对象传输与网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。
序列化的实现: 将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Seriaizable只是为了标注该对象是可被序列化的,然后使用一个输出流(如FileOutputSream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。

2、Java 中有几种类型的流?

按照流的方向:输入流(inputStream)和输出流(outputStream)。
按照处理数据的单位: 字节流和字符流。字节流继承于 InputStream 和 OutputStream, 字符流继承于InputStreamReader 和 OutputStreamWriter 。

3、字节流如何转为字符流?

字节输入流转字符输入流通过 InputStreamReader 实现,该类的构造函数可以传入 InputStream 对象。
字节输出流转字符输出流通过 OutputStreamWriter 实现,该类的构造函数可以传入 OutputStream 对象。
FileInputStream fileInputStream = new FileInputStream(“d://abc.txt”);
//将字节流转为字符流
InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream);
//使用缓存读取
BufferedReader bufferedReader=new BufferedReader(inputStreamReader);

4、将一个java 对象序列化到文件中

public class Test {public static void main(String[] args) throws IOException, ClassNotFoundException {//对象输出流ObjectOutputStream objectOutputStream =new ObjectOutputStream(new FileOutputStream(new File("D://obj")));objectOutputStream.writeObject(new User("zhangsan"));objectOutputStream.close();//对象输入流ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("D://obj")));User user = (User) objectInputStream.readObject();System.out.println(user);objectInputStream.close();}
}

5、字节流和字符流的区别?

● 字节流读取的时候,读到一个字节就返回一个字节;字符流使用了字节流读到一个或多个字节(中文对应的字节数是两个,在 UTF-8 码表中是 3 个字节)时。先去查指定的编码表,将查到的字符返回。
● 字节流可以处理所有类型数据,如:图片,MP3,AVI视频文件,而字符流只能处理字符数据。只要是处理纯文本数据,就要优先考虑使用字符流,除此之外都用字节流。
● 字节流主要是操作 byte 类型数据,以 byte 数组为准,主要操作类就是 OutputStream、InputStream;字符流处理的单元为 2 个字节的 Unicode 字符,分别操作字符、字符数组或字符串,而字节流处理单元为 1 个字节,操作字节和字节数组。

6、BIO、NIO、AIO有什么区别?

● BIO (Blocking I/O): 同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。
● NIO (New I/O): NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象类。NIO中的N可以理解为Non-blocking。它支持面向缓冲的,基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发。
● AIO (Asynchronous I/O): AIO 也就是 NIO 2。它是异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步IO的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO操作本身是同步的。查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。

更多关于NIO、IO分析,请点击传送门NIO非阻塞式

7、什么是IO?为什么IO时线程会阻塞?

I/O其实就是input和output的缩写,即输入/输出。
而我们时常关心的磁盘I/O指的是硬盘和内存之间的输入输出。读取本地文件的时候,需要将磁盘的数据拷贝到内存中,修改本地文件的时候,需要把修改后的数据拷贝到磁盘中。
网络I/O指的是网卡与内存之间的输入输出。
当网络上的数据到来时,网卡需要将数据拷贝到内存中。当要发送数据给网络上的其他人时,需要将数据从内存拷贝到网卡里。
为什么都要跟内存交互呢?
我们的指令最终是由CPU执行的,究其原因是CPU与内存交互的速度远高于CPU和这些外部设备直接交互的速度。CPU的运行速度要远超过一个IO的速度,CPU不可能去等待一个要很久才能完成的IO的操作,所以就是当进行IO操作时线程需要进入一个阻塞的状态。当数据准备好了之后,才会进入CPU运行队列执行接下来的操作。

面试宝典Java篇(基础+高级+集合+线程+IO+JVM)相关推荐

  1. Java面试宝典系列之基础面试题String、变量、类与对象、集合类、SSH(三)

    Java面试宝典之数据结构基础 -- 线性表篇 作者:egg 邮箱:xtfggef@gmail.com 微博:http://weibo.com/xtfggef 博客:http://blog.csdn. ...

  2. very very good,Java面试宝典+Java核心知识集

    这几天刚整理出炉的两份最全"Java面试宝典+Java核心知识集"(very very good!!!),因此有了今天咱这篇文章,没错,我又来分享干货了!!! Java面试宝典 说 ...

  3. java map常用类及其方法_Day50.Map类常用的方法 -Java常用类、集合#、IO

    Day50.Map类常用的方法 -Java常用类.集合#.IO Day50.Map类常用的方法 -Java常用类.集合#.IO Map类常用的方法 . 总结: 常用方法 添加: put(Object ...

  4. Java面试宝典系列之基础面试题String、变量、类与对象、集合类、SSH(一)

    作者:egg 邮箱:xtfggef@gmail.com 微博:http://weibo.com/xtfggef 博客:http://blog.csdn.net/zhangerqing(转载请说明出处) ...

  5. 各公司 Java 面试题目整理(基础+高级+算法+数据库)

    包含 Java 面试的各个方面,史上最全,苦心整理最全 Java 各公司面试题目整理包括但不限于基础+高级+算法+数据库优化+算法优化,使用层面广,知识量大,涉及你的知识盲点.要想在面试者中出类拔萃就 ...

  6. 【前端面试宝典】超基础的vue知识

    写在前面 CSDN话题挑战赛第1期 活动详情地址:https://marketing.csdn.net/p/bb5081d88a77db8d6ef45bb7b6ef3d7f 参赛话题:前端面试宝典 话 ...

  7. Java多线程基础-6:线程安全问题及解决措施,synchronized关键字与volatile关键字

    线程安全问题是多线程编程中最典型的一类问题之一.如果多线程环境下代码运行的结果是符合我们预期的,即该结果正是在单线程环境中应该出现的结果,则说这个程序是线程安全的. 通俗来说,线程不安全指的就是某一代 ...

  8. Java学习基础:搞懂IO

    原文参考链接:吃透JavaIO:字节流.字符流.缓冲流 文章目录 1. 初识java IO 1.1 输入流与输出流 1.2 字节流与字符流 1.3 案例 2. IO 流对象 2.1 File类 2.2 ...

  9. Java面试宝典之:基础篇

    一.   Java基础部分 1.Java多态的具体体现 [必背] 面向对象编程有四个特征:抽象,封装,继承,多态. 多态有四种体现形式: 1. 接口和接口的继承. 2. 类和类的继承. 3. 重载. ...

最新文章

  1. linux时间轮算法,关于时间轮的设计 linux hashed Hierarchical timing wheel
  2. c++版a+b问题的各种无聊做法
  3. cve-2017–10271 XMLDecoder 反序列化漏洞 原理分析
  4. 数字化转型生态的三个层级(平台 场景 工具/技术)
  5. 2015-12-15 关于数量个
  6. 关于用Restful API下载网易云笔记时遇到的图片外链不能打开的问题
  7. linux php 版本切换,linux更换PHP版本,多个PHP版本切换
  8. uva 12627——Erratic Expansion
  9. C进阶指南(1):整型溢出和类型提升、内存申请和管理(转)
  10. java 类加载器-基础
  11. 【NOIP2013模拟】七夕祭
  12. java电子书大全 下载
  13. 引用 你唯一能把握的是变成最好的自己
  14. 520 miix 小兵 黑苹果,Hackintosh黑苹果长期维护机型整理清单
  15. Windows XP SP3 VOL 简体中文正式版 V201106
  16. postman接口测试提交实体参数实践
  17. 2014-04-12腾讯实习生笔试题目及解析
  18. sqlnet.ora
  19. JavaExcel模板下载(多sheet)
  20. PHP开发规范——转自ThinkPHP手册

热门文章

  1. 图文笔记,带你走进《未来简史》(6-10)
  2. while循环语句表白
  3. 上百部BBC经典纪录片,既学英语又涨知识,送给程序员们~~
  4. JavaCV 实现怀旧滤镜
  5. 未来实验A/B测的统计学原理
  6. 52讲轻松搞定网络爬虫(全)+笔记
  7. MAC系统JDK环境变量配置(包含找安装路径和解决执行source .bash_profile报错)
  8. vs打开sln是空白_周末惊喜版块 | 新文速递 强推全息网游无限流鬼怪文/软妹身大佬心锦鲤女主VS阴郁暴躁倒霉蛋男主【言情】01.20...
  9. educoder:第1关:实现直接插入排序
  10. vuejs中路由的传参以及路由props配置