1.单例模式
1.1 线程安全问题条件
- 多线程的环境下
- 必须有共享资源
- 对资源进行非原子性操作
1.2 饿汉式单例模式
/**
饿汉式单例模式-不会出现线程安全模式
*/
public class SingletonHunger {
// 私有化构造方法
private SingletonHunger () {}
//类加载时就产生了instance对象
private static SingletonHunger instance = new SingletonHunger();
public static SingletonHunger getInstance() {
return instance;
}
}
饿汉式单例模式的优缺点:
优点:类加载时产生instance,没有对资源进行费原子性操作,所以不会出现安全问题.
缺点:加载过多无用实例
1.3 懒汉式
//出现了非原子性操作-会有多线程安全问题
public class SingletonLazy1 {
private SingletonLazy1() {
}
private static volatile SingletonLazy1 instance;
//这里出现了非原子性操作-会有多线程问题
public static SingletonLazy1 getInstance() {
if (instance == null) {
instance = new SingletonLazy1();
}
return instance;
}
}
SingletonLazy1_2:更明显的线程安全问题
/**
* 懒汉式1-2-更明显的线程安全问题(sleep了10ms)
*/
public class SingletonLazy1_2 {
private SingletonLazy1_2() {
}
private static volatile SingletonLazy1_2 instance;
//这里出现了非原子性操作-会有多线程问题
public static SingletonLazy1_2 getInstance() {
if (instance == null) {
try {
Thread.sleep(10L);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new SingletonLazy1_2();
}
return instance;
}
}
1.4 懒汉式-同步-synchronized
public class SingletonLazy2 {
private SingletonLazy2() {
}
private static volatile SingletonLazy2 instance;
/**
* 虽然有synchronized具有如下特性:
* 偏向锁(但是针对单线程访问synchronized方法)
* 轻量锁-第二个线程也能进入, 自旋,自旋还消耗cpu资源(相当于while true),还不如wait,wait不消耗cpu资源
* 所以如果synchronized放在方法中,对性能也不会很好
*
* 分析出现线程不同步的场景:只有第一次加载的时候(多线程同时加载)才可能出现线程不同步的情况.
* 优化方式查看SingletonLazy3
*/
public static synchronized SingletonLazy2 getInstance() throws InterruptedException {
if (instance == null) {
Thread.sleep(100);
instance = new SingletonLazy2(); // 指令重排序
}
return instance;
}
}
虽然有synchronized具有如下特性:
偏向锁(但是针对单线程访问synchronized方法)
轻量锁-第二个线程也能进入, 自旋,自旋还消耗cpu资源(相当于while true),还不如wait,wait不消耗cpu资源
所以如果synchronized放在方法中,对性能也不会很好
需要优化:
分析出现线程不同步的场景:只有第一次加载的时候(多线程同时加载判断instance为null的之后,然后各自new instance)才可能出现线程不同步的情况.
优化方式查看SingletonLazy3
1.4 懒汉式-同步-双重检查再加锁
public class SingletonLazy3 {
private SingletonLazy3() {}
//禁止指令重排序
private static volatile SingletonLazy3 instance;
/**
* 分析出现线程不同步的场景:只有第一次加载的时候(多线程同时加载)才可能出现线程不同步的情况.
*
* 从普通synchronized升级到 SingletonLazy2 在升级到 双重锁,在升级到 volatile
*
* 双重检查(两次判断instance == null)再加锁
*/
public static SingletonLazy3 getInstance () {
// 自旋 while(true)
if(instance == null) {
synchronized (SingletonLazy3.class) {
if(instance == null) {
instance = new SingletonLazy3();
/*在双重检查加锁只有,还可能会有指令重排序的问题:
申请一块内存空间 // 1
在这块空间里实例化对象 // 2
instance的引用指向这块空间地址 // 3
以上可能1->2->3,也可能1->3-2,
如果1,3,2的话则会造成判断instance==null的时候,判断结果不为空,但是实际上没有实例化对象.
解决办法就是用 volatile(不会出现指令重排序问题)
*/
}
}
}
return instance;
}
}
1.5 测试类
public class TestSingleton {
public static void main(String[] args) throws Exception{
//1.测试饥汉模式
testSingleHunger();
//2-1.测试懒汉模式-线程安全问题
testSingleLazy1();
//2-2.测试懒汉模式-线程安全问题(sleep后效果明显)
testSingleLazy1_2();
//3.测试懒汉模式-解决线程安全问题(synchronized)
testSingleLazy2();
//4.测试懒汉模式-解决线程安全问题(双重检查再加锁,后volatile)
testSingleLazy3();
}
//1.测试饥汉模式
public static void testSingleHunger(){
SingletonHunger s1 = SingletonHunger.getInstance();
SingletonHunger s2 = SingletonHunger.getInstance();
SingletonHunger s3 = SingletonHunger.getInstance();
SingletonHunger s4 = SingletonHunger.getInstance();
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
System.out.println(s4);
}
//2.测试懒汉模式
/**
* 懒汉式1-1-线程安全问题
*/
public static void testSingleLazy1(){
ExecutorService threadPool = Executors.newFixedThreadPool(20);
for(int i = 0;i<20;i++) {
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println("testSingleLazy1->"+Thread.currentThread().getName() + ":" +SingletonLazy1.getInstance());
}
});
}
threadPool.shutdown();
}
/**
* 懒汉式1-2-更明显的线程安全问题
*/
public static void testSingleLazy1_2(){
ExecutorService threadPool = Executors.newFixedThreadPool(20);
for(int i = 0;i<20;i++) {
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println("testSingleLazy1->"+Thread.currentThread().getName() + ":" +SingletonLazy1_2.getInstance());
}
});
}
threadPool.shutdown();
}
public static void testSingleLazy2() throws Exception{
ExecutorService threadPool = Executors.newFixedThreadPool(20);
for(int i = 0;i<20;i++) {
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
System.out.println("testSingleLazy2->"+Thread.currentThread().getName() + ":" +SingletonLazy2.getInstance());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
threadPool.shutdown();
}
public static void testSingleLazy3() throws Exception{
ExecutorService threadPool = Executors.newFixedThreadPool(20);
for(int i = 0;i<20;i++) {
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println("testSingleLazy3->"+Thread.currentThread().getName() + ":" +SingletonLazy3.getInstance());
}
});
}
threadPool.shutdown();
}
}