天天看点

阿里面试题-单件模式及相关问题

内推网上投了份简历,先是电话面试半个多小时,一周后通知face to face面试,邮件里面时间是1小时,后来面了接近两个小时,包括linux文件系统、常用命令、服务器监控,java方面包括jvm、jms、多线程、并发、常用框架,DB包括隔离级别、锁、优化等。因为面试岗位是web java高级开发,所以linux、java、DB都有。最后结果还是挂了。

这里记录面试中一个由单件模式扩展的题目,当时答对了前半部分,后半部分在最后问面试官的问题中又请教了他,不过下来试验,发现他的一个答案有错。当时就觉得有问题,只是没有时间细想就结束了。

package com.du.concurrent;

public class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton(){
        System.out.println("Singleton construct...");
        try {
            Thread.sleep(500);   //线程睡眠0.5s,模拟有些大对象需要长时间construct的过程
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public static Singleton getInstance() {
        return instance;
    }

    public static void main(String[] args) {
        System.out.println("Before Singleton construct...");
        Singleton.getInstance();
    }
}
           

普通的单件模式代码。其中Thread.sleep(500)用来模拟大对象构建过程中,系统调度构建线程交出CPU时间的过程。

然后被问到假如这个对象很大,不希望在类加载的时候构建对象,希望用一种lazy的方式构建。 1. static对象在类加载时构建,所以上述代码的输出为:

Singleton construct...
Before Singleton construct...
           
  1. lazy的方式:
    package com.du.concurrent;
    
    public class Singleton {
        private static Singleton instance = null;
        private Singleton(){
            System.out.println("Singleton construct...");
            try {
                Thread.sleep(500);   //线程睡眠0.5s,模拟有些大对象需要长时间construct的过程
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    
        public static Singleton getInstance() {
            if(instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    
        public static void main(String[] args) {
            System.out.println("Before Singleton construct...");
            Singleton.getInstance();
        }
    }
               

上述代码输出为:

Before Singleton construct...
Singleton construct...
           

再被问到上面代码有什么问题,我回答不是线程安全的。然后就需要做同步保护。最直接就是用synchronized保护。不加synchronized的代码:

package com.du.concurrent;

public class Singleton {
    private static Singleton instance = null;
    private Singleton(){
        System.out.println("Singleton construct...");
        try {
            Thread.sleep(500);   //线程睡眠0.5s,模拟有些大对象需要长时间construct的过程
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public static Singleton getInstance() {
        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

    public void print(String str) {
        System.out.println(str);
    }

    private static final int THREAD_COUNT = 20;
    public static void main(String[] args) {
        System.out.println("Before Singleton construct...");
        Thread[] threads = new Thread[THREAD_COUNT];
        for(int i = 0; i < THREAD_COUNT; ++i) {
            threads[i] = new Thread(new Runnable() {    
                public void run() {
                    Singleton.getInstance().print("Thread run...");
                }
            });
            threads[i].start();
        }

        if(Thread.activeCount() > 0) {
            Thread.yield();
        }
    }
}
           

输出结果显示Singleton构建了多次。加了synchronized保护:

public static synchronized Singleton getInstance() {
        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
or
public static Singleton getInstance() {
        synchronized (Singleton.class) {
            if (instance == null) {
                instance = new Singleton();
            }
        }
        return instance;
    }
           

结果显示Singleton构建一次。

在用synchronized保护时,面试官提到要讲synchronized细化,我理解上述两种方式效果是一样的,也就没有继续细化的空间,如果只用synchronized保护instance = new Singleton();这行代码,非常明显是达不到效果的。

以上为面试过程中我的答案。最后面试完,问我有没有什么问题,我又请教了这个问题。面试官给的答案:1. 同步可以用volatile修饰对象;2. lazy初始化可以用内部类来实现,因为父类虽然是在加载时就初始化了static对象,但是内部类却是在调用时才初始化。对1,我感觉有问题,因为《深入理解Java虚拟机》中提到,volatile对象需要在更新值时不依赖于其当前的值,但是这里只有当对象为null时才new,也即依赖了当前值。但当时继续下一个问题,来不及细想。对2,则是完全不知道。

问题1的测试代码:

package com.du.concurrent;

public class Singleton {
    private static volatile Singleton instance = null;
    private Singleton(){
        System.out.println("Singleton construct...");
        try {
            Thread.sleep(500);   //线程睡眠0.5s,模拟有些大对象需要长时间construct的过程
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

    public void print(String str) {
        System.out.println(str);
    }

    private static final int THREAD_COUNT = 20;
    public static void main(String[] args) {
        System.out.println("Before Singleton construct...");
        Thread[] threads = new Thread[THREAD_COUNT];
        for(int i = 0; i < THREAD_COUNT; ++i) {
            threads[i] = new Thread(new Runnable() {    
                public void run() {
                    Singleton.getInstance().print("Thread run...");
                }
            });
            threads[i].start();
        }

        if(Thread.activeCount() > 0) {
            Thread.yield();
        }
    }
}
           

输出结果显示的确是construct了多次。

在内部类中初始化:

package com.du.concurrent;

public class Singleton {
    private Singleton(){
        System.out.println("Singleton construct...");
        try {
            Thread.sleep(500);   //线程睡眠0.5s,模拟有些大对象需要长时间construct的过程
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public void print(String str) {
        System.out.println(str);
    }

    public static void main(String[] args) {
        System.out.println("Before Singleton construct...");
        Singleton.InSing.instance.print("Lazy construct...");
    }

    static class InSing{
        public static Singleton instance = new Singleton();
    }
}
           

输出为:

Before Singleton construct...
Singleton construct...
Lazy construct...
           

construct的确是在进入main函数后才执行。

阿里的java在业界是非常厉害的,很期望能进入阿里,不过可惜...