单例模式
单例模式
饿汉式
public class SingleTonCrazy {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
Thread t = new MyThread();
t.start();
}
}
}
class SingleTon {
// 先将构造方法私有化,外部不能定义
private SingleTon() {}
// 成员变量
private static final SingleTon instance = new SingleTon();
public static SingleTon getInstance() {
return instance;
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println(SingleTon.getInstance().hashCode());
}
}
创建了100个线程,来测试打印出来的hashcode,结果是一样的。

懒汉式
饿汉式单例模式存在一个浪费空间的问题,如果这个对象一直不使用,那么就会一直在堆中存放。
下面的懒汉式就应运而生了
public class SingleTonCrazy {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
Thread t = new MyThread();
t.start();
}
}
}
class SingleTon {
// 懒汉式
private SingleTon(){};
private static SingleTon instance = null;
public static SingleTon getInstance() {
if (instance == null) {
instance = new SingleTon();
}
return instance;
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println(SingleTon.getInstance().hashCode());
}
}
创建了一百个线程,打印对象的hashcode值,发现有不一样的,说明这种懒汉式存在线程安全问题,有可能会有多个线程在同一时间进入
instacne == null这个判断逻辑中,那么不同的线程就会创建不同的对象。

改进后的懒汉式
怎么解决呢?加锁
public class SingleTonCrazy {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
Thread t = new MyThread();
t.start();
}
}
}
class SingleTon {
// 懒汉式
private SingleTon(){};
private static SingleTon instance = null;
public static synchronized SingleTon getInstance() {
if (instance == null) {
instance = new SingleTon();
}
return instance;
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println(SingleTon.getInstance().hashCode());
}
}
给getInstance()这个方法加了一把锁,所有的线程来都是串行化的来,所以最后得到的结果肯定都是一样的,但是这样的效率比较低,每一个线程创建单例对象都要等待锁的释放。

减小锁粒度
给整个方法加锁的话,粒度会很大,考虑减小锁粒度
public class SingleTonCrazy {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
Thread t = new MyThread();
t.start();
}
}
}
class SingleTon {
// 懒汉式
private SingleTon(){};
private static SingleTon instance = null;
public static SingleTon getInstance() {
/**
* 在这可以执行其他逻辑
*/
synchronized (SingleTon.class) {
if (instance == null) {
instance = new SingleTon();
}
return instance;
}
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println(SingleTon.getInstance().hashCode());
}
}
最后得到的结果,hashcode的值都是一样的

再次改进的懒汉式(双重校验锁Double Check Lock)
Double Check Lock(DCL)
在下边这个代码里边,就算已经有一个线程创建了单例,那么其他的线程还是要依次拿到锁,进入synchronized代码内部,拿到单例,这样损耗性能。
public static SingleTon getInstance() {
/**
* 在这可以执行其他逻辑
*/
synchronized (SingleTon.class) {
if (instance == null) {
instance = new SingleTon();
}
return instance;
}
}
考虑在synchronized外部再判断依次为不为null,如果不是空了直接返回instance,为空的话,放一个线程进来,创建单例,这样在单例创建好之后,多线程可以并发的拿到对象
public class SingleTonCrazy {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
Thread t = new MyThread();
t.start();
}
}
}
class SingleTon {
// 懒汉式
private SingleTon(){};
private static SingleTon instance = null;
public static SingleTon getInstance() {
/**
* 在这可以执行其他逻辑
*/
if (instance == null) {
synchronized (SingleTon.class) {
if (instance == null) {
instance = new SingleTon();
}
}
}
return instance;
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println(SingleTon.getInstance().hashCode());
}
}
测试结果如下

指令重排问题
public class Test {
public static void main(String[] args) {
Test t = new Test();
}
}
创建对象时候的指令

- new 等同于为对象分配空间(Java出于安全考虑会给成员变量赋上默认值,不会有遗留变量的问题)
- invokespecial初始化方法,会为成员变量赋上初始值
- astore_1会将变量和实际地址建立联系
如果发生了指令重排,执行顺序变成以下:
- new
- astore_1
- invokespecial
也就是说先建立了联系,那么变量中就有了对象的实际地址,就不为空了,再上述代码中,就会直接返回没有初始化的对象。
所以最终得到的单例可能是不一样的。
所以要解决指令重排问题volatile强制不指令重排。
class SingleTon {
// 懒汉式
private SingleTon(){};
private static volatile SingleTon instance = null;
public static SingleTon getInstance() {
/**
* 在这可以执行其他逻辑
*/
if (instance == null) {
synchronized (SingleTon.class) {
if (instance == null) {
instance = new SingleTon();
}
}
}
return instance;
}
}

浙公网安备 33010602011771号