1. 自我介绍
2. 做过的项目
(Java 基础)
3. Java的四个基本特性(抽象、封装、继承,多态),对多态的理解(多态的实现方式)以及在项目中那些地方用到多态
-
•Java的四个基本特性
○ 抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些
属性和行为,并不关注这些行为的细节是什么。
○继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到继承信息的
类被称为子类(派生类)。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段。
○ 封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我们编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口。
○ 多态性是指允许不同子类型的对象对同一消息作出不同的响应。
•多态的理解(多态的实现方式)
○方法重载(overload)实现的是编译时的多态性(也称为前绑定)。
○方法重写(override)实现的是运行时的多态性(也称为后绑定)。运行时的多态是面向对象最精髓的东西。
○要实现多态需要做两件事:1). 方法重写(子类继承父类并重写父类中已有的或抽象的方法);2). 对象造型(用父类型引用引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)。
•项目中对多态的应用
○ 举一个简单的例子,在物流信息管理系统中,有两种用户:订购客户和卖房客户,两个客户都可以登录系统,他们有相
同的方法Login,但登陆之后他们会进入到不同的页面,也就是在登录的时候会有不同的操作,两种客户都继承父类的
Login方法,但对于不同的对象,拥有不同的操作。
4. 面向对象和面向过程的区别?用面向过程可以实现面向对象吗?那是不是不能面向对象?
•面向对象和面向过程的区别
○ 面向过程就像是一个细心的管家,事无具细的都要考虑到。而面向对象就像是个家用电器,你只需要知道他的功能,不需要知道它的工作原理。
○ 面向过程”是一种是事件为中心的编程思想。就是分析出解决问题所需的步骤,然后用函数把这些步骤实现,并按顺序调用。面向对象是以“对象”为中心的编程思想。
○ 简单的举个例子:汽车发动、汽车到站
•这对于“面向过程”来说,是两个事件,汽车启动是一个事件,汽车到站是另一个事件,面向过程编程的过程中我们关心的是事件,而不是汽车本身。针对上述两个事件,形成两个函数,之 后依次调用。
•然而这对于面向对象来说,我们关心的是汽车这类对象,两个事件只是这类对象所具有的行为。而且对于这两个
行为的顺序没有强制要求。
•用面向过程可以实现面向对象吗
•那是不是不能面向对象
5. 重载和重写,如何确定调用哪个函数
•重载:重载发生在同一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载。
•重写:重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。根据不同的子类对象确定调用的那个方法。
6. 面向对象开发的六个基本原则(单一职责、开放封闭、里氏替换、依赖倒置、合成聚合复用、接口隔离),迪米特法则。在项目中用过哪些原则
•六个基本原则
○ 单一职责:一个类只做它该做的事情(高内聚)。在面向对象中,如果只让一个类完成它该做的事,而不涉及与它无关的领域就是践行了高内聚的原则,这个类就只有单一职责。
○ 开放封闭:软件实体应当对扩展开放,对修改关闭。要做到开闭有两个要点:①抽象是关键,一个系统中如果没有抽象类或接口系统就没有扩展点;②封装可变性,将系统中的各种可变因素封装到一个继承结构中,如果多个可变因素混杂在一起,系统将变得复杂而换乱。
○ 里氏替换:任何时候都可以用子类型替换掉父类型。子类一定是增加父类的能力而不是减少父类的能力,因为子类比父类的能力更多,把能力多的对象当成能力少的对象来用当然没有任何问题。
○依赖倒置:面向接口编程。(该原则说得直白和具体一些就是声明方法的参数类型、方法的返回类型、变量的引用类型时,尽可能使用抽象类型而不用具体类型,因为抽象类型可以被它的任何一个子类型所替代)
○ 合成聚和复用:优先使用聚合或合成关系复用代码。
○ 接口隔离:接口要小而专,绝不能大而全。臃肿的接口是对接口的污染,既然接口表示能力,那么一个接口只应该描述一种能力,接口也应该是高度内聚的。
•迪米特法则
○ 迪米特法则又叫最少知识原则,一个对象应当对其他对象有尽可能少的了解。
•项目中用到的原则
○ 单一职责、开放封闭、合成聚合复用(最简单的例子就是String类)、接口隔离
7. static和final的区别和用途
•Static
○ 修饰变量:静态变量随着类加载时被完成初始化,内存中只有一个,且JVM也只会为它分配一次内存,所有类共享静态变量。
○ 修饰方法:在类加载的时候就存在,不依赖任何实例;static方法必须实现,不能用abstract修饰。
○ 修饰代码块:在类加载完之后就会执行代码块中的内容。
○ 父类静态代码块->子类静态代码块->父类非静态代码块->父类构造方法->子类非静态代码块->子类构造方法
•Final
○ 修饰变量:
•编译期常量:类加载的过程完成初始化,编译后带入到任何计算式中。只能是基本类型。
•运行时常量:基本数据类型或引用数据类型。引用不可变,但引用的对象内容可变。
○ 修饰方法:不能被继承,不能被子类修改。
○ 修饰类:不能被继承。
○ 修饰形参:final形参不可变
8. Hash Map和Hash Table的区别,Hash Map中的key可以是任何对象或数据类型吗?HashTable是线程安全的么?
•Hash Map和Hash Table的区别
○Hashtable的方法是同步的,HashMap未经同步,所以在多线程场合要手动同步HashMap这个区别就像Vector和ArrayList一样。
○Hashtable不允许 null 值(key 和 value 都不可以),HashMap允许 null 值(key和value都可以)。
○两者的遍历方式大同小异,Hashtable仅仅比HashMap多一个elements方法。Hashtable 和 HashMap 都能通过values()方法返回一个 Collection ,然后进行遍历处理。两者也都可以通过 entrySet() 方法返回一个 Set , 然后进行遍历处理。
○HashTable使用Enumeration,HashMap使用Iterator。
○哈希值的使用不同,Hashtable直接使用对象的hashCode。而HashMap重新计算hash值,而且用于代替求模。
○Hashtable中hash数组默认大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。
○HashTable基于Dictionary类,而HashMap基于AbstractMap类
•Hash Map中的key可以是任何对象或数据类型吗
○ 可以为null,但不能是可变对象,如果是可变对象的话,对象中的属性改变,则对象HashCode也进行相应的改变,导
致下次无法查找到已存在Map中的数据。
○如果可变对象在HashMap中被用作键,那就要小心在改变对象状态的时候,不要改变它的哈希值了。我们只需要保证成员变量的改变能保证该对象的哈希值不变即可。
•HashTable是线程安全的么
○HashTable是线程安全的,其实现是在对应的方法上添加了synchronized关键字进行修饰,由于在执行此方法的时候需要获得对象锁,则执行起来比较慢。所以现在如果为了保证线程安全的话,使用CurrentHasxhMap。
9. HashMap和Concurrent HashMap区别, Concurrent HashMap 线程安全吗, Concurrent HashMap如何保证 线程安全?
•HashMap和ConcurrentHashMap区别?
○HashMap是非线程安全的,CurrentHashMap是线程安全的。
○ConcurrentHashMap将整个Hash桶进行了分段segment,也就是将这个大的数组分成了几个小的片段(segment),而且每个小的片段(segment)上面都有锁存在,那么在插入元素的时候就需要先找到应该插入到哪一个片段(segment),然后再在这个片段上面进行插入,而且这里还需要获取(segment)锁。
○ConcurrentHashMap让锁的粒度更精细一些,并发性能更好。
•ConcurrentHashMap 线程安全吗, ConcurrentHashMap如何保证 线程安全?
○ HashTable容器在竞争激烈的并发环境下表现出效率低下的原因是所有访问HashTable的线程都必须竞争同一把锁,那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。
○ get操作的高效之处在于整个get过程不需要加锁,除非读到的值是空的才会加锁重读。get方法里将要使用的共享变量都定义成volatile(不稳定的),如用于统计当前Segement大小的count字段和用于存储值的HashEntry的value。定义成volatile(不稳定的)的变量,能够在线程之间保持可见性,能够被多线程同时读,并且保证不会读到过期的值,但是只能被单线程写(有一种情况可以被多线程写,就是写入的值不依赖于原值),在get操作里只需要读不需要写共享变量count和value,所以可以不用加锁。
○ Put方法首先定位到Segment(片段),然后在Segment里进行插入操作。插入操作需要经历两个步骤,第一步判断是否需要对Segment里的HashEntry数组进行扩容,第二步定位添加元素的位置然后放在HashEntry数组里。
10. 因为别人知道源码怎么实现的,故意构造相同的hash的字符串进行攻击,怎么处理?那jdk7怎么办?
•怎么处理构造相同hash的字符串进行攻击?
○ 当客户端提交一个请求并附带参数的时候,web应用服务器会把我们的参数转化成一个HashMap存储,这个HashMap的逻辑结构如下:key1-->value1;
○ 但是物理存储结构是不同的,key值会被转化成Hashcode,这个hashcode有会被转成数组的下标:0-->value1;
○ 不同的string就会产生相同hashcode而导致碰撞,碰撞后的物理存储结构可能如下:0-->value1-->value2;
○ 1、限制post和get的参数个数,越少越好
2、限制post数据包的大小
3、WAF
•Jdk7 如何处理hashcode字符串攻击
○HashMap会动态的使用一个专门的treemap实现来替换掉它。
11. String、StringBuffer、StringBuilder以及对String不变性的理解
•String、StringBuffer、StringBuilder
○ 都是 final 类, 都不允许被继承;
○String 长度是不可变的, StringBuffer、StringBuilder 长度是可变的;
○StringBuffer 是线程安全的, StringBuilder 不是线程安全的,但它们两个中的所有方法都是相同的,StringBuffer在StringBuilder的方法之上添加了synchronized修饰,保证线程安全。
○StringBuilder比StringBuffer拥有更好的性能。
○如果一个String类型的字符串,在编译时就可以确定是一个字符串常量,则编译完成之后,字符串会自动拼接成一个常量。此时String的速度比StringBuffer和StringBuilder的性能好的多。
•String不变性的理解
○String 类是被final进行修饰的,不能被继承。
○ 在用+号链接字符串的时候会创建新的字符串。
○String s = new String("Hello world"); 可能创建两个对象也可能创建一个对象。如果静态区中有“Hello world”字符串常量对象的话,则仅仅在堆中创建一个对象。如果静态区中没有“Hello world”对象,则堆上和静态区中都需要创建对象。
○在 java 中, 通过使用 "+" 符号来串联字符串的时候, 实际上底层会转成通过 StringBuilder 实例的 append() 方法来实现。
12. String有重写Object的hashcode和toString吗?如果重写equals不重写hashcode会出现什么问题?
•String有重写Object的hashcode和toString吗?
○String重写了Object类的hashcode和toString方法。
•当equals方法被重写时,通常有必要重写hashCode方法,以维护hashCode方法的常规协定,该协定声明相对等的两个对象必须有相同的hashCode
○object1.euqal(object2)时为true, object1.hashCode() == object2.hashCode() 为true
○object1.hashCode() == object2.hashCode() 为false时,object1.euqal(object2)必定为false
○object1.hashCode() == object2.hashCode() 为true时,但object1.euqal(object2)不一定定为true
•重写equals不重写hashcode会出现什么问题
○ 在存储散列集合时(如Set类),如果原对象.equals(新对象),但没有对hashCode重写,即两个对象拥有不同的hashCode,则在集合中将会存储两个值相同的对象,从而导致混淆。因此在重写equals方法时,必须重写hashCode方法。
13. Java序列化,如何实现序列化和反序列化,常见的序列化协议有哪些
•Java序列化定义
○ 将那些实现了Serializable接口的对象转换成一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象,序列化可以弥补不同操作系统之间的差异。
•Java序列化的作用
○ Java远程方法调用(RMI)
○ 对JavaBeans进行序列化
•如何实现序列化和反序列化
○ 实现序列化方法
• 实现Serializable接口
•该接口只是一个可序列化的标志,并没有包含实际的属性和方法。
•如果不在改方法中添加readObject()和writeObject()方法,则采取默认的序列化机制。如果添加了这两个方法之后还想利用Java默认的序列化机制,则在这两个方法中分别调用defaultReadObject()和defaultWriteObject()两个方法。
•为了保证安全性,可以使用transient关键字进行修饰不必序列化的属性。因为在反序列化时,private修饰的属性也能发查看到。
•实现ExternalSerializable方法
•自己对要序列化的内容进行控制,控制那些属性能被序列化,那些不能被序列化。
○ 反序列化
•实现Serializable接口的对象在反序列化时不需要调用对象所在类的构造方法,完全基于字节。
•实现externalSerializable接口的方法在反序列化时会调用构造方法。
○ 注意事项
•被static修饰的属性不会被序列化
•对象的类名、属性都会被序列化,方法不会被序列化
•要保证序列化对象所在类的属性也是可以被序列化的
•当通过网络、文件进行序列化时,必须按照写入的顺序读取对象。
•反序列化时必须有序列化对象时的class文件
•最好显示的声明serializableID,因为在不同的JVM之间,默认生成serializableID 可能不同, 会造成反序列化失败。
•常见的序列化协议有哪些
○ COM主要用于Windows平台,并没有真正实现跨平台,另外COM的序列化的原理利用了编译器中虚表,使得其学习成本巨大。
○ CORBA是早期比较好的实现了跨平台,跨语言的序列化协议。COBRA的主要问题是参与方过多带来的版本过多,版本
之间兼容性较差,以及使用复杂晦涩。
○ XML&SOAP
•XML是一种常用的序列化和反序列化协议,具有跨机器,跨语言等优点。
•SOAP(Simple Object Access protocol) 是一种被广泛应用的,基于XML为序列化和反序 列化协议的结构化消息传递协议。SOAP具有安全、可扩展、跨语言、跨平台并支持多种传输层协议。
○JSON(Javascript Object Notation)
•这种Associative array格式非常符合工程师对对象的理解。
•它保持了XML的人眼可读(Human-readable)的优点。
•相对于XML而言,序列化后的数据更加简洁。
•它具备Javascript的先天性支持,所以被广泛应用于Web browser的应用常景中,是Ajax的事 实标准协议。
•与XML相比,其协议比较简单,解析速度比较快。
•松散的Associative array使得其具有良好的可扩展性和兼容性。
○ Thrift是Facebook开源提供的一个高性能,轻量级RPC服务框架,其产生正是为了满足当前大数据量、分布式、跨语言、跨平台数据通讯的需求。Thrift在空间开销和解析性能上有了比较大的提升,对于对性能要求比较高的分布式系统,它是一个优秀的RPC解决方案;但是由于Thrift的序列化被嵌入到Thrift框架里面,Thrift框架本身并没有透出序列化和反序列化接口,这导致其很难和其他传输层协议共同使用
○ Protobuf具备了优秀的序列化协议的所需的众多典型特征
•标准的IDL和IDL编译器,这使得其对工程师非常友好。
•序列化数据非常简洁,紧凑,与XML相比,其序列化之后的数据量约为1/3到1/10。
•解析速度非常快,比对应的XML快约20-100倍。
•提供了非常友好的动态库,使用非常简介,反序列化只需要一行代码。由于其解析性能高, 序列化后数据量相对少,非常适合应用层对象的持久化场景
○Avro的产生解决了JSON的冗长和没有IDL的问题,Avro属于Apache Hadoop的一个子项目。 Avro提供两种序列化格式:JSON格式或者Binary格式。Binary格式在空间开销和解析性能方面可以和Protobuf媲美,JSON格式方便测试阶段的调试。适合于高性能的序列化服务。
•几种协议的对比
○XML序列化(Xstream)无论在性能和简洁性上比较差;
○ Thrift与Protobuf相比在时空开销方面都有一定的劣势;
○ Protobuf和Avro在两方面表现都非常优越。
14. Java实现多线程的方式及三种方式的区别
•实现多线程的方式
○ 继承Thread类,重写run函数。
○ 实现Runnable接口
○ 实现Callable接口
•三种方式的区别
○ 实现Runnable接口可以避免Java单继承特性而带来的局限;增强程序的健壮性,代码能够 被多个线程共享,代码与数据是独立的;适合多个相同程序代码的线程区处理同一资源的 情况。
○ 继承Thread类和实现Runnable方法启动线程都是使用start方法,然后JVM虚拟机将此线程 放到就绪队列中,如果有处理机可用,则执行run方法。
○ 实现Callable接口要实现call方法,并且线程执行完毕后会有返回值。其他的两种都是重写 run方法,没有返回值。
15. 线程安全
•定义
○ 某个类的行为与其规范一致。
○不管多个线程是怎样的执行顺序和优先级,或是wait,sleep,join等控制方式,,如果一个类在多 线程访问下运转一切正常,并且访问类不需要进行额外的同步处理或者协调,那么我们就 认为它是线程安全的。
•如何保证线程安全?
○ 对变量使用volitate
○对程序段进行加锁(synchronized,lock)
•注意
○ 非线程安全的集合在多线程环境下可以使用,但并不能作为多个线程共享的属性,可以作为 某个线程独享的属性。
○例如Vector是线程安全的,ArrayList不是线程安全的。如果每一个线程中new一个ArrayList, 而这个ArrayList只是在这一个线程中使用,肯定没问题。
16. 多线程如何进行信息交互
Object中的方法,wait(), notify(),notifyAll();
17. 多线程共用一个数据变量需要注意什么?
•当我们在线程对象(Runnable)中定义了全局变量,run方法会修改该变量时,如果有多个 线程同时使用该线程对象,那么就会造成全局变量的值被同时修改,造成错误.
•ThreadLocal是JDK引入的一种机制,它用于解决线程间共享变量,使用ThreadLocal声明的 变量,即使在线程中属于全局变量,针对每个线程来讲,这个变量也是独立的。
•volatile变量每次被线程访问时,都强迫线程从主内存中重读该变量的最新值,而当该变量发 生修改变化时,也会强迫线程将最新的值刷新回主内存中。这样一来,不同的线程都能及 时的看到该变量的最新值。
18. 什么是线程池?如果让你设计一个动态大小的线程池,如何设计,应该有哪些方法?
•什么是线程池
○线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。
•设计一个动大小的线程池,如何设计,应该有哪些方法
○ 一个线程池包括以下四个基本组成部分:
•线程管理器(ThreadPool):用于创建并管理线程池,包括创建线程,销毁线程池,添加 新任务;
•工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行 任务;
•任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规 定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
•任务队列(TaskQueue):用于存放没有处理的任务。提供一种缓冲机制;
○ 所包含的方法
•private ThreadPool() 创建线程池
•public static ThreadPool getThreadPool() 获得一个默认线程个数的线程池
•public void execute(Runnable task) 执行任务,其实只是把任务加入任务队列,什么时候 执行有线程池管理器决定
•public void execute(Runnable[] task) 批量执行任务,其实只是把任务加入任务队列,什 么时候执行有线程池管理器决定
•public void destroy() 销毁线程池,该方法保证在所有任务都完成的情况下才销毁所有线 程,否则等待任务完成才销毁
•public int getWorkThreadNumber() 返回工作线程的个数
•public int getFinishedTasknumber() 返回已完成任务的个数,这里的已完成是只出了任务 队列的任务个数,可能该任务并没有实际执行完成
•public void addThread() 在保证线程池中所有线程正在执行,并且要执行线程的个数大 于某一值时。增加线程池中线程的个数
•public void reduceThread() 在保证线程池中有很大一部分线程处于空闲状态,并且空闲 状态的线程在小于某一值时,减少线程池中线程的个数
19. Java是否有内存泄露和内存溢出
•静态集合类,使用Set、Vector、HashMap等集合类的时候需要特别注意。当这些类被定义成静态的时候,由于他们的生命周期跟应用程序一样长,这时候就有可能发生内存泄漏。
class StaticTest
{
private static Vector v = new Vector(10);
public void init()
{
for (int i = 1; i < 100; i++)
{
Object object = new Object();
v.add(object);
object = null;
}
}
}
在上面的代码中,循环申请了Object对象,并添加到Vector中,然后设置为null,可是这些对象呗vector引用着,因此不能被GC回收,因此造成内存泄漏。因此要释放这些对象,还需要将它们从vector删除,最简单的方法就是将vector设置为null
•监听器: 在Java编程中,我们都需要和监听器打交道,通常一个应用中会用到很多监听器,我们会调用一个控件,诸如addXXXListener()等方法来增加监听器,但往往在释放的时候却没有去删除这些监听器,从而增加了内存泄漏的机会。
•
物理连接:一些物理连接,比如数据库连接和网络连接,除非其显式的关闭了连接,否则是不会自动被GC 回收的。Java 数据库连接一般用DataSource.getConnection()来创建,当不再使用时必须用Close()方法来释放,因为这些连接是独立于JVM的。对于Resultset 和Statement 对象可以不进行显式回收,但Connection 一定要显式回收,因为Connection 在任何时候都无法自动回收,而Connection一旦回收,Resultset 和Statement 对象就会立即为NULL。但是如果使用连接池,情况就不一样了,除了要显式地关闭连接,还必须显式地关闭Resultset Statement 对象(关闭其中一个,另外一个也会关闭),否则就会造成大量的Statement 对象无法释放,从而引起内存泄漏。。一般情况下,在try代码块里创建连接,在finally里释放连接,就能够避免此类内存泄漏。
•内部类和外部模块等的引用:内部类的引用是比较容易遗忘的一种,而且一旦没释放可能导致一系列的后继类对象没有释
放。在调用外部模块的时候,也应该注意防止内存泄漏,如果模块A调用了外部模块B的一个方法,如:
public void register(Object o)
这个方法有可能就使得A模块持有传入对象的引用,这时候需要查看B模块是否提供了出去引用的方法,这种情况容易忽略,而且发生内存泄漏的话,还比较难察觉。
•单例模式:因为单利对象初始化后将在JVM的整个生命周期内存在,如果它持有一个外部对象的(生命周期比较短)引用,那么这个外部对象就不能被回收,从而导致内存泄漏。如果这个外部对象还持有其他对象的引用,那么内存泄漏更严重。
20. concurrent包下面,都用过什么?
•concurrent下面的包
○ Executor 用来创建线程池,在实现Callable接口时,添加线程。
○ FeatureTask 此 FutureTask 的 get 方法所返回的结果类型。
○ TimeUnit
○Semaphore
○LinkedBlockingQueue
•所用过的类
○ Executor
21. volatile 关键字的如何保证内存可见性
•volatile 关键字的作用
○ 保证内存的可见性
○ 防止指令重排
○ 注意:volatile 并不保证原子性
•内存可见性
○ volatile保证可见性的原理是在每次访问变量时都会进行一次刷新,因此每次访问都是主内 存中最新的版本。所以volatile关键字的作用之一就是保证变量修改的实时可见性。
•当且仅当满足以下所有条件时,才应该使用volatile变量
○ 对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。
○ 该变量没有包含在具有其他变量的不变式中。
•volatile使用建议
○ 在两个或者更多的线程需要访问的成员变量上使用volatile。当要访问的变量已在 synchronized代码块中,或者为常量时,没必要使用volatile。
○ 由于使用volatile屏蔽掉了JVM中必要的代码优化,所以在效率上比较低,因此一定在必要 时才使用此关键字。
•volatile和synchronized区别
○ volatile不会进行加锁操作:
volatile变量是一种稍弱的同步机制在访问volatile变量时不会执行加锁操作,因此也就不 会使执行线程阻塞,因此volatile变量是一种比synchronized关键字更轻量级的同步机制。
○ volatile 变量作用类似于同步变量读写操作:
从内存可见性的角度看,写入volatile变量相当于退出同步代码块,而读取volatile变量相 当于进入同步代码块。
○ volatile 不如 synchronized安全:
在代码中如果过度依赖volatile变量来控制状态的可见性,通常会比使用锁的代码更脆 弱,也更难以理解。仅当volatile变量能简化代码的实现以及对同步策略的验证时,才 应该使用它。一般来说,用同步机制会更安全些。
○ volatile 无法同时保证内存可见性和原则性:
加锁机制(即同步机制)既可以确保可见性又可以确保原子性,而volatile变量只能确保 可见性,原因是声明为volatile的简单变量如果当前值与该变量以前的值相关,那么 volatile关键字不起作用,也就是说如下的表达式都不是原子操作:“count++”、“count = count+1”。
22. sleep和wait分别是那个类的方法,有什么区别
•sleep和wait
○sleep是Thread类的方法
○wait是Object类的方法
•有什么区别
○sleep()方法(休眠)是线程类(Thread)的静态方法,调用此方法会让当前线程暂停执行指 定的时间,将执行机会(CPU)让给其他线程,但是对象的锁依然保持,因此休眠时间结 束后会自动恢复(线程回到就绪状态)。
○wait()是Object类的方法,调用对象的wait()方法导致当前线程放弃对象的锁(线程暂停执 行),进入对象的等待池(wait pool),只有调用对象的notify()方法(或notifyAll()方法) 时才能唤醒等待池中的线程进入等锁池(lockpool),如果线程重新获得对象的锁就可以 进入就绪状态。
23. synchronized与lock的区别,使用场景。看过synchronized的源码没?
•synchronized与lock的区别
○(用法)synchronized(隐式锁):在需要同步的对象中加入此控制,synchronized可以加在 方法上,也可以加在特定代码块中,括号中表示需要锁的对象。
○(用法)lock(显示锁):需要显示指定起始位置和终止位置。一般使用ReentrantLock类做 为锁,多个线程中必须要使用一个ReentrantLock类做为对 象才能保证锁的生效。且在加锁 和解锁处需要通过lock()和unlock()显示指出。所以一般会在finally块中写unlock()以防死锁。
○(性能)synchronized是托管给JVM执行的,而lock是java写的控制锁的代码。在Java1.5 中,synchronize是性能低效的。因为 这是一个重量级操作,需要调用操作接口,导 致有可能加锁消耗的系统时间比加锁以外的操作还多。相比之下使用Java提供的Lock对 象,性能更高一些。但 是到了Java1.6,发生了变化。synchronize在语义上很清晰,可以 进行很多优化,有适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致 在Java1.6上 synchronize的性能并不比Lock差。
○(机制)synchronized原始采用的是CPU悲观锁机制,即线程获得的是独占锁。独占锁意味 着其 他线程只能依靠阻塞来等待线程释放锁。Lock用的是乐观锁方式。所谓乐观锁就是, 每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功 为止。乐观锁实现的机制就 是CAS操作(Compare and Swap)。
24. synchronized底层如何实现的?用在代码块和方法上有什么区别?
•synchronized底层如何实现的
•用在代码块和方法上有什么区别?
○synchronized用在代码块锁的是调用该方法的对象(this),也可以选择锁住任何一个对象。
○synchronized用在方法上锁的是调用该方法的对象,
○synchronized用在代码块可以减小锁的粒度,从而提高并发性能。
○ 无论用在代码块上还是用在方法上,都是获取对象的锁;每一个对象只有一个锁与之相关联;实现同步需要很大的系统开销作为代价,甚至可能造成死锁,所以尽量避免无谓的同步控制。
•synchronized与static synchronized的区别
○synchronized是对类的当前实例进行加锁,防止其他线程同时访问该类的该实例的所有synchronized块,同一个类的两个不同实例就没有这种约束了。
○那么static synchronized恰好就是要控制类的所有实例的访问了,static synchronized是限制线程同时访问jvm中该类的所有实例同时访问对应的代码快。
25. 常见异常分为那两种(Exception,Error),常见异常的基类以及常见的异常
•Throwable是java语言中所有错误和异常的超类(万物即可抛)。它有两个子类:Error、Exception。
•异常种类
○Error:Error为错误,是程序无法处理的,如OutOfMemoryError、ThreadDeath等,出现这种情况你唯一能做的就是听之任之,交由JVM来处理,不过JVM在大多数情况下会选择终止线程。
○Exception:Exception是程序可以处理的异常。它又分为两种CheckedException(受捡异常),一种是UncheckedException(不受检异常)。
•CheckException发生在编译阶段,必须要使用try…catch(或者throws)否则编译不通过。
•UncheckedException发生在运行期,具有不确定性,主要是由于程序的逻辑问题所引起的, 难以排查,我们一般都需要纵观全局才能够发现这类的异常错误,所以在程序设计中我 们需要认真考虑,好好写代码,尽量处理异常,即使产生了异常,也能尽量保证程序朝着 有利方向发展。
•常见异常的基类
○IOException
○RuntimeException