volatile关键字
Java中的volatile关键字的功能
volatile是java中的一个类型修饰符。它是被设计用来修饰被不同线程访问和修改的变量。如果不加入volatile,基本上会导致这样的结果:要么无法编写多线程程序,要么编译器 失去大量优化的机会。
1,可见性
可见性指的是在一个线程中对该变量的修改会马上由工作内存(Work Memory)写回主内存(Main Memory),所以会马上反应在其它线程的读取操作中。顺便一提,工作内存和主内存可以近似理解为实际电脑中的高速缓存和主存,工作内存是线程独享的,主存是线程共享的。
2,禁止指令重排序优化
禁止指令重排序优化。大家知道我们写的代码(尤其是多线程代码),由于编译器优化,在实际执行的时候可能与我们编写的顺序不同。编译器只保证程序执行结果与源代码相同,却不保证实际指令的顺序与源代码相同。这在单线程看起来没什么问题,然而一旦引入多线程,这种乱序就可能导致严重问题。volatile关键字就可以从语义上解决这个问题。
注意,禁止指令重排优化这条语义直到jdk1.5以后才能正确工作。此前的JDK中即使将变量声明为volatile也无法完全避免重排序所导致的问题。所以,在jdk1.5版本前,双重检查锁形式的单例模式是无法保证线程安全的。因此,下面的单例模式的代码,在JDK1.5之前是不能保证线程安全的。
volatile不能保证线程安全;
类多态问题
package Wangyi;
class Base
{
public void method()
{
System.out.println("Base");
}
}
class Son extends Base
{
public void method()
{
System.out.println("Son");
}
public void methodB()
{
System.out.println("SonB");
}
}
public class Test01
{
public static void main(String[] args)
{
Base base = new Son();
base.method();
base.methodB();
}
}
编译不通过
Base base=new Son(); 是多态的表示形式。父类对象调用了子类创建了Son对象。
base调用的method()方法就是调用了子类重写的method()方法。
而此时base还是属于Base对象,base调用methodB()时Base对象里没有这个方法,所以编译不通过。
要想调用的话需要先通过SON son=(SON)base;强制转换,然后用son.methodB()调用就可以了。
这类多态问题中,无论向上或向下转型,都记住一句话就可以了。
编译看左边,运行看右边。意思编译时候,看左边有没有该方法,运行的时候结果看 new 的对象是谁,就调用的谁。
线程
下列关于JAVA多线程的叙述正确的是()
正确答案: B C
A. 调用start()方法和run()都可以启动一个线程
B. CyclicBarrier和CountDownLatch都可以让一组线程等待其他线程
C. Callable类的call()方法可以返回值和抛出异常
D. 新建的线程调用start()方法就能立即进行运行状态
A. start()方法来启动线程,真正实现了多线程运行,调用了run()方法;run()方法当作普通方法的方式调用。
B. CyclicBarrier让一组线程等待其他线程;CountDownLatch让一组线程等待某个事件发生。
C. Callable能够抛出checked exception。
D. start()方法让thread进去可运行状态(runnable),等待获取CPU的使用权。
区分“终止” 和 “阻塞”:
终止:这个线程不会在进入“就绪态”,宣告死亡,即“死亡状态”。
阻塞:进入阻塞态的线程还可以再进入“就绪态”,等待下一次 CPU 时间。
然后是对线程的 5 个状态的理解:
- 新建,刚刚新建的线程,还未进入就绪队列
- 就绪,进入就绪队列的线程拥有了获得 CPU 时间的机会,但不是一定会马上执行,与线程调度有关。
- 运行,获得了 CPU 时间,正在被执行的线程。
- 阻塞,进入阻塞状态的线程只是暂时失去了 CPU 时间,该类线程没有结束,“阻塞态”的线程只能进入到“就绪态”。
- 死亡,死亡的线程即彻底结束了。
下面总结下使一个线程进入阻塞状态的方法:
- sleep() / suspend()
- 发生IO阻塞
- 等待同步锁
- 等待通知
解除一个线程的阻塞状态,使之进入就绪态的方法:
- sleep() 指定的睡眠时间到了
- IO阻塞解除
- 获得同步锁
- 收到通知
- 调用了 suspend() 的线程阻塞后,再调用 resume() 解除阻塞
集合框架
杂项
Which statement declares a variable a which is suitable for referring to an array of 50 string objects?(Java)
正确答案: B C F
A. char a[][];
B.String a[];
C.String[] a;
D.Object a[50];
E.String a[50];
F.Object a[];
在java 中,声明一个数组时,不能直接限定数组长度,只有在创建实例化对象时,才能对给定数组长度.。
A:char[][] 定义了二位字符数组。在Java中,使用字符串对char数组赋值,必须使用toCharArray()方法进行转换。所以A错误。
B、C:在Java中定义String数组,有两种定义方式:String a[]和String[] a。所以B、C正确。
D、E:数组是一个引用类型变量 ,因此使用它定义一个变量时,仅仅定义了一个变量 ,这个引用变量还未指向任何有效的内存 ,因此定义数组不能指定数组的长度。所以D、E错误。
F:Object类是所有类的父类。子类其实是一种特殊的父类,因此子类对象可以直接赋值给父类引用变量,无须强制转换,这也被称为向上转型。这体现了多态的思想。所以F正确。
最后选B、C、F
面向对象程序设计语言的三个基本特征是封装、继承、多态
StringBuffer是线程安全,StringBuilder不是
接口和抽象
- 一个子类只能继承一个抽象类,但能实现多个接口
- 抽象类可以有构造方法,接口没有构造方法
- 抽象类可以有普通成员变量,接口没有普通成员变量
- 抽象类和接口都可有静态成员变量,抽象类中静态成员变量访问类型任意,接口只能public static final(默认)
- 抽象类可以没有抽象方法,抽象类可以有普通方法,接口中都是抽象方法
- 抽象类可以有静态方法,接口不能有静态方法
- 抽象类中的方法可以是public、protected;接口方法只有public
==和equal
==是比较两个内存地址是否相同,相同为true,不相同为false;
在字符串缓冲池中,若已有字符串,则直接赋值时不再新创建一个字符串,如果是new 关键字,则新创建一个不同的字符串。
String类的equals被重新复写,只要字符串相等,则返回true。
1、基本型和基本型封装型进行== 运算符的比较,基本型封装型将会自动拆箱变为基本型后再进行比较,因此Integer(0)会自动拆箱为int类型再进行比较,显然返回true
2、两个Integer类型进行“==”比较,如果其值在-128至127,那么返回true,否则返回false, 这跟Integer.valueOf()的缓冲对象有关,这里不进行赘述。
3、两个基本型的封装型进行equals()比较,首先equals()会比较类型,如果类型相同,则继续比较值,如果值也相同,返回true
4、基本型封装类型调用equals(),但是参数是基本类型,这时候,先会进行自动装箱,基本型转换为其封装类型,再进行3中的比较。