知识源于:Multithreading and Architecture《Java Concurrency Programming》;Freeman , E.《Head First Java》
本文有借用其他博文观点的部分,贴在具体内容块里。
为什么要用单例模式?
避免程序行为异常,资源使用过量,或者结果不一致。
下面列举了单例的实现,标记可用的才是线程安全的。
懒汉式 ——利用限定与加锁
package designpattern.singleton;
/**
*
* JavaBasic
* @classname LazySingleton
* @description 懒汉式单例
* @author Q
* @date 2018年11月12日 下午4:54:47
*/
public class LazySingleton {
private static LazySingleton uniqueInstance;
private LazySingleton() {}
//线程不安全的写法:
/*public static LazySingleton getInstance() {
if(uniqueInstance == null) {
//如果线程1运行到这,判断后创建前,线程2去判断依然是null
uniqueInstance = new LazySingleton();
}
return uniqueInstance;
}*/
//同步方法 —— 保证同时只能有一个线程进入该方法
/*public synchronized static LazySingleton getInstance() {
if(uniqueInstance == null) {
//如果线程1运行到这,判断后创建前,线程2去判断依然是null
uniqueInstance = new LazySingleton();
}
return uniqueInstance;
}*/
//同步代码块 —— 只同步if内的部分,还是会产生多个实例
public static LazySingleton getInstance() {
if(uniqueInstance == null) {
synchronized(LazySingleton.class) {
uniqueInstance = new LazySingleton();
}
}
return uniqueInstance;
}
}
双重检查 —— 在懒汉基础上,错开两次判断if的时机(可用)
package designpattern.singleton;
public class DoubleCheckLockingSingleton {
//volatile 保证内存可见性
private volatile static DoubleCheckLockingSingleton uniqueInstance;
private DoubleCheckLockingSingleton() {}
public static DoubleCheckLockingSingleton getInstance(){
//因为volatile夺锁的只会有线程1和2
if(uniqueInstance == null) {
synchronized(DoubleCheckLockingSingleton.class){
if(uniqueInstance == null) {
//只有第一个线程会执行此处代码。
uniqueInstance = new DoubleCheckLockingSingleton();
}
}
}
return uniqueInstance;
}
}
Java内存模型——JMM(Java Memory Model)
每个线程有自己的工作内存,线程只能对自己的工作内存变量副本进行操作(只对自己的写,优先读自己的)。不同线程间不能直接访问对方的工作内存,线程间变量传递都需要主内存完成。
什么是内存可见性?
如果线程2读内存时,线程1对变量的修改还没来得及写,也就是说普通的共享变量写入内存的时机是不确定的,那么就会导致不可见
CPU为了提高处理性能,并不直接和内存进行通信,而是将内存的数据读取到内部缓存(L1,L2)再进行操作,但操作完并不能确定何时写回到内存
synchronized 关键字
控制代码段互斥的被执行,阻塞性质。可以防止多个线程执行同一个对象锁住的同步代码段。
synchoronized可应用的位置?
非static方法
static方法
synchronized(对象)
什么是monitor,同步具体怎么实现?
监视器monitor,每个对象都拥有一个。
synchoronized通过成对的()monitorenter和monitorexit,来保证一个monitor的lock锁同一时间只被一个线程获得。
monitor有一个进入数,为0表示没有线程进入,此时进入的线程为持有者,且对进入数当>0时
volatile 关键字
参考:https://www.jianshu.com/p/7798161d7472;https://www.jianshu.com/p/195ae7c77afe;
volatile如何实现可见性?
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值(JMM通过将工作内存的设为失效,以让其直接去读内存)。
volatile变量的内存可见性是基于内存屏障实现的
什么是内存屏障(Memory Barrier)?
内存屏障保护这句指令不会被重排序,在汇编中,操作volatile的这句指令会多处一个lock前缀指令。
这个lock前缀指令相当于:
1、将当前CPU缓存中的数据写回到主内存;
2、这个写回内存的操作会导致在其它CPU里缓存了该内存地址的数据无效。(其他CPU嗅探出的)
经典的非原子性操作——i++
i++操作可被分解成三步原子操作,每一步之间都可能被打断
- 从内存中取当前i值
- 计算 i + 1
- 将新的i值写回到内存
volatile常用在哪里?
- 状态标记量:业务逻辑的开关
- double-check Singleton
饿汉式 —— 利用JVM的类加载机制
package designpattern.singleton;
/**
* JavaBasic
* @classname EagerSingleton
* @description 饿汉式单例 —— 在调用前就创建好了实例(线程安全,可能浪费)
* @author Q
* @date 2018年11月12日 下午4:48:14
*/
public class EagerSingleton {
/*静态常量
private static final EagerSingleton uniqueInstance = new EagerSingleton();
*/
//静态代码块
private static EagerSingleton uniqueInstance;
static {
uniqueInstance = new EagerSingleton();
}
private EagerSingleton() {}
public static EagerSingleton getInstance() {
return uniqueInstance;
}
}
IODH(Initialization on Demand Holder)静态内部类——推荐⭐
package designpattern.singleton;
public class IODHSingleton {
private IODHSingleton() {}
//静态内部类
private static class SingletonInstance {
//私有静态常量实例
private static final IODHSingleton uniqueInstance = new IODHSingleton();
//这里对外层的IODHSingleton首次创建实例,此时IODHSingleton初始化
//对于内部类SingletonInstance来说,这是静态常量,应在其准备阶段初始化
}
public static IODHSingleton getInstance() {
//调用内部类的静态字段,此时内部类初始化
return SingletonInstance.uniqueInstance;
}
}
JVM类加载机制
ClassLoader 是一个抽象的class,加载class文件,并且在JVM中的庚哥对应内存分区中生成各数据结构。
JVM内存模型
类加载机制分为五个步骤?
类的加载过程分为五个阶段:加载 → 连接(验证 →准备 → 解析) → 初始化
加载:将 .class中的二进制数据读取到内存,将其字节流代表的静态存储结构转化为方法区中的运行时数据结构,并且在堆内存中生成该类的 java.lang.Class 对象,作为访问方法区数据结构的入口
加载过程在JVM外部,实现加载的代码成为类加载器。HotSopt VM采用双亲委派模型。比如Object,无论用哪个类加载器,加载任务都传递到启动类加载器(这个类的最顶层的加载器)加载,保证在各加载器环境中都是同一个类。
验证:对格式、元数据、字节码和符号引用验证
准备:为类变量(static修饰)在方法区中分配内存,并赋默认值
解析:将常量池的符号引用替换为直接引用
初始化:为类静态变量赋代码给定的初始值
类在什么时候初始化?
类在Java代码中首次主动使用的时候(这个只是理论上,不排除JVM在运行期间提前预判)
-
首次创建某个类的新实例:
new、反射[obj.getclass();/Object.class;Class.forName("java.lang.Object")]、克隆[https://www.jianshu.com/p/231a2008e91f]、反序列化[https://www.jianshu.com/p/551d6b9ae6c8]
首次调用某个类的静态方法时
首次使用某个类或接口的静态字段
首次调用Java的某些反射方法
首次初始化子类会导致父类初始化(通过子类使用父类的静态变量 static只会使父类初始化)
在虚拟机启动时,含有main()的那个启动类
什么时候不会类不会加载和初始化?
构造某个类的数组
-
引用某个类的静态常量static final
常量final为什么一定要写static?
因为static修饰后是类成员而不是变量成员,不会出现随多个对象拷贝多份的情况。
谢谢观看,如果有用麻烦点个喜欢,对我是莫大的鼓励。