面试题1:赋值运算符函数
题目:如下为类型CMyString的声明,请为该类型添加赋值运算符函数
class CMyString
{
public:
CMyString(char* pData = nullptr);
CMyString(const CMyString& str);
~CMyString(void);
private:
char* m_pData;
}; - 赋值运算符函数
- 对于传入的参数,不应该被修改,使用final修饰;
- 如果两个对象相同或值相等,不进行操作,直接返回;
- 返回值最好为this,这样可以使赋值链接起来;
- 一个缺点:此赋值从左到右进行,a=b=c等价于a=c,b不会被赋值;而如果是String的=运算,a,b都会被赋成c的值。
public class AssignmentOperator {
public static class MyString {
private String data;
public MyString(String data) {
this.data = data;
}
public MyString assign(final MyString another){
if (this == another || this.data.equals(another.data)) {
return this;
} else {
this.data = another.data;
return this;
}
}
@Override
public String toString() {
return "MyString { " + "data = '" + data + "'" + " } ";
}
}
public static void main(String[] args) {
MyString s1 = new MyString("a");
MyString s2 = new MyString("b");
MyString s3 = new MyString("c");
System.out.println(s1.assign(s2).assign(s3));
System.out.println("s1:" + s1);
System.out.println("s2:" + s2);
System.out.println("s3:" + s3);
}
} 面试题2:实现Singleton模式
题目:设计一个类,我们只能生成该类的一个实例
- 单例模式
- 定义:指实现了特殊模式的类,该类仅能被实例化一次,产生唯一的一个对象
- 分类:饿汉式,懒汉式,双检锁,静态内部类,枚举
- 应用举例:windows的任务管理器,回收站,web应用的配置对象,spring中的bean默认也是单例
- 评价指标:单例(必须),线程安全,延迟实例化,防止反序列化产生新对象,防止反射攻击
- 实现方法的选择:一般情况下直接使用饿汉式就好了,要求延迟实例化时倾向于用静态内部类,涉及到反序列化创建对象或反射问题最好选择枚举
public class Singleton {
public static void main(String[] args) {
// 调用方式
Singleton1 singleton1 = Singleton1.getInstance();
Singleton2 singleton2 = Singleton2.getInstance();
Singleton3 singleton3 = Singleton3.getInstance();
Singleton4 singleton4 = Singleton4.getInstance();
Singleton5 singleton5 = Singleton5.getInstance();
Singleton6 singleton6 = Singleton6.getInstance();
Singleton7 singleton7 = Singleton7.uniqueInstance;
singleton7.setAttribute("aaa");
}
}
// 版本一:饿汉式
// 特点:线程安全;类初始化执行到静态属性时分配内存,资源浪费
class Singleton1 {
// 或者将private static final成员设为公有成员,可省去getInstance公有函数
private static Singleton1 uniqueInstance = new Singleton1();
private Singleton1() {}
public static Singleton1 getInstance() {
return uniqueInstance;
}
}
// 版本二:懒汉式
// 特点:非线程安全;第一次调用获取实例方法时分配内存,延迟实例化
class Singleton2 {
private static Singleton2 uniqueInstance;
private Singleton2() {}
public static Singleton2 getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton2();
}
return uniqueInstance;
}
}
// 版本三:懒汉式变种(synchronized同步方法,支持多线程)
// 特点:线程安全;synchronized造成的阻塞致使效率低,第一次调用后的每次调用都是没有必要的
class Singleton3 {
private static Singleton3 uniqueInstance;
private Singleton3() {}
public static synchronized Singleton3 getInstance() {
if(uniqueInstance == null) {
uniqueInstance = new Singleton3();
}
return uniqueInstance;
}
}
// 版本四:懒汉式变种(synchronized同步块,支持多线程)
// 特点:写法不同,但与版本三有一样的问题
class Singleton4 {
private static Singleton4 uniqueInstance;
private Singleton4() {}
public static Singleton4 getInstance() {
synchronized (Singleton4.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton4();
}
}
return uniqueInstance;
}
} - 版本五:双检锁DCL(支持多线程-懒汉式)
- 特点:线程安全;多进行一次if判断,加入volatile修饰,只有在第一次实例化时加锁,之后不会加锁,提升了效率
- volatile的两大作用:
- 1.防止编译器对被修饰变量相关代码进行指令重排,保证有序性
- 2.读写操作都不会调用工作内存而是直接取主存,保证内存可见性
- 禁止指令重排:
- instance = new Singleton5()可主要分为三步:1.分配内存,2.调用构造函数,3.instance指向被分配的内存(此时instance不为null了)
- 不加入volatile,可能出现第一个if判断不为null,但还并未执行构造函数的情况,因为java编译器会进行指令重排:正常顺序为123,指令重排可能执行顺序为132,会造成已不为null但未执行构造函数的问题
- 内存可见性:
- 如果字段是被volatile修饰的,Java内存模型将在写操作后插入一个写屏障指令,在读操作前插入一个读屏障指令
- 这意味着:1.一旦完成写入,任何访问这个字段的线程将会得到最新的;2.在写入前,任何更新过的数据值是可见的,因为内存屏障会把之前的写入值都刷新到缓存
- 因此volatile可提供一定的线程安全,但不适用于写操作依赖于当前值的情况,如自增,自减
- 简单来说,volatile适合这种场景:一个变量被多个线程共享,线程直接给这个变量赋值
class Singleton5 {
private volatile static Singleton5 uniqueInstance;
private Singleton5() {}
public static Singleton5 getInstance() {
if (uniqueInstance == null) {
synchronized (Singleton5.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton5();
}
}
}
return uniqueInstance;
}
} - 版本六:静态内部类,支持多线程-懒汉式
- 特点:利用静态内部类(只有在出现它的引用时才被加载),完成懒加载;final保证线程安全
- final的作用:
- 1.在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序
- 2.初次读一个包含final域的对象的引用,与随后读这个final域,这两个操作之间不能重排序
- static变量初始化遵循以下规则
- 1.静态变量会按照声明的顺序先依次声明并设置为该类型的默认值,但不赋值为初始化的值
- 2.声明完毕后,再按声明的顺序依次设置为初始化的值,如果没有初始化的值就跳过
class Singleton6 {
private Singleton6() {}
public static Singleton6 getInstance() {
return Singleton6Holder.uniqueInstance;
}
private static class Singleton6Holder {
public static final Singleton6 uniqueInstance = new Singleton6();
}
} - 版本七:通过枚举实现
- 一个完美的单例需要做到:单例,懒加载,线程安全,防止反序列化产生新对象,防止反射攻击
- 枚举的特性保证了以上除了懒加载以外的所有要求,而且实现代码极其简单
- Enum的单例模式参考:http://www.jianshu.com/p/83f7958b0944
enum Singleton7 {
uniqueInstance;
private String attribute;
void setAttribute(String attribute) {
this.attribute = attribute;
}
String getAttribute() {
return this.attribute;
}
} 

京公网安备 11010502036488号