Basic
一个".java"源文件中是否可以包括多个类(不是内部类)?有什么限制?
可以有多个类,但只能有一个public的类,并且public的类名必须与文件名相一致。
说说&和&&的区别
- 二者都可以用作逻辑运算符,但作用不同;&还可以用作位运算符。
- &和&&都可以表示逻辑与(and),当运算符两边的表达式的结果都为true时,整个运算结果才为true,否则,只要有一方为false,则结果为false。
- &&具有短路的功能,即如果第一个表达式为false,则不再计算第二个表达式,例如,对于if(str != null && !str.equals(“”))表达式,当str为null时,后面的表达式不会执行,所以不会出现NullPointerException。如果将&&改为&,则会抛出NullPointerException异常。
- &还可以用作位运算符,当&操作符两边的表达式不是boolean类型时,&表示按位与操作,我们通常使用0x0f来与一个整数进行&运算,来获取该整数的最低4个bit位,例如,0x31 & 0x0f的结果为0x01。
备注:这道题先说两者的共同点,再说出&&和&的特殊之处,并列举一些经典的例子来表明自己理解透彻深入、实际经验丰富。
以下两段代码有问题吗?为什么?
short s1 = 1;
s1 = s1 + 1;
short s1 = 1;
s1 += 1;
s1+1时,short类型的s1被提升为空间更大的int类型;而后再赋值给s1时,由于向下转型需要类型强转,因此赋值操作编译器会报错。
而+=相当于s1 = (short)(s1 + 1);其中有隐含的强制类型转换。
使用final关键字修饰一个变量时,是引用不能变,还是引用的对象不能变?
1)使用final修饰变量,是指引用不能变,而引用变量
final StringBuffer sb = new StringBuffer("hello");
sb = new StringBuffer("world"); // 编译器报错
sb.append("haha"); // 正确
2)有人在定义方法时,将参数定义成final类型,以阻止方法内部修改传进来的参数对象
public void method(final StringBuffer sb) {
...
}
实际上,这是办不到的,方法内部可以使用这样的方式来修改参数对象
sb.append("haha");
"=="操作符和equals方法有什么区别?
1)“==”用于比较两个变量的值是否相等。即比较两个变量所对应的内存中存储的数值是否相同
如果一个变量指向的数据是对象类型的,如
String str = new String("hello");
对象本身 new String("hello");存在于堆内存中,而String类型的变量str存在于栈内存中,其中存储的数值就是对象占用堆内存的首地址。对于两个指向对象类型的变量,如果要比较它们是否指向同一对象,就要看各自内存中对应的数值是否相等,这就需要使用“==”操作符进行比较。
2)equals方法用于比较的两个对象是独立的,是比较内容是否相同,如
String a = new String("hello");
String b = new String("hello");
两条语句创建了两个String类型的对象,且有不同的变量指向它们,两个变量中存储的堆内存地址是不同的,因此表达式 (a==b) 返回false,而 (a.equals(b)) 则返回true。
3)一个类默认继承Object类的equals()方法,其实现是使用操作符”==“,比较两个变量指向的对象是否是同一对象。
boolean equals(Object o) {
return this == o;
}
Integer 和 int 的区别
1) int 是基本数据类型,默认值是0
2)Integer 是int的封装类,提供了对int值的一些操作方法,其默认值是null
3)int一般用于数值计算,而Integer用在需要使用对象的地方,如 Map 的 Key 和 Value,List 和 Set 的 element 若要要保存数值信息就需要封装成Integer对象使用。
代码
Integer a = new Integer(3);
Integer b = 3;
int c = 3;
System.out.println(a == b);
System.out.println(a == c);
第二行代码是否正确?为什么? 写出输出结果,并解释原因
1)正确,将3自动装箱成Integer类型,事实上执行了 Integer b = Integer.valueOf(3);
2)false。此处是比较两个Integer类型对象的内存地址,而两个引用没有引用同一对象
3)true。a自动拆箱成int类型再和c比较,因此这里是比较两个int数值
什么叫自动拆箱?举例说明
1)自动拆箱,就是将对象中的基本数据从对象中自动取出
2)代码
Integer i = 10; // 装箱
int t = i; // 拆箱
// int t = i.intValue();
类变量和实例变量的区别
1)语法:类变量就是静态变量,由 static 关键字修饰,而实例变量则不没有
2)程序运行时:
实例变量是某个对象的属性,必须创建了对象实例后,其属性才被分配内存空间,才能被使用
类变量属于类,而不属于某个对象实例。只要程序加载了类的字节码,即使没有创建任何对象实例,类变量也被分配了内存空间,可以直接使用。
3)示例
public class VariantTest {
// 类变量
public static int staticVar = 0;
// 实例变量
public int instanceVar = 0;
public VariantTest() {
staticVar ++;
instanceVar ++;
System.out.println(“staticVar=” + staticVar + ”,instanceVar=” + instanceVar);
}
}
例中,无论创建多少个类实例对象,永远只有一个 staticVar 变量;而每创建一个类对象,就会创建一个 instanceVar 变量。
为什么静态方法中不能调用一个非静态方法?
1)非静态方法要与对象关联在一起,只有对象被实例化后,才能在对象上调用非实例方法
2)静态方法不依赖于类实例,它在类被加载后就可以调用,这时还没有创建任何对象实例,因此也无法调用对象的防非实例方法
ClassLorder如何加载class?
1)JVM 里有多个类加载,每个类加载可以负责加载特定位置的类,例如,bootstrap类加载负责加载jre/lib/rt.jar中的类, 我们平时用的jdk中的类都位于rt.jar中。extclassloader负责加载jar/lib/ext/*.jar中的类,appclassloader负责classpath指定的目录或jar中的类
2)所有的类加载器的父类都是ClassLoader
final 关键字有哪些用法
1)修饰变量,表示变量被一次赋值后不可改变(常量)
2)修饰方法,表示方法不能被重写
3)修饰类,表示该类不能被继承,是强不可变类,如String类
String
代码
String str = "Hello";
str += " world!";
这两行代码执行后,原始的String对象中的内容变了没有?
1)没有改变
2)String类被设计成不可变类(immutable),就是其所有对象都是不可改变的。第一行代码中str 指向了String对象A,然后对str进行了+操作,这时String对象A仍然存在,而str则指向了另一个字符串对象B,B的内容是“Hello world!”。因此,String的操作是改变赋值地址,而不是改变值操作。
简述字符串常量池的设计思想和用处
1)字符串常量池(String intern pool)是java堆内存中的一块特殊区域,类似Java系统级别提供的缓存。
2)当创建一个String对象时,假如此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象。
3)它的实现可以在运行时节约很多堆内存空间,因为不同的字符串变量都指向池中的同一个字符串。
4)字符串池的实现,也要求字符串是不可变的。如果字符串的变量改变了它的值,那么其它指向这个值的变量的值也会一起改变。
5)直接使用双引号声明出来的String对象会直接存储在常量池中。如果不是用双引号声明的String对象,可以使用String提供的intern方法。这样如果常量池中存在当前字符串, 就会直接返回当前字符串. 如果常量池中没有此字符串, 会将此字符串放入常量池中后, 再返回。
6)jdk7中将静态常量池移到堆里面,此时若先有 new String 再调用 intern(),由于此时堆中已有该对象,将会直接返回该对象。
不可变类的设计目的是怎样的?不可变对象有什么好处?
String s = "ABC";
s.toLowerCase();
s.toLowerCase()并没有改变“ABC“的值,而是创建了一个新的String类“abc”,然后将新的实例的指向变量s。
1)不可变对象,顾名思义,就是创建后不可改变的对象
2)String类不可变性的好处
设计:只有字符串是不可变的,字符串池才有可能实现
效率:如果一个对象是不可变的,那么需要拷贝这个对象的内容时,就不用复制它的本身而只是复制它的地址。复制地址(通常一个指针大小)只需要很小的内存。这对于同时引用这个“ABC”的其他变量也不会造成影响。
因为字符串是不可变的,所以在它创建的时候哈希码就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其他的键对象。也因为此HashMap中的键往往都使用字符串。
安全:不可变对象对于多线程是安全的。并发环境下,一个可变对象的值很可能被其他线程改变,这会造成不可预期的结果,但使用不可变对象就可以避免这种情况。
如果字符串是可变的,那会引起严重的安全问题。比如数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接,或者在socket编程中,主机名和端口都是以字符串的形式传入。如果字符串的值可以改变,那黑客就可以钻到空子,改变字符串指向的对象的值,造成安全漏洞。
代码
public class Demo {
private String str;
Demo () {
str = new String("hello"); // 方式一
str = "hello"; // 方式二
}
}
字符串str两种初始化的方式有什么不同?
1)方式一:Demo类每次调用构造器时,总会创建一个字符串实例,内存开销大
2)方式二:String类型对象是不可变的,对于内容相同的字符串,系统只需要创建一个对象就可以。即使多次调用构造器,str都指向同一个字符串对象。
String s = new String("hello"); 创建了几个对象?为什么?
1)一个或两个。
2)创建 new String("hello") 前,会去常量区找 “hello”,如果没有找到会执行 new String("hello"),在堆内存中创建一个对象,然后再在常量区中创建一个 “hello”
下面这条语句一共创建了多少个对象:String s="a"+"b"+"c"+"d";
1)对于如下代码: Strings1 = "a"; Strings2 = s1 + "b"; Strings3 = "a" + "b"; System.out.println(s2== "ab"); System.out.println(s3== "ab");
第一条语句打印的结果为false,第二条语句打印的结果为true,这说明javac编译可以对字符串常量直接相加的表达式进行优化,不必要等到运行期去进行加法运算处理,而是在编译时去掉其中的加号,直接将其编译成一个这些常量相连的结果。
2)题目中的第一行代码被编译器在编译时优化后,相当于直接定义了一个”abcd”的字符串,所以,上面的代码应该只创建了一个String对象。写如下两行代码, String s = "a" +"b" + "c" + "d"; System.out.println(s =="abcd"); // 最终打印的结果应该为true。
代码
String s1 = “abc”;
String s2 = “a”;
String s3 = “bc”;
String s4 = s2 + s3;
System.out.println(s1 == s4);
输出结果是什么?为什么?
1)false
2)Java中字符串的相加其内部是使用StringBuilder类的append()方法和toString()方法来实现的. 而StringBuilder类toString()方法返回的字符串是通过构造函数创建的.
代码
String str1 = "a";
String str2 = "bc";
String str3 = "a" + "bc";
String str4 = str1 + str2;
System.out.println(str3 == str4);
str4 = (str1+str2).intern();
System.out.println(str3 == str4);
输出结果是什么?为什么?
字符串对象的创建方式有两种 如下:
String s1 = new String(""); // 第一种
String s2 = ""; // 第二种
第一种始终不会入池的. 第二种要看情况而定(等号右边如果是常量则入池,非常量则不入池)
例:
String s3 = "a" + "b"; // "a"是常量,"b"是常量,常量+常量=常量,所以会入池.
String s4 = s1 + "b"; // s1是变量,"b"是常量,变量+常量!=常量,所以不会入池.
一旦入池的话,就会先查找池中有无此对象.如果有此对象,则让对象引用指向此对象;如果无此对象,则先创建此对象,再让对象引用指向此对象.
例: String s5 = "abc"; // 先在池中查找有无"abc"对象,如果有,则让s5指向此对象;如果池中无"abc"对象,则在池中创建一个"abc"对象,然后让s5指向该对象.
补充一下: 看了字节码后,发现 String str ="a"+"b"; 完全等同于 String str="ab";
代码
String s = new String("a");
s.intern();
String s2 = "a";
System.out.println(s == s2);
String s3 = new String("a") + new String("a");
s3.intern();
String s4 = "aa";
System.out.println(s3 == s4);
输出结果是什么?为什么?
1)false true
2)在第一段代码中,先看 s3和s4字符串。String s3 = new String("1") + new String("1");,这句代码中现在生成了2最终个对象,是字符串常量池中的“1” 和 JAVA Heap 中的 s3引用指向的对象。中间还有2个匿名的new String("1")我们不去讨论它们。此时s3引用对象内容是"11",但此时常量池中是没有 “11”对象的。
接下来s3.intern();这一句代码,是将 s3中的“11”字符串放入 String 常量池中,因为此时常量池中不存在“11”字符串,因此常规做法是跟 jdk6 图中表示的那样,在常量池中生成一个 "11" 的对象,关键点是 jdk7 中常量池不在 Perm 区域了,这块做了调整。常量池中不需要再存储一份对象了,可以直接存储堆中的引用。这份引用指向 s3 引用的对象。 也就是说引用地址是相同的。 最后String s4 = "11"; 这句代码中"11"是显示声明的,因此会直接去常量池中创建,创建的时候发现已经有这个对象了,此时也就是指向 s3 引用对象的一个引用。所以 s4 引用就指向和 s3 一样了。因此最后的比较 s3 == s4 是 true。
再看 s 和 s2 对象。 String s = new String("1"); 第一句代码,生成了2个对象。常量池中的“1” 和 JAVA Heap 中的字符串对象。s.intern(); 这一句是 s 对象去常量池中寻找后发现 “1” 已经在常量池里了。
接下来String s2 = "1"; 这句代码是生成一个 s2的引用指向常量池中的“1”对象。 结果就是 s 和 s2 的引用地址明显不同。
3)jdk7 版本对 intern 操作和常量池都做了一定的修改。主要包括两点:
将String常量池 从 Perm 区移动到了 Java Heap区
String#intern 方法时,如果存在堆中的对象,会直接保存对象的引用,而不会重新创建对象。
说一下String#intern() 的大致实现
1)String#intern() 使用jni调用C++实现的 StringTable 的 intern(),而StringTable的intern方法跟Java中的HashMap的实现是差不多的,只是不能自动扩容。
2)String的 String Pool 是一个固定大小的Hashtable,默认值大小长度是1009。因此要注意,如果放进String pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后的直接影响就是当调用String#intern()时性能会大幅下降,因为要一个一个找。
3)在 jdk6中StringTable是固定的,就是1009的长度。而jdk7中,StringTable的长度可以通过一个参数指定:-XX:StringTableSize=9999
代码
String str1 = new String("a");
String str2 = new String("a");
System.out.println(str1.hashCode() == str2.hashCode);
打印结果是什么?为什么?
1)true,即 str1 和 str2 的哈希码是一样的
2)Object的hashCode()方法的默认实现,
String 和 StringBuffer 的区别
1)两者都可以用来存放和操作包含多个字符的字符数据。但String类表示内容不可改变的字符类,而StringBuffer则内容可改变。
2)如果需要将一个字符串资源改变10次,使用String操作则会创建11个String对象实例,而使用后者则只创建了一个StringBuffer类对象,因此,涉及到对可变字符的操作,使用String类耗费内存,效率低下,而使用StringBuffer则效率较高
3)StringBuffer类没有覆盖 equals 和 hashCode 方法,因此将StringBuffer对象存进Java集合类中时会出问题
StringBuffer 和 StringBuider 的区别
1)两个都表示内容可修改的字符串
2)区别在于线程安全性。StringBuffer是线程安全的,而StringBuilder是线程不安全的,它的所有方面都没有被synchronized修饰,因此它的效率也比StringBuffer要高。
Container
说一下Java容器类的一般特点
1)除了Map,所有容器类都继承了Collection接口,在Collection接口中定义了容器类的一般特点
2)元素的存取操作,有 add, addAll, remove, removeAll, removeIf, clear
3)状态判断,有 isEmpty, size, contains, containsAll
4)toArray(),获取一个包含集合内所有元素的数组
说一下数组和集合的区别
1)数组是大小固定的,而集合可以存储和操作数目不固定的一组数据。
2)数组存放的数据可以是基本类型或引用类型,而集合只能存放引用类型的数据。
3)由于几乎所有集合类型都是基于数组实现的,其功能比数组多,但速度没有数组快。
4)数组是一种可读写数据结构,没办法创建一个只读数组,但集合提供了readOnly方法,以只读方式使用集合。
Collection 和 Collections的区别。
1)Collection是集合类的上级接口,继承它的接口主要有Set,List,map等.
2)Collections是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。
为什么集合类不直接实现 Iterator 接口而是实现 Iterable 接口?
1)因为Iterator接口的核心方法next()或者hasNext() 是依赖于迭代器的当前迭代位置的。
2)如果Collection直接实现Iterator接口,势必导致集合对象中包含当前迭代位置的数据(指针)。 当集合在不同方法间被传递时,由于当前迭代位置不可预置,那么next()方法的结果会变成不可预知。
3)除非再为Iterator接口添加一个reset()方法,用来重置当前迭代位置。 但即时这样,Collection也只能同时存在一个当前迭代位置。 而Iterable则不然,每次调用都会返回一个从头开始计数的迭代器,多个迭代器是互不干扰的。
说一下 List 的特点
1)有序。List是有序的Collection。List是基于数组或链表的数据结构实现的,因此能够使用索引存取每个元素。
基于数组结构的Vector和ArrayList适合查询,而基于链表结构的LinkedList适合添加和删除。
2)单个元素。所有的List只能容纳单个对象,而不是Key-Value键值对。
3)Null元素。所有的List中可以有null元素。
4)相同元素。所有的List中可以有相同的元素。
说一下 Set 的特点
1)Set提供的接口和父类Collection一样,没有提供额外的功能。
2)Set不保存重复元素。新存入元素时会遍历已有元素,调用equels()方法判断是否重复。因此加入Set的元素必须定义 equels()方法以确保对象的唯一性。
Set不能保存重复元素的根本原因是,Set是基于Map实现的,存储方式是把HashMap中的key作为Set的对应存储项,而Map的key是不能有重复的。
HashSet是基于HashMap实现的,TreeSet是基于SortedMap实现的,因此SortedMap是有序的,这也是它和HashSet的根本不同。
3)Set不维护元素的次序。
说一下 Map 的特点
1)Map是一种把键对象和值对象映射的集合,它的每一个元素都包含一对键对象和值对象。
2)Map维护“键值对”的关联性,这样可以通过“键”查找“值”
3)Map没有继承Collection接口,它的操作方法有自己的特点。
说一下 Map 和 Collection 的区别
1)容器内每个元素位置存储的数据个数不同。Collection每个位置只有一个元素,Map每个位置存入键值对(key-value pair),像个小型数据库。
说一下 Vector 和 ArrayList 的区别
1)同步性。Vector的方法都是同步的(Synchronized),是线程安全的,而ArrayList的方法不是,由于线程的同步必然要影响性能,因此,ArrayList的性能比Vector好。
2)数据增长。当Vector或ArrayList中的元素超过它的初始大小时,Vector会将它的容量翻倍,而ArrayList只增加50%的大小,这样,ArrayList就有利于节约内存空间
说一下 ArrayList 和 LinkedList 的区别
1)存储结构。ArrayList是基于数组实现的,而LinkedList是基于链表。
2)操作效率。ArrayList适合随机访问get和set,因为LinkedList要移动指针;而LinkedList适合插入和删除操作,因为ArrayList要移动数据。
对于容器类Set来说,添加两个 new String("a"); 是否算存入了重复元素?为什么?
1)Set对存入元素是否重复的判断,执行 equals() 和 hashCode() 两个方法
2)对于字符串对象来说,即使不是一个对象,但只要内容一样,其散列码/hashCode是一样的,因为字符串的散列码由内容导出
HashMap 和 Hashtable 的区别。
1)它们都是Map子类,实现了Map接口
2)Hashtable是线程安全的,HashMap则不是,因此HashMap的效率高于Hashtable
3)HashMap允许将null作为键值对的键或者值,而Hashtable不允许。
List、Map、Set三个接口,存取元素时,各有什么特点?
List 以特定次序来持有元素,可有重复元素。Set 无法拥有重复元素,内部排序。Map 保存key-value值,value可多值。
Set里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还是equals()? 它们有何区别?
Set里的元素是不能重复的,那么用iterator()方法来区分重复与否。equals()是判读两个Set是否相等。
equals()和==方法决定引用值是否指向同一对象equals()在类中被覆盖,为的是当两个分离的对象的内容和类型相配的话,返回真值。
并发
什么叫同步? ***
1)以通讯为例 同步:发送一个请求,等待返回,然后再发送下一个请求 异步:发送一个请求,不等待返回,随时可以再发送下一个请求 并发:同时发送多个请求
2)多线程对共享资源的并发访问会造成数据一致性问题,而同步就是避免问题的方法
3)同步语言有同步语句和同步方法,同步方法指用synchronized修饰方法
4)同步只能针对方法和代码块,不能同步变量和类,即synchronized 关键字不能修饰变量和类
同步和锁的关系 ***
1)为了避免并发访问导致数据不一致的问题,需要将执行方法或语句由可并发访问,改为只可同步访问,如一个方法,本来可以由多条线程并发访问,改为同一时间只可由一条线程访问。在一次访问未结束前,不允许其他线程访问,其他线程只能等待。
2)锁是为了实现同步机制。也可以说,同步机制,就是锁机制。锁也被称为监视器。
如何理解并发访问中的“先后顺序”和“可见性” ***
1)
synchronized修饰方法和修饰代码块有什么区别? *****
1)同步关键字修饰方法有个问题,先看代码
class Counter {
private int count;
public synchronized void increment() {
count++;
}
public synchronized void decrement() {
count--;
}
}
public static void main() {
Counter counter = new Counter();
Thread t1 = new Thread()
}
怎样理解竞争锁和非竞争锁?有什么区别?***
1)如果一个锁自始自终只有一个线程使用,或者虽然被多个线程使用,但在任意时刻,都只有一个线程尝试获取锁。这两种锁被称为非竞争锁。
2)多个线程同时尝试获取一个锁,这种锁被称为竞争锁。
3)两种锁的性能差异巨大。JVM会对非竞争锁的资源损耗做很大优化,但对于竞争锁造成的严重损耗无能为力。
说明锁的粒度 ***
粗粒度的锁会造成时间和性能的损耗,因此锁的使用需要细化和分拆
1)同步方法会造成调用线程过长时间的持有锁。由于需要保护的仅仅是对象的共享状态,而不是代码,因此可以在方法内部使用同步代码块来同步对象操作代码,这样可以减少锁的持有时间和程序执行时间。
2)使用一个锁保护所有的状态变量,这会加大锁的请求频率,增加损耗。分拆锁和分离锁可以降低锁的访问频率。相互独立的状态变量,可以使用单独的锁进行保护,这样可以减小锁的粒度,但要小心处理,避免死锁。
什么叫死锁?它是如何产生的?*****
1)死锁是指多个进程循环等待它方占有的资源而无限期地僵持下去的局面,它造成多条线程永久阻塞
2)死锁产生的原因很简单,即两条线程相互等待对方持有的锁。可见,只要在一个任务中使用了一个以上的锁,就有发生死锁的风险。
如何解决死锁问题?****
1)
什么叫活锁?和死锁的区别是什么?
1)活锁指的是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败。
2)活锁和死锁的区别在于,处于活锁的实体是在不断的改变状态,所谓的“活”, 而处于死锁的实体表现为等待。 活锁有可能自行解开,死锁则不能。
什么是锁粗化?**
原则上,我们在编写代码的时候,总是推荐将同步块的作用范围限制的尽量小,仅仅在共享数据的实际作用域才进行同步,这样是为了使得需要同步的操作尽可能变小,如果存在锁竞争,那等待锁的线程也能尽快的拿到锁。大部分情况下,这儿原则是正确的,但是如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至锁出现在循环体内,即使没有线程竞争,频繁的进行互斥操作也会导致不必要的性能损耗。
说明锁的状态 **
1)无锁状态
2)偏向锁状态
3)轻量级锁状态
4)重量级锁状态
什么是线程安全? *****
如何实现线程安全? ****
1)互斥同步
2)非阻塞同步
3)无同步方案。可重入代码(ReetrantLock)和线程本地存储(ThreadLocal)
说明 volatile 的作用 *****
1)volatile 和 synchronized 关键字都是实现代码同步的, synchronized 修饰方法和代码块,而 volatile 修饰变量
2)volatile 变量能够实现可见性,但无法保证原子性。可见性指一个线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的。
3)volotile 不能和 final 关键字并用,因为final变量是不可修改的,也就不存在线程安全问题
线程
说明进程和线程的区别 *****
1)从操作系统层面看,进程是应用程序的一个运行活动过程,是操作系统资源管理的实体。进程是操作系统分配和调度系统内存、CPU时间片等资源的基本单位,为正在运行的应用程序提供运行环境。一个进程至少包括一个线程。每个进程都有自己独立的内存地址空间。
2)线程是进程内部执行代码的实体,它是CPU调度资源的最小单元,一个进程内部可以有多个线程并发运行。线程没有自己独立的内存资源,它只有自己的执行堆栈和局部变量,所以线程不能独立地执行,它必须依附在一个进程上。在同一个进程内多个线程之间可以共享进程的内存资源。
如何创建一个线程
可以直接调用Thread类的run()方法吗?
什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing)?
线程调度器是一个操作系统服务,它负责为Runnable状态的线程分配CPU时间。一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的实现。时间分片是指将可用的CPU时间分配给可用的Runnable线程的过程。分配CPU时间可以基于线程优先级或者线程等待的时间。线程调度并不受到Java虚拟机控制,所以由应用程序来控制它是更好的选择(也就是说不要让你的程序依赖于线程的优先级)。
说明线程的状态 *****
1)新状态
2)可运行状态
3)运行状态
4)等待/阻塞/睡眠状态
5)死亡状态
线程之间是如何通信的?
当线程间是可以共享资源时,线程间通信是协调它们的重要的手段。Object类中wait()\notify()\notifyAll()方法可以用于线程间通信关于资源的锁的状态。
说明 wait() 和 notify() 的作用和用法,并做代码演示 *****
说明 notifyAll() 的用法,并做代码演示 ****
为什么线程通信的方法wait(), notify()和notifyAll()被定义在Object类里?
Java的每个对象中都有一个锁(monitor,也可以成为监视器) 并且wait(),notify()等方法用于等待对象的锁或者通知其他线程对象的监视器可用。在Java的线程中并没有可供任何对象使用的锁和同步器。这就是为什么这些方法是Object类的一部分,这样Java的每一个类都有用于线程间通信的基本方法
为什么wait(), notify()和notifyAll()必须在同步方法或者同步块中被调用?
当一个线程需要调用对象的wait()方法的时候,这个线程必须拥有该对象的锁,接着它就会释放这个对象锁并进入等待状态直到其他线程调用这个对象上的notify()方法。同样的,当一个线程需要调用对象的notify()方法时,它会释放这个对象的锁,以便其他在等待的线程就可以得到这个对象锁。由于所有的这些方法都需要线程持有对象的锁,这样就只能通过同步来实现,所以他们只能在同步方法或者同步块中被调用。
为什么Thread类的sleep()和yield()方法是静态的?
Thread类的sleep()和yield()方法将在当前正在执行的线程上运行。所以在其他处于等待状态的线程上调用这些方法是没有意义的。这就是为什么这些方法是静态的。它们可以在当前正在执行的线程中工作,并避免程序员错误的认为可以在其他非运行线程调用这些方法。
wait() 和 sleep() 有什么区别
最大的不同是在等待时wait会释放锁,而sleep一直持有锁。Wait通常被用于线程间交互,sleep通常被用于暂停执行。
说明线程合并的含义和实现方式
说明守护线程的生命周期和使用场景
用户线程和守护线程有什么区别
Java中用到的线程调度算法是什么?
在Java中什么是线程调度?
在线程中你怎么处理不可捕捉异常?
什么是ThreadLocal?
ThreadLocal用于创建线程的本地变量,我们知道一个对象的所有线程会共享它的全局变量,所以这些变量不是线程安全的,我们可以使用同步技术。但是当我们不想使用同步的时候,我们可以选择ThreadLocal变量。
每个线程都会拥有他们自己的Thread变量,它们可以使用get()\set()方法去获取他们的默认值或者在线程内部改变他们的值。ThreadLocal实例通常是希望它们同线程状态关联起来是private static属性。在ThreadLocal例子这篇文章中你可以看到一个关于ThreadLocal的小程序。
什么是线程组,为什么Java中不推荐使用?
ThreadGroup是一个类,它的目的是提供关于线程组的信息。
ThreadGroup API比较薄弱,它并没有比Thread提供了更多的功能。它有两个主要的功能:一是获取线程组中处于活跃状态线程的列表;二是设置为线程设置未捕获异常处理器(ncaught exception handler)。但在Java 1.5中Thread类也添加了setUncaughtExceptionHandler(UncaughtExceptionHandler eh) 方法,所以ThreadGroup是已经过时的,不建议继续使用。
什么是Java线程转储(Thread Dump),如何得到它?
线程转储是一个JVM活动线程的列表,它对于分析系统瓶颈和死锁非常有用。有很多方法可以获取线程转储——使用Profiler,Kill -3命令,jstack工具等等。我更喜欢jstack工具,因为它容易使用并且是JDK自带的。由于它是一个基于终端的工具,所以我们可以编写一些脚本去定时的产生线程转储以待分析。
什么是原子操作?并发包中的原子类(atomic classes)有什么作用?
原子操作是指一个不受其他操作影响的操作任务单元。原子操作是在多线程环境下避免数据不一致必须的手段。
int++并不是一个原子操作,所以当一个线程读取它的值并加1时,另外一个线程有可能会读到之前的值,这就会引发错误。
为了解决这个问题,必须保证增加操作是原子的,在JDK1.5之前我们可以使用同步技术来做到这一点。到JDK1.5,java.util.concurrent.atomic包提供了int和long类型的装类,它们可以自动的保证对于他们的操作是原子的并且不需要使用同步。
Java Concurrency API中的Lock接口(Lock interface)是什么?对比同步它有什么优势?
Lock接口比同步方法和同步块提供了更具扩展性的锁操作。他们允许更灵活的结构,可以具有完全不同的性质,并且可以支持多个相关类的条件对象。
它的优势有:
可以使锁更公平 可以使线程在等待锁的时候响应中断 可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间 可以在不同的范围,以不同的顺序获取和释放锁
什么是Executors框架?
Executor框架同java.util.concurrent.Executor 接口在Java 5中被引入。Executor框架是一个根据一组执行策略调用,调度,执行和控制的异步任务的框架。
无限制的创建线程会引起应用程序内存溢出。所以创建一个线程池是个更好的的解决方案,因为可以限制线程的数量并且可以回收再利用这些线程。利用Executors框架可以非常方便的创建一个线程池
在Java中Executor和Executors的区别?
为什么使用Executor框架比使用应用创建和管理线程好?
什么是阻塞队列?如何使用阻塞队列来实现生产者-消费者模型?
java.util.concurrent.BlockingQueue的特性是:当队列是空的时,从队列中获取或删除元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操作会被阻塞。
阻塞队列不接受空值,当你尝试向队列中添加空值的时候,它会抛出NullPointerException。
阻塞队列的实现都是线程安全的,所有的查询方法都是原子的并且使用了内部锁或者其他形式的并发控制。
BlockingQueue 接口是java collections框架的一部分,它主要用于实现生产者-消费者问题。
什么是Callable和Future?
Java5在concurrency包中引入了java.util.concurrent.Callable 接口,它和Runnable接口很相似,但它可以返回一个对象或者抛出一个异常。
Callable接口使用泛型去定义它的返回类型。Executors类提供了一些有用的方法去在线程池中执行Callable内的任务。由于Callable任务是并行的,我们必须等待它返回的结果。java.util.concurrent.Future对象为我们解决了这个问题。在线程池提交Callable任务后返回了一个Future对象,使用它我们可以知道Callable任务的状态和得到Callable返回的执行结果。Future提供了get()方法让我们可以等待Callable结束并获取它的执行结果。
什么是FutureTask?
FutureTask是Future的一个基础实现,我们可以将它同Executors使用处理异步任务。通常我们不需要使用FutureTask类,单当我们打算重写Future接口的一些方法并保持原来基础的实现是,它就变得非常有用。我们可以仅仅继承于它并重写我们需要的方法。阅读Java FutureTask例子,学习如何使用它。
什么是并发容器的实现?
Java集合类都是快速失败的,这就意味着当集合被改变且一个线程在使用迭代器遍历集合的时候,迭代器的next()方法将抛出ConcurrentModificationException异常。
并发容器支持并发的遍历和并发的更新。
主要的类有ConcurrentHashMap, CopyOnWriteArrayList 和CopyOnWriteArraySet,阅读这篇文章了解如何避免ConcurrentModificationException。
Executors类是什么?
Executors为Executor,ExecutorService,ScheduledExecutorService,ThreadFactory和Callable类提供了一些工具方法。
Executors可以用于方便的创建线程池。