Java内存模型

https://www.cnblogs.com/dolphin0520/p/3920373.html

Java内存模型规定所有的变量都是存在主存当中(类似于前面说的物理内存),每个线程都有自己的工作内存(类似于前面的高速缓存)。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存

1.原子性

Java内存模型只保证了基本读取和赋值是原子性操作,如果要实现更大范围操作的原子性,可以通过synchronized和
Lock来实现。由于synchronized和Lock能够保证任一时刻只有一个线程执行该代码块,那么自然就不存在原子性问题了,
从而保证了原子性.

2.可见性

volatile关键字来保证可见性,当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线
程需要读取时,它会去内存中读取新值。另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保
证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可
见性。

3.有序性

在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影
响到多线程并发执行的正确性。

volatile关键字

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

1.保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见,即其它内存需要到主内存中读取共享变量,而非在本地内存中。

2.禁止进行指令重排序(JDK1.5之后,为了提供比锁更加轻量级的线程之间通信机制)。

如果volatile变量修饰符使用恰当的话,他会比synchronized的使用和执行成本更低,因为他不会引起线程==上下文的切换和调度==。

volatile关键字保证了操作的可见性,保证每次读取的是最新的值,但是不能保证对变量操作的原子性。(例子,自增操作)原来的代码,无法保证原子性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Test {
public volatile int inc = 0;

public void increase() {
inc++;
}

public static void main(String[] args) {
final Test test = new Test();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++)
test.increase();
};
}.start();
}

while(Thread.activeCount()>1) //保证前面的线程都执行完
Thread.yield();
System.out.println(test.inc);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
修改后的,采用synchronized
public class Test {
public int inc = 0;

public synchronized void increase() {
inc++;
}

public static void main(String[] args) {
final Test test = new Test();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++)
test.increase();
};
}.start();
}

while(Thread.activeCount()>1) //保证前面的线程都执行完
Thread.yield();
System.out.println(test.inc);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
采用Lock

public class Test {
public int inc = 0;
Lock lock = new ReentrantLock();

public void increase() {
lock.lock();
try {
inc++;
} finally{
lock.unlock();
}
}

public static void main(String[] args) {
final Test test = new Test();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++)
test.increase();
};
}.start();
}

while(Thread.activeCount()>1) //保证前面的线程都执行完
Thread.yield(); //线程让步,当一个线程使用了这个方法之后,它就会把自己CPU执行的时间让掉,
//让自己或者其它的线程运行,注意是让自己或者其他线程运行,并不是单纯的让给其他线程
System.out.println(test.inc);
}
}

ArrayList和LinkedList区别

List是个接口,它继承Collection接口,代表有序队列。LinkedList是双向链表

1.ArrayList基于动态数组,LinkedList基于链表的

2.对于随机访问get和set,ArrayList要优于LinkedList,因为LinkedList要移动指针

String 和StringBuffer 和StringBuilder的区别

String 和StringBuffer都能储存和操作字符串,但String提供了数值不可改变的字符串。有重写equals方法

StringBuffer提供的字符串的修改。没有重写equals方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
例子:
StringBuffer sb = new StringBuffer();
String s = new String();

for(int i = 0 ; i < 10 ; i++) {
s = s + "1";
System.out.println(s.hashCode());
/**
* 效率低,创建了10个String的对象
* 49
* 1568
* 48657
* 1508416
* 46760945
* 1449589344
* 1987596753
* 1485957248
* -1179965519
* 2075774624
*/
}
for (int i = 0; i < 10; i++) {
sb.append("2");
System.out.println(sb.hashCode());
/**
* 效率高,因为只创建了一个StringBuffer对象
* 2101440631
* 2101440631
* 2101440631
* 2101440631
* 2101440631
* 2101440631
* 2101440631
* 2101440631
* 2101440631
* 2101440631
*/
}

StringBuffer没有重写hashcode证明

StringBuilder 与 StringBuffer

StringBuilder是非线程安全的。StringBuffer是线程安全的。StringBuffer 和 Stringbuilder 上的主要操作都是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。

一个”.java”源文件中是否可以包括多个类(不是内部类)?有什么限制?

可以有多个类,但只能有一个 public 的类,并且 public 的类名必须与文件名相一致

说说&和&&的区别

&和&&都可以用作逻辑与的运算符,表示逻辑与(and),当运算符两边的表达式的结果都为true时,整个运算结果才为true,否则,只要有一方为 false,则结果为false。

