别划走,干货在最底下呢!!!
一。系统底层如何实现数据一致性?
①MESI—缓存行对齐(数据本身不超出缓存行的情况)
②锁总线—(数据大小本身超出缓存行的情况)
二。五种单例模式的演变
单例模式:某一个类的对象再内存中只能有一个,叫单例模式。
①最简单 的单例模式的实现
看注解 这是最简单的实现,不赘述了
//直接new一个对象
private static final Mgr01 INSTANCE = new Mgr01();
//构造方法设为私有的,不让别人new
private Mgr01(){}
//给一个getInstance方法,供别人来使用(因为别人不能new)
public static Mgr01 getInstance(){
return INSTANCE;//返回INSTANCE
}
public void m(){
System.out.println("m");
}
public static void main(String[] args) {
Mgr01 m1 = Mgr01.getInstance();
Mgr01 m2 = Mgr01.getInstance();
System.out.println(m1 == m2);
}
②由于第一种是不管别人用不用new对象,他总是会创建出对象来。那么我想要调用getInstance方法时再来创建对象,所以引出了第二种方式:也称懒汉式单例
问题:虽然达到了按需初始化的目的,但却带来了线程不安全的问题违背了单例的本身含义,所以要继续改进
public class Mgr02 {
//定义一个INSTANCE,不new
private static Mgr02 INSTANCE;
//构造方法设为私有的,不让别人new
private Mgr02(){}
//给一个getInstance方法,
public static Mgr02 getInstance(){
//判断有没有new出来,如果没有就new,如果new出来了直接return
if(INSTANCE == null){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
INSTANCE = new Mgr02();
}
return INSTANCE;
}
public void m(){
System.out.println("m");
}
public static void main(String[] args) {
for(int i=0;i<100;i++){
new Thread(()->System.out.println(Mgr02.getInstance().hashCode())
).start();
}
}
加判断:如果为null就new否则直接return。
那么问题来了,这样做是否可以保证多线程访问的情况下只new了一个对象?
答案是当然不可以。
看我的运行结果
③使用synchronized修饰getInstance方法,解决了②的问题但是,也带来了效率下降的问题
public class Mgr03 {
//定义一个INSTANCE,不new
private static Mgr03 INSTANCE;
//构造方法设为私有的,不让别人new
private Mgr03(){}
//给一个getInstance方法,
public static synchronized Mgr03 getInstance(){
//判断有没有new出来,如果没有就new,如果new出来了直接return
if(INSTANCE == null){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
INSTANCE = new Mgr03();
}
return INSTANCE;
}
public void m(){
System.out.println("m");
}
public static void main(String[] args) {
for(int i=0;i<100;i++){
new Thread(()->System.out.println(Mgr03.getInstance().hashCode())
).start();
}
}
}
相对于②中来说,只是对getInstance方法加了synchroniazed修饰。
④ 在③中直接把整个方法全加锁了万一方法中有业务型的代码那么锁粒度太粗,如何解决—看代码
public class Mgr04 {
//定义一个INSTANCE,不new
private static Mgr04 INSTANCE;
//构造方法设为私有的,不让别人new
private Mgr04(){}
//给一个getInstance方法,
public static synchronized Mgr04 getInstance(){
if(INSTANCE == null){
//对当前类加锁
synchronized(Mgr04.class){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
INSTANCE = new Mgr04();
}
}
return INSTANCE;
}
public void m(){
System.out.println("m");
}
public static void main(String[] args) {
for(int i=0;i<100;i++){
new Thread(()->System.out.println(Mgr04.getInstance().hashCode())
).start();
}
}
}
这种方法是把方法上的synchronized关键字去掉,加到类对象上。
那么像一个问题这样会保证对象只被创建出一个吗?
答案是不能。eg:第一个线程执行判断是否为空,是;这会儿第二个线程来了,也判断是否为空,也是空;那么第二个线程开始创建对象完了以后释放锁,此时第一个线程得到锁,也new对象。所以这就是对象不唯一的原因。那么如何处理呢?
⑤**————DCL加双重判断**
代码如下:
public class Mgr05 {
//定义一个INSTANCE,不new
private static volatile Mgr05 INSTANCE;
//构造方法设为私有的,不让别人new
private Mgr05(){}
//给一个getInstance方法,
public static Mgr05 getInstance(){
if(INSTANCE == null){
//对当前类加锁
synchronized(Mgr05.class){
if(INSTANCE == null){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
INSTANCE = new Mgr05();
}
}
}
return INSTANCE;
}
public void m(){
System.out.println("m");
}
public static void main(String[] args) {
for(int i=0;i<100;i++){
new Thread(()->System.out.println(Mgr05.getInstance().hashCode())
).start();
}
}
}
执行过程:eg:第一个线程执行判断是否为空,是;这会儿第二个线程来了,也判断是否为空,也是空;然后上锁;再次判断是否为空,是的;那么第二个线程开始创建对象完了以后释放锁。此时第一个线程得到锁,再次判断是否为空,这会儿线程二已经创建出对象来了,所以此时为false,直接return INSTANCE。
—————————————————————————————
改变:1.INSTANCE必须加上volatile 2.加了双重判断
再引出一个问题:DCL单例为什么要加volatile呢?(美团面试问到)
此时我们要了解一个知识:对象new的过程。
public class T {
int m = 8;
public static void main(String[] args) {
T t = new T();
}
}
555。new对象的过程——汇编码实现
- new #2——申请内存,此时成员变量m的值为0;0为默认值,因为这会儿还没有执行构造方法,所以只是初始值。(成员变量的赋值在构造方法中)
- dup
- invokespecial #3 <T,>——invokespecial (特殊调用)调用init——构造方法,此时成员变量m的值为8.
- astore_1——在栈上的t对象与堆中new出来的T建立关联。
- return
总而言之,当我们new一个对象的时候,它有一个中间状态叫半初始化状态。再了解一个知识;CPU在执行指令的时候会发生指令重排序,重点来了:如果线程1new出来后(此时为半初始化状态),不加volatile的情况下,3和4可能会发生指令重排,意思是先建立关联此时m的值仍为0(t指向了半初始化状态的T对象);那么这时线程2来了,判断是否为空,因为线程1发生了指令重排t指向了半初始化状态的M对象,值为0,所以判断结果不为空,但是值是半初始化状态的0,.这必然导致系统的不安全性,比如:订单人家已经进行到10000单了,这时突然有人拿到了一个半初始化的值变为了0了。那么就完蛋了。
所以DCL的INSTANCE变量必须加上volatile关键字——以此来保证指令不会发生重排序。
——————————————————————————
还有个问题:成员变量和局部变量有没有默认值?使用时为什么局部变量非要赋值以后才可以使用。
答案:成员变量有默认值,可以不赋值,也可通过编译。
局部变量,没有默认值,使用前必须赋值,否则不能通过编译。
eg:局部变量不赋值就使用,编译不通过会报错——The local variable s may not have been initialized(意思就是必须初始化才可以用。)
8种基本数据类型的默认值:
基本类型 默认值
boolean false
char ‘\u0000’ (null)
byte (byte)0
short (short)0
int 0
long 0L
float 0.0f
double 0.0d