前言:
社长,4年api搬运工程师,之前做的都是一些框架的搬运工作,做的时间越长,越发感觉自己技术越菜,有同感的社友,可以在下方留言。现侧重于java底层学习和算法结构学习,希望自己能改变这种现状。
为什么大厂面试,更侧重于java原理底层的提问,因为通过底层的提问,他能看出一个人的学习能力,看看这个人的可培养潜力。随着springboot的流行,大部分的开发,起步就是springboot。也不看看springboot为什么简单?他底层是如果实现的。为什么同样出来4年,工资差别都很大?这些问题都值得我们深思(对社长自己说的)。
api工程师,顶天了,最多达到某些公司中级工资上限,很难突破高级这道坎。希望我们大家成就更好的自己。回想起往事,不会后悔。
1 每日一面:
面试官隔壁小王:自我介绍一下
社长:面试官,您好!我叫社长….
面试官隔壁小王:你说说如何实现一个单例?都有几种实现方式
社长: 我知道两种,一种是懒汉式,还有一种饿汉式。
懒汉式:
package com.fyqd.test;
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
饿汉式:
package com.fyqd.test;
/**
* Description:
* Author: 程序猿学社
* Date: 2020/1/11 23:46
* Modified By:
*/
public class HungrySingleton {
private static HungrySingleton instance = new HungrySingleton();
private HungrySingleton (){}
public static HungrySingleton getInstance() {
return instance;
}
}
面试官隔壁小王:不错,能回答上来二种。实际上还有很多种。你回去后,可以去了解一下。
社长:嗯嗯,我回去了解一下。
2 什么是单例?
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
• 1、单例类只能有一个实例。
• 2、单例类必须自己创建自己的唯一实例。
• 3、单例类必须给所有其他对象提供这一实例。
应用实例:
- 一个人只能有一个对象,在这里社长得提醒某些人动不动就new一个对象。
- Window所有的文件操作,都是只有一个实例的。大家应该经常遇到某个文件打开后,我们再去删除这个文件,会提示删除失败,这就是应为window只有一个实例的原因。
3 单例模式的几种实现方式?
3.1 懒汉式:
package com.fyqd.test;
/**
* Description:懒汉式:见名思意,很懒的意思
* 优点:应用程序一运行的时候,不会直接加载对象,减少不必要的内存开销
* 缺点:不支持多线程。
* Author: 程序猿学社
* Date: 2020/1/11 23:46
* Modified By:
*/
public class Singleton {
private static Singleton instance;
private Singleton (){
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
3.1.1如何验证懒汉式是线程不安全的?
社友纷纷议论说,这也太简单勒,直接调用两次getIntance方法,比较他们的值是不是相等就行
社友提供的想法:
package com.fyqd.test;
/**
* Description:
* Author: 程序猿学社
* Date: 2020/1/12 0:29
* Modified By:
*/
public class SingletonTest {
public static void main(String[] args) {
System.out.println(Singleton.getInstance() == Singleton.getInstance());
System.out.println(Singleton.getInstance() == Singleton.getInstance());
System.out.println(Singleton.getInstance() == Singleton.getInstance());
}
}
对应输出结果
看,这不就是小case,为了怕社长你赖皮,我还特意多对比几次,这你服不服。
划重点:
我们大多的写法,在单线程里面是不会出现问题的。有问题,也不会让我们学习。一般出问题,就出现多线程里面。
反方:社长
package com.fyqd.test;
/**
* Description:懒汉式:见名思意,很懒的意思
* 优点:应用程序一运行的时候,不会直接加载对象,减少不必要的内存开销
* 缺点:不支持多线程。
* Author: 程序猿学社
* Date: 2020/1/11 23:46
* Modified By:
*/
public class Singleton {
private static Singleton instance;
private Singleton (){
System.out.println("实例化");
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
在构造方法中打印一句话。看他打印多少次,我们通过控制台观察他的次数,是不是就是实例化的次数
测试类:
package com.fyqd.test.test;
import com.fyqd.test.Singleton;
/**
* Description:
* Author: 程序猿学社
* Date: 2020/1/12 11:14
* Modified By:
*/
public class Test {
public static void main(String[] args) throws InterruptedException {
for(int i=0;i<1000;i++){
new Thread(){
@Override
public void run() {
Singleton s1 = Singleton.getInstance();
}
}.start();
}
}
}
测试结果:
社友更加代码验证的时候,记得把次数设置大一点,多运行几次,再下结论。养成良好的学习习惯,成为一个严谨的程序员。
咦,咋回事,社友们是不是觉得很奇怪,怎么会打印多次,说好的单例模式,一个应用程序中只会实例化一次,一个人只能有一个对象,而不能无限的new,这样是要不得的。
分析:
原因出在这块
为了更好地理解消化,我们可以假设有两个线程A,B
可能存在这样一种线程A运行到判断这一行时,线程B抢到执行的权力,先一步创建勒对象,而A还不知道这回事,拿到执行权后,也new对象,这样就会造成多次实例化对象,造成内存不必要的开销。
3.1.2如何解决懒汉式不线程安全?
第一种,加synchronized
分析:在多线程中加锁,性能低下,synchronized就是上锁,可以简单的理解为有一个大门,每个人来,都需要从上一个人哪里拿到钥匙,才能进来。还有一种就是大门直接打开。加锁和不加锁,这两种懒汉式写法,实际上就是安全和效率,两者取其一。想安全就效率低,想效率高,就不安全。
第二种:Volatile+双重校验锁法
package com.fyqd.test;
/**
* Description:懒汉式:见名思意,很懒的意思
* 优点:应用程序一运行的时候,不会直接加载对象,减少不必要的内存开销
* 缺点:不支持多线程。
* Author: 程序猿学社
* Date: 2020/1/11 23:46
* Modified By:
*/
public class Singleton {
private volatile static Singleton instance;
private Singleton (){
System.out.println("实例化");
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class){
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
Volatile关键字的作用:
禁止进行指令的重排序。
指令重排包含编译器优化的重排序,指令级并行的重排序,内存系统的重排序。
增加volatile实际上就是增加一个内存屏障,告诉虚拟机,你不要给我多事,按我程序写的来。
是不是有点懵,下面通过一个小例子,我们来简单的说说把
指令重排序对单线程没有什么影响,他不会影响程序的运行结果,但是会影响多线程。假设我下面这段代码是在多线程环境里面。他在底层执行的顺序可能是?
通过行号来标识
按从上到下跑11 12 13,输出1
可能还存在 11 13 12,输出0 这种情况为什么会存在,就是因为多线程过程中,为了性能更好,会有重排,这也是为什么我在单例哪里加入volatile关键字的原因。就是告诉虚拟机,你不要跟我重排,老老实实的根据我写的代码顺序执行。
注意:不要随便用volatile。这里我只是举例子。实际上还得考虑场景。
3.2 饿汉式:
package com.fyqd.test;
/**
* Description:饿汉式
*
* Author: 程序猿学社
* Date: 2020/1/11 23:46
* Modified By:
*/
public class HungrySingleton {
//应用程序启动的时候就创建,并用静态变量保持
private static HungrySingleton instance = new HungrySingleton();
/**
* 构造器是时间私有,划重点,这就是单例实现的一个重要要素,其他人没有办法实例化对象
*/
private HungrySingleton (){}
public static HungrySingleton getInstance() {
return instance;
}
}
直接创建对象,不存在线程安全问题。
记忆小技巧:听名字,就知道,他很饿,所以他就不会管很多,会直接创建对象,可以这样记忆。
描述:这种方式比较常用,但容易产生垃圾对象。因为他被加载时,就会被实例化,如果没有一直没有被调用,就会造成内存的浪费。
3.3枚举
/**
* Description:
* Author: 程序猿学社
* Date: 2020/1/12 0:22
* Modified By:
*/
public enum EnumSingleton {
INSTANCE;
public void whateverMethod() {
}
}
描述:jdk1.5版本后引入的,这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制。
每日一问环节:
package com.fyqd.test.test;
/**
* Description:
* Author: 程序猿学社
* Date: 2020/1/12 12:51
* Modified By:
*/
public class Test2 {
public static void main(String[] args) {
}
}
运行main方法,社友们觉得这里启动了几个线程?欢迎在下方评论,留上你的答案,正确的答案,会在下篇文章中分享出来。
每周实时更新,分享java最新大厂面试题,底层原理等干货
关注个人公众号“程序猿学社”或者扫码关注