&&主要用于逻辑,运算还具有短路的功能,如之前一个表达式是false,则不接下去计算下一个表达式的真值。

&常用于位运算,也可用于逻辑运算。

switch不能作用在long上

当我用long类型的值作用在switch上时,出现了以下警告,从图中可知要求的类型

其实作用在switch上的主要是整型表达式和枚举常量,整数表达式可以是 int 基本 类型或 Integer 包装类型,由于,byte,short,char 都可以隐含转换为int。然后JDK7.0的标准中,switch可以使用string类型。

short s1 = 1; s1 = s1 + 1;有什么错? short s1 = 1; s1 += 1;有什么错?

对于 short s1 = 1; s1 = s1 + 1; 由于 s1+1 运算时会自动提升表达式的类型,所以结果是 int 型,再赋值
给 short 类型 s1 时,编译器将报告需要强制转换类型的错误。

对于 short s1 = 1; s1 += 1;由于 += 是 java 语言规定的运算符,java 编译器会对它进行特殊处理,因此
可以正确编译。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class App {
public static void main(String[] args) {
short s1 = 1;
// s1 = s1+1;//报错,因为s1+1结果是int类型,等号左边是short类型,所以要强转
s1 = (short) (s1 + 1);
System.out.println("s1:" + s1);

short s2 = 1;
s2 += 1;// 因为+=在Java中进行了特殊处理,所以不会报错
System.out.println("s2:" + s2);

long l1 = 1;
l1 = l1 + 1;// 不报错,+左边是long型,右边是int型,结果是long型再赋值给long型自然不会错
System.out.println("l1:" + l1);

long l2 = 1;
l2 += 1;// OK
System.out.println("l2:" + l2);

}
}

char 型变量中能不能存贮一个中文汉字?为什么?

char 型变量是用来存储 Unicode 编码的字符的,unicode 编码字符集中包含了汉字,所以,char 型变量中
可以存储汉字啦。不过,如果某个特殊的汉字没有被包含在 unicode 编码字符集中,那么,这个 char 型变量中就不能 存储这个特殊汉字

使用 final 关键字修饰一个变量时,是引用不能变,还是引用的对象不能变?

对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象,但是被final修饰的引用变量指向的对象内容可变

举例

查看更多final的用法

静态变量和实例变量的区别

语法上:静态变量之前得加static,实例变量前不需要

运行时:实例变量是属于某个对象的属性,所以,只有创建了实例对象,实例变量才会被分配空间,实例变量才能被使用。而静态变量是属于类的,不属于实例对象,所以当类被加载字节码时,静态变量就会被分配空间,就可以被使用。

1
2
3
4
5
6
7
8
9
public class VariantTest{
public static int staticVar = 0;
public int instanceVar = 0;
public VariantTest(){
staticVar++;
instanceVar++;
System.out.println(“staticVar=” + staticVar + ”,instanceVar=” +
instanceVar);
}

无论创建多少个实例对象,永远都只分配了一个 staticVar 变量,并且每创建一个实例对 象,这个 staticVar 就会加 1;但是,每创建一个实例对象,就会分配一个 instanceVar,即可能分配多个 instanceVar,并且每个 instanceVar 的值都只自加了 1 次。

是否可以从一个 static 方法内部发出对非 static 方法的调用?

不可以。因为非 static 方法是要与对象关联在一起的。创建一个对象后,才可以在该对象上进行方法调用,
而 static 方法调用时不需要创建对象,可以直接调用。也就是说,当一个 static 方法被调用时,可能还没有创建任 何实例对象。所以,一个 static 方法内部发出对非 static 方法的调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class App {
// 静态成员
public static String string = "static成员";
// 普通成员
public String string2 = "非static成员";

// 静态方法
public static void method() {
string = "sss";
// string2 = "sss";
// method2();//编译报错,因为静态方法里面只能调用静态方法或静态成员
System.out.println("这是static方法,static方法与对象无关" + string);
}

// 普通方法
public void method2() {
string = "string1";
string2 = "string2";
method();// 非静态方法里面可以发出对static方法的调用
System.out.println("这是非static方法,此方法必须和指定的对象关联起来才起作用" + string + string2);
}

public static void main(String[] args) {
App app = new App();
app.method2();// 引用调用普通方法
method();// 引用调用静态方法,对于静态方法可以直接引用
}

}

Integer 与 int 的区别

Java 8 种原始数据类型

byte:8位,最大存储数据量是255,存放的数据范围是-128~127之间。

short:16位,最大数据存储量是65536,数据范围是-32768~32767之间。

int:32位,最大数据存储容量是2的32次方减1,数据范围是负的2的31次方到正的2的31次方减1。

long:64位,最大数据存储容量是2的64次方减1,数据范围为负的2的63次方到正的2的63次方减1。

float:32位,数据范围在3.4e-45~1.4e38,直接赋值时必须在数字后加上f或F。

double:64位,数据范围在4.9e-324~1.8e308,赋值时可以加d或D也可以不加。

boolean:只有true和false两个取值。

char:16位,存储Unicode码,用单引号赋值。

int 是 java 提供的 8 种原始数据类型之一。Java 为每个原始类型提供了封装类,Integer 是 java 为 int 提供
的封装类。int 的默认值为 0,而 Integer 的默认值为 null,即 Integer 可以区分出未赋值和值为 0 的区别,int 则无法表达出未赋值的情况,例如,要想表达出没有参加考试和考试成绩为 0 的区别,则只能使用 Integer。在 JSP 开发中,Integer 的默认为 null,所以用 el 表达式在文本框中显示时,值为空白字符串,而 int 默认的默认值为 0, 所以用 el 表达式在文本框中显示时,结果为 0,所以,int 不适合作为 web 层的表单数据的类型。

另外,Integer 提供了多个与整数相关的操作方法,例如,将一个字符串转换成整数,Integer 中还定义了表示整 数的最大值和最小值的常量。

Math.round(11.5)等於多少? Math.round(-11.5)等於多少?

1
2
3
4
5
6
7
public class App {
public static void main(String[] args) {
System.out.println(Math.round(-11.5)); //-11
System.out.println(Math.round(11.5)); //12
//Math.round() == Math.floor(x+0.5)向下取整
}
}

“==”和 equals 方法究竟有什么区别?

\==操作符专门用来比较两个变量的值是否相等,也就是用于比较变量所对应的内存中所存储的数值是否相同,要比 较两个基本类型的数据或两个引用变量是否相等,只能用\==操作符。

如果一个变量指向的数据是对象类型的,那么,这时候涉及了两块内存,对象本身占用一块内存(堆内存),变量也 占用一块内存,例如 Objet obj = new Object();变量 obj 是一个内存,new Object()是另一个内存,此时,变 量 obj 所对应的内存中存储的数值就是对象占用的那块内存的首地址。对于指向对象类型的变量,如果要比较两个变量 是否指向同一个对象,即要看这两个变量所对应的内存中的数值是否相等,这时候就需要用\==操作符进行比较。

equals 方法是用于比较两个独立对象的内容是否相同,就好比去比较两个人的长相是否相同,它比较的两个对象是 独立的。例如,对于下面的代码:

1
2
String a=new String("foo");
String b=new String("foo");

两条 new 语句创建了两个对象,然后用 a,b 这两个变量分别指向了其中一个对象,这是两个不同的对象,它们的首
地址是不同的,即 a 和 b 中存储的数值是不相同的,所以,表达式 a==b 将返回 false,而这两个对象中的内容是相同 的,所以,表达式 a.equals(b)将返回 true。

在实际开发中,我们经常要比较传递进行来的字符串内容是否等,例如,String input = …;input.equals(“quit”),许多人稍不注意就使用==进行比较了,这是错误的,随便从网上找几个项目实战的教 学视频看看,里面就有大量这样的错误。记住,字符串的比较基本上都是使用 equals 方法。

如果一个类没有自己定义 equals 方法,那么它将继承 Object 类的 equals 方法,Object 类的 equals 方法的 实现代码如下:

1
2
3
boolean equals(Object o){
return this==o;
}

这说明,如果一个类没有自己定义 equals 方法,它默认的 equals 方法(从 Object 类继承的)就是使用==操 作符,也是在比较两个变量指向的对象是否是同一对象,这时候使用 equals 和使用\==会得到同样的结果,如果比较的 是两个独立的对象则总返回 false。如果你编写的类希望能够比较该类创建的两个实例对象的内容是否相同,那么你必 须覆盖 equals 方法,由你自己写代码来决定在什么情况即可认为两个对象的内容是相同的。

Overload 和 Override 的区别。Overloaded 的方法是否可以改变返回值的类型

Overload是重载,重载包括参数类型的不同和参数数量的不同。如果两个方法的参数列表完全一样,不可以让它们的返回值不同来实现重载 Overload。java

就无法确定编程者倒底是想调用哪个方法了, 因为它无法通过返回结果类型来判断。

使用重载要注意以 下的几点:

1、在使用重载时只能通过不同的参数样式。例如,不同的参数类型,不同的参数个数,不同的参数顺序(当然,同一方 法内的几个参数类型必须不一样,例如可以是 fun(int,float),但是不能为 fun(int,int));

2、不能通过访问权限、返回类型、抛出的异常进行重载;

3、方法的异常类型和数目不会对重载造成影响;

4、对于继承来说,如果某一方法在父类中是访问权限是 priavte,那么就不能在子类对其进行重载,如果定义的话, 也只是定义了一个新方法,而不会达到重载的效果。

Override是重写,对我们来 说最熟悉的覆盖就是对接口方法的实现,在接口中一般只是对方法进行了声明,而我们在实现时,就需要实现接口声明 的所有方法

在覆盖要注意以下的几点:

1、覆盖的方法的标志必须要和被覆盖的方法的标志完全匹配,才能达到覆盖的效果;

2、覆盖的方法的返回值必须和被覆盖的方法的返回一致;

3、覆盖的方法所抛出的异常必须和被覆盖方法的所抛出的异常一致,或者是其子类;

4、被覆盖的方法不能为
private,否则在其子类中只是新定义了一个方法,并没有对其进行覆盖

构造器 Constructor 不能被继承,因此不能重写 Override,但可以被重载 Overload

抽象类,接口,普通类

参考链接1
链接2

抽象类

在Java语言中表示的是一种继承关系,一个类只能使用一次继承关系。在后续的发行版中,如果希望在抽象类中增加一个方法,只增加一个默认的合理的实现即可,抽象类的所有实现都自动提供了这个新的方法

1、抽象方法必须由子类来进行重写

2、只要包含一个抽象方法的抽象类,该方法必须要定义成抽象类,不管是否还包含有其他方法。

3、抽象类中可以包含具体的方法,当然也可以不包含抽象方法。

4、子类中的抽象方法不能与父类的抽象方法同名。

6、abstract不能与final并列修饰同一个类。

7、abstract不能与private、static、final或native并列修同一个方法。

接口:

一个类可以实现多个接口,接口中的方法在实现类中必须全部实现。所以,设计公有的接口要非常谨慎,一旦一个接口被公开且被广泛实现,对它进行修改将是不可能的。

相同点:

1.都不能被实例(抽象类的实例交由子类完成)
2.都能包含抽象方法

不同点:

1.抽象类当中可以存在非抽象的方法,可接口不能且它里面的方法只是一个声名必须用public来修饰没有具体实现的方法。

2.抽象类中的成员变量可以被不同的修饰符来修饰.可接口中的成员变量默认的都是静态常量(public static final),可以通过类命名直接访问:ImplementClass.name。

3.接口中不存在实现的方法。

4.一个Interface的所有方法访问权限自动被声明为public。确切的说只能为public,当然你可以显示的声明为protected、private,但是编译会出错!

5.实现接口的非抽象类必须要实现该接口的所方法。抽象类可以不用实现,但必须实现抽象方法。

参考

接口可以继承接口,但是要使用extends,而不是用implements如:interface b extends a{}。抽象类可以实现(implements)接口,抽象类可以继承具体类。抽象类中可以有静态的main方法。

写 clone()方法时,通常都有一行代码,是什么?

参考

注意clone() 是浅拷贝

clone 有缺省行为,super.clone();因为首先要把父类中的成员复制到位,然后才是复制自己的成员。

java中实现的多态机制是什么?

java中实现多态的机制,靠的是父类或者接口定义的引用变量可以指向子类或者具体实现类的实例对象,而程序调用的方法在运行期才动态绑定

java中的引用变量

推荐这篇博客的前半部分

abstract 的 method 是否可同时是 static,是否可同时是 synchronized?

abstract 的 method 不可以是 static 的,因为抽象的方法是要被子类实现的,而 static 与子类扯不上关系!

synchronized 应该是作用在一个具体的方法上才有意义。而且,方法上的 synchronized 同步所 使用的同步锁对象是 this,而抽象方法上无法确定 this 是什么。

内部类

参考此博客

try {}里有一个 return 语句,那么紧跟在这个 try 后的 finally {}里的 code 会不会被执行,什 么时候被执行,在 return 前还是后?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class App {
static int x = 1;
static int test() {
//x = 1;
try {
return x;
} finally {
++x;
}
}

public static void main(String[] args) {
//先返回return,在执行finally里的
System.out.println(App.test()); //1

System.out.println(App.test()); //2
}
}

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class smallT {
public static void main(String args[]) {
smallT t = new smallT();
int b = t.get();
System.out.println(b); //2
}
public int get() {
try {
return 1 ;
} finally {
return 2 ;
}
}
}

try 中的 return 语句调用的函数先于 finally 中调用的函数 执行,也就是说 return 语句先执行,finally 语句 后执行,所以,返回的结果是 2。return 并不是让函 数马上返回,而是 return 语句执行后,将把返回结 果放置进函数栈中,此时函数并不是马上返回,它要 执行 finally 语句后才真正开始返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Test {
public static void main(String[] args) {
System.out.println(new Test().test());
//func1
//func2
//2

}
int test() {
try {
return func1();
} finally {
return func2();
}
}
int func1() {
System.out.println("func1");
return 1;

}
int func2() {
System.out.println("func2");
return 2;
}
}

java运行时异常和一般异常有何区别,error

参考博客

error是JVM侦测到无法预计的错误,会导致JVM无法继续执行。这错误无法捕捉到,无法采取恢复的操作。虚拟机必须宕机。

runtime exception异常,有空指针,数组越界,类型转换等,由java虚拟机接管,不需要我们参与,出现该异常时,不catch,会出现线程或者主程序退出。程序可以死掉也可以不死掉的错误。

异常分为runtime exception和checked exception。
checked exception异常,内存溢出,线程死锁等,java编译器强制要求我们去catch异常。程序不应该死掉的错误。

名次解释

throws: 出现在方法函数头,表示出现这个异常的可能性,并不一定发生这个异常

throw: 是抛出异常

try 是将会发生异常的语句括起来,从而进行异常的处理,也可以在 try 块中抛出新的异常

catch 是如果有异常就会执行他里面的语句

finally 不论是否有异常都会进行执行的语句

如果在函数体内用throw抛出了某种异常,最好要在函数名中加throws抛异常声明,然后交给调用它的上层函数进行处理

用什么关键字修饰同步,stop()和suspend()为什么不推荐使用,java中实现线程的方法。

Synchronized关键字可以和对象的锁交互,来实现同步方法或同步块

stop()是不安全的,会解除线程获得的所有锁定。即一个线程对象上调用stop(),则会直接终止所运行的线程

suspend()容易发生死锁。使用该命令会使命令停下来,但是不会释放锁。

解决方法:而应在自己的Thread类中置入一个标志,指出线程应该活动还是挂起。若标志指出线程应该挂起,便用 wait()命其进入等待状态。若标志指出线程应当恢复,则用一个notify()重新启动线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public class App {

public static void main(String[] args) {
new Thread(){
@Override
public void run() { //调用Thread的run方法
System.out.println("run " + Thread.currentThread().getName());
}
}.start();

new Thread(new Runnable() { //调用Thread接受的runnable的run方法
@Override
public void run() {
System.out.println("Runnable " + Thread.currentThread().getName());
}
}).start();


//线程池是java5的新特性
/**
* 固定大小的线程池,不可新建线程
*/
ExecutorService fixedPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
fixedPool.execute(new Runnable() {
@Override
public void run() {
System.out.println("fixedPool " + Thread.currentThread().getName());
}
});
}


/**
* 单任务的线程池
* 当有长时间面向的连接的线程时,需要用newFixedThreadPool
*/
ExecutorService singlePool = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
singlePool.execute(new Runnable() {
@Override
public void run() {
System.out.println("singlePool " + Thread.currentThread().getName());
}
});
}


/**
* 缓存行池子,可改变尺寸的线程池。
* 当有线程时,先查看池子中是否有以前的建立的线程,若有,则用,若没有,则新建。
* 通常执行生存周期短的异步型任务
*/
ExecutorService cachePool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
cachePool.execute(new Runnable() {
@Override
public void run() {
System.out.println("cachePool " + Thread.currentThread().getName());
}
});
}

}
}

线程池的参数

参考博客

1.corePoolSize

核心线程数。该核心线程数会一直存活。不满核心线程数时,即使线程空闲,也会优先创建新线程来处理任务。核心线程在allowCoreThreadTimeOut设置为true时会超时退出,默认情况不会退出

2.maxPoolSize

最大线程数,当线程数大于或等于核心线程数时,且任务队列满,则会创建新线程,直到达到maxPoolSize。如果线程数达到maxPoolSize且任务队列满,则会拒绝处理任务,抛出异常

3.keepAliveTime

当线程空闲时间达到keepAliveTime,该线程会退出,直到线程数量等于corePoolSize。如果allowCoreThreadTimeout设置为true,则所有线程均会退出直到线程数量为0。

4.allowCoreThreadTimeOut

是否允许核心线程空闲退出,默认值为false。

5.queueCapacity

任务队列容量,任务队列的容量会影响到线程的变化

sleep()和wait()的区别

参考博客

sleep()方法主动让出cpu,让cpu干其他事,但是不释放同步资源锁,依旧保持监控。

wait()方法让自己释放同步资源锁,让其它等待该资源的线程争夺该锁。只有调用了notify()方法,使得之前调用wait()方法的线程才会解除wait()状态,进而又可以去参与争夺同步资源锁

sleep()方法可以在任何地方使用。wait()方法则只能在同步方法或同步块中使用,wait()是Object的方法,调用会放弃对象锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public class App {
/**
* 输出
* enter thread1...
* thread1 is waiting
* enter thread2...
* thread2 notify other thread can release wait status..
* thread2 is sleeping ten millisecond...
* thread2 is going on...
* thread2 is being over!
* thread1 is going on...
* thread1 is being over!
*
*/
public static void main(String[] args) {
new Thread(new Thread1()).start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new Thread2()).start();
}
private static class Thread1 implements Runnable {
public void run() {
synchronized (App.class) {
System.out.println("enter thread1...");
System.out.println("thread1 is waiting");
try {
App.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread1 is going on...");
System.out.println("thread1 is being over!");
}
}
}
private static class Thread2 implements Runnable {
public void run() {
synchronized (App.class) {
System.out.println("enter thread2...");
System.out.println("thread2 notify other thread can release wait status..");
App.class.notify();
System.out.println("thread2 is sleeping ten millisecond...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread2 is going on...");
System.out.println("thread2 is being over!");
}
}
}
}

同步和异步的区别,多线程的实现方法,同步的实现方法,当一个线程进入一个对象的一个 synchronized方法后,其它线程是否可进入此对象的其它方法?

如果数据在线程间共享。正在写的数据可能被以后的线程读到,或者读的数据被写,那么就是用同步存取

一个线程花费了很长时间去执行方法,并且不需等待程序的返回时,使用异步,且会更有效率

1
2
3
4
5
6
7
8
9
10
11
12
//这俩方法同步吗
class Test {
synchronized static void sayHello3() {

}
synchronized void getX(){

}
}

解答:本身同步,但是方法不同步。第一个静态方法持有的锁是Test.class,
第二个一般方法持有的锁是创建的实例对象,即this,所有两个方法的锁不同,即不同步

参考博客

多线程有两种实现方法,分别是继承Thread类与实现Runnable接口

同步的实现,主要是使用synchronized,wait与notify 配合使用

wait():使一个线程处于等待状态,并且释放所持有的对象的 lock,只能在sychronized内部使用。

sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉 InterruptedException异常。

notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且不是按优先级。

Allnotity():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。

当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法

分情况:

1.当其它方法没有加synchronized关键字时,则可以进。

2.当加synchronized关键字,这个方法内部有wait(),则可进入此对象的其它方法,没有wait(),则不可以进入此对象的其它方法。

3.其它方法是static方法,用的同步锁是当前类的字节码。与非静态的方法不能同步,因为非静态的方法用的是this。static方法和非静态方法用的监视器是不一样的。

启动一个线程是用 run()还是 start()?

启动一个线程是调用start()方法,使线程就绪状态,以后可以被调度为运行状态。

一个线程必须关联一些具体的执行代码,run()方法是该线程所关联的执行代码。

线程间的状态转化

状态:就绪,运行,synchronize 阻塞,wait 和 sleep 挂起,结束。wait 必须在 synchronized 内部调用。 调用线程的 start 方法后线程进入就绪状态,线程调度系统将就绪状态的线程转为运行状态,遇到 synchronized 语句时,由运行状态转为阻塞,当 synchronized 获得锁后,由阻塞转为运行,在这种情况可 以调用 wait 方法转为挂起状态,当线程关联的代码执行完后,线程变为结束状态。

synchronized和java.util.concurrent.locks.Lock的异同

参考博客

1.Lock能实现synchronized锁的所有功能。

2.Lock 有比 synchronized 更精确的线程语义和更好的性能。synchronized 会自动释放锁,而 Lock
一定要求程序员手工释放,并且必须在 finally 从句中释放。Lock 还有更强大的功能,例如,它的 tryLock 方法可以非阻塞方式去拿锁。

tryLock如果获取了锁,立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;

如果用synchronized,发现锁被占了,只能等着。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class App {
/**
* @param args
*/
private int j;
private Lock lock = new ReentrantLock();
public static void main(String[] args) {

// TODO Auto-generated method stub
App tt = new App();
for(int i=0;i<2;i++) {
new Thread(tt.new Adder()).start();
new Thread(tt.new Subtractor()).start();
}
}
private class Subtractor implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub while(true) {
/*synchronized (App.this) {
System.out.println("j--=" + j--);
}*/ //这里抛异常了,锁能释放吗?
lock.lock();
try {
System.out.println("j--=" + j--);
}
finally {
lock.unlock();
}
}
}

private class Adder implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub while(true) {
/*synchronized (App.this) {
System.out.println("j++=" + j++);
}*/
lock.lock();
try {
System.out.println("j++=" + j++);
}finally {
lock.unlock();
}
}
}
}

ArrayList 和 Vector 的区别

参考博客

vector是线程安全的吗

ArrayList,Vector这两个类都实现List接口(List继承collection接口),都是有序线性集合,集合内的元素允许重复,能用索引号取元素。HashSet之类集合不可以用用索引号取元素,而且不允许有重复元素。

同步方面:

Vector是线程同步的,即线程安全。ArrayList是线程不安全的,若只有一个线程访问,优先使用ArrayList,效率会高。若为多个线程,最好使用Vector,不需要我们编写线程安全的代码。

注:Vector 与 Hashtable 是 旧的,是 java 一诞生就提供了的,它们是线程安全的,ArrayList 与 HashMap 是 java2 时才提供的,它们是线程不安全的。

容量方面:

当ArrayList和 Vector存储的容量达到上限,且超过容量时,就需要增加存储空间。

ArrayList增加原来的0.5倍,Vector增加原来的1倍。这么做为了内存空间利用和程序效率达到一个平衡。

附:对于以散列表为底层数据结构实现的,(譬如hashset,hashmap,hashtable等),扩容方式为当链表数组的非空元素除以数组大小超过加载因子时,链表数组长度变大(乘以2+1),然后进行重新散列。

LinkedList:

ArrayList和Vector都是使用数组方式存储数据,对于删除和插入,移动数据代价较高,适合查询定位元素。

LinkedList用链表结构存储数据,适合数据的动态插入和删除。另外,他还提供了List接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。

HashMap 和 HashTable 区别

1.都实现了Map接口,但是HashTable继承Dictionary,HashMap继承AbstractMap类。

2.HashMap非线程安全,HashTable是线程安全的。

3.HashMap 允许将 null 作为一个 entry 的 key 或者 value,而 Hashtable 不允许空。

4.HashMap 把 HashTable的contains的方法去掉,变成了containKey 和 containsValue。

List 和 Map 和 Set 的区别

首先List和Set这俩个接口比较相似,都是单列元素的集合,共同继承Collection接口。

但是Set集合不允许有重复元素,没有get() 方法,要想获取Set中的值,可通过Iterator接口获取所有值。

List集合里面的元素是有先后顺序的,元素可重复,可直接通过索引来获取元素,get()方法,也可通过Iterator接口获取所有值。

Map是双列元素集合。要放入一对键值对(key/value),且键(key)不能重复,值(value)可以重复,用keySet()返回所有键,entrySet方法返回键值对组合的entrySet集合。也可用get(key),获取value。

HashSet 和 HashMap 插入元素时,首先经过hashcode比对,若hashcode相同,在用equals比对,若还是相同,则代表插入的元素已存在,导致无法插入。

Collection 和 Collections 的区别

Collection是集合类的主要接口,Collections里面有很多静态方法,用于实现各种集合类的搜索,排序,线程化安全等。

1
2
3
String[] args = new String(){"adsf","adf"};
List<String> argList = new ArrayList<>();
Collections.addAll(argList, args); //将args放到argList中

两个对象值相同(x.equals(y) == true),但却可有不同的 hash code,这句话对不对?

如果对象要保存在 HashSet 或 HashMap 中,它们的 equals 相等,那么,它们的 hashcode 值就必须相等。 如果不是要保存在 HashSet 或 HashMap,则与 hashcode 没有什么关系了,这时候 hashcode 不等是可以的,例如 arrayList 存储的对象就不用实现 hashcode,当然,我们没有理由不实现,通常都会去实现的。

字符流和字节流

参考博客1
博客2

所有文件的储存是都是字节(byte)的储存,在磁盘上保留的并不是文件的字符而是先把字符编码成字节,再储存这些字节到磁盘。

InputStream,OutputStream,Reader,Writer是java输入输出的四个抽象类。

InputStream,OutputStream用于字节流,一般用于二进制和字节对象。将二进制数据输出或读入到设备。

Reader,Writer用于字符流,一般用于字符和字符串。

字节流操作时候不会用到缓冲区,是与文件之间操作的。字符流操作时候用到缓冲区。

字节流操作文件时,即使不关闭资源,也能输出,输入,但是字符流操作文件时,必须得用close()关闭资源,写入到文件中。或者使用flush() 强制刷新缓冲区

计算机中的一切最终都是二进制的字节形式存在。对于“中国”这些字符,首先要得到其对应的字节,然后将字节写入到输出流。读取时,首先读到的是字节,可是我们要把它显示为字符,我们需要将字节转换成字符,所以使用字符流,字符流是字节流的包装。

附加:编码的转换博客

编码转换

什么是 java 序列化,如何实现 java 序列化?或者请解释 Serializable 接口的作用

有时候需要奖java对象转换成字节流的形式向外传输数据或者将字节流转换成java对象,比如将对象存储到硬盘或者传输给网络中的某个计算机。我们可以通过调用OutputStream的writeObject方法来做,也可以将要传输的对象实现Serializable接口,这样javac进行编译时就会进行特殊处理,编译的类才会被writeObject方法操作,这就是序列化。Serializable接口为mini接口,没有要实现的方法,implements Serializable 只是为了标注该对象是可被序列化的。

JVM 加载 class 文件的原理机制

参考博客

java中的类,必须装载到jvm中才能运行,这个装载工作得由jvm中的类装载器(ClassLoader)完成,类装载器的实质就是把类文件从硬盘中读取到内存中

栈和堆的区别

参考博客

java内存分为两类,一个是堆内存,一个是栈内存。

栈内存是指一个程序进入方法块时,会给方法分配一个私属的存储空间,用来存储方法内的局部变量,当该方法结束时,就会收回分配给该方法的栈。

堆内存主要存放new出来的对象或者数组,在堆中分配的内存,有java的自动垃圾回收器来管理。在堆中产生了一个数组或对象后,还可以在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。

方法中的局部变量使用 final 修饰后,放在堆中,而不是栈中。

GC是什么,GC的优点和原理,写出两种回收机制,有什么办法主动通知虚拟机进行垃圾 回收?

参考博客

GC是垃圾收集,编程人员对于内存容易忘记或者错误回收内存从而导致系统的崩溃。因为GC能自动监控对象是否超过作用域,从而对对象回收的目的。

垃圾回收可以防止内存的泄漏,有效使用可以使用的内存。垃圾回收通常作为单独的低级别的线程运行,对于内存堆中的已经死亡或者长时间没有使用的的对象进行清除和回收。

回收机制:分代回收,标记清楚,增量回收

对于GC来说,程序员创建对象后,GC就开始监控该对象的使用情况和大小,地址等。GC一般采用有向图的方式来记录和管理堆中的对象,从而来确定哪些对象是可达的,哪些对象是不可达的,GC有义务去回收哪些不可达的对象。

程序员可以手动执行 System.gc(),通 知 GC 运行,但是 Java 语言规范并不保证 GC 一定会执行。

Java中的ThreadLocal

参考博客

THreadlocal是线程创建局部变量的类

通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。而用ThreadLocal创建的变量只能被单个线程访问,其它线程无法访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
例子:
private void testThreadLocal() {
Thread t = new Thread() {
ThreadLocal<String> mStringThreadLocal = new ThreadLocal<>();

@Override
public void run() {
super.run();
mStringThreadLocal.set("droidyue.com");
mStringThreadLocal.get();
}
};

t.start();
}