饿汉式
public class Demo01 {
private static final Demo01 INSTANCE = new Demo01();
private Demo01(){};
public static Demo01 getInstance(){
return INSTANCE;
}
public static void main(String[] args) {
Demo01 m1 = Demo01.getInstance();
Demo01 m2 = Demo01.getInstance();
System.out.println(m1 == m2);
}
}
单例模式(饿汉式)优点:饿汉式是典型的空间换时间,当类装载的时候就会创建类实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断了,节省了运行时间。
缺点:不管用与不用,类加载时就会完成实例化,会浪费一定的内存空间
改进方法:让对象在使用的时候在进行创建。------> 懒汉式
懒汉式
public class Demo02 {
private static Demo02 INSTANCE ;
private Demo02(){};
public static Demo02 getInstance(){
if(INSTANCE == null){
try{
Thread.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
INSTANCE = new Demo02();
}
return INSTANCE;
}
public static void main(String[] args) {
for(int i = 0 ; i < 100 ; i++){
new Thread(()->
System.out.println(Demo02.getInstance().hashCode())
).start();
}
}
}
单例模式(懒汉式)优点:懒汉式是典型的时间换空间,也就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,那就不会创建实例,则节约内存空间。
缺点:懒汉式在多个线程进行访问时有可能会出现多个不同的对象。
改进方法:对创建方法getInstance加锁 ------> 懒汉式(加锁synchronized)
懒汉式(加锁synchronized)
public class Demo03 {
private static Demo03 INSTANCE ;
private Demo03(){};
public static synchronized Demo03 getInstance(){
if(INSTANCE == null){
try{
Thread.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
INSTANCE = new Demo03();
}
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(Demo03.getInstance().hashCode())
).start();
}
}
}
单例模式(懒汉式(加锁))优点:懒汉式(加锁)可以保证线程的安全性,但是当上锁的方法getInstance中存在业务逻辑代码时,会拉低整个对象创建过程中速度。
缺点:对整个方法加锁,降低了方法运行的时间
改进方法:对创建方法的程序块进行上锁,业务逻辑代码部分不上锁 -------->懒汉式(部分加锁synchronized)
懒汉式(部分加锁synchronized)
public class Demo04 {
private static Demo04 INSTANCE ;
private Demo04(){};
public static Demo04 getInstance(){
if(INSTANCE == null){
synchronized (Demo04.class){
try{
Thread.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
INSTANCE = new Demo04();
}
}
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(Demo04.getInstance().hashCode())
).start();
}
}
}
单例模式(部分加锁懒汉式)优点:加快了程序的运行,只对创建对象的部分进行加锁
缺点:通过if判断后会有多个线程在等待线程资源,等第一个线程执行完成后还会进行第二个线程创建对象。
改进方法:加入两层if判断可以防止该问题出现 -------->双层检查锁
懒汉式(DCL)
public class Demo04 {
private static Demo04 INSTANCE ;
private Demo04(){};
public static Demo04 getInstance(){
if(INSTANCE == null){
synchronized (Demo04.class){
if(INSTANCE == null){
try{
Thread.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
}
INSTANCE = new Demo04();
}
}
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(Demo04.getInstance().hashCode())
).start();
}
}
}
单例模式(懒汉式DCL)优点:加快了对象创建的时间,同时保证了线程的安全性。
缺点:当对象发生指令重排时,第二个线程虽然拿到了对象,但是是拿到的不完整的对象,容易出现问题
改进方法:给该方法加上volatile关键字进行上锁可以防止指令重排问题。
延伸一下:为什么要用两层if判断呢? 答:因为使用两层if可以提高方法的运行速度,因为if判断消耗的时间较少,但是synchronized 消耗的时间却很大。在外面加上一层if,可以帮助过滤掉很多线程访问。
懒汉式(DCL)最终版
public class Demo04 {
private static volatile Demo04 INSTANCE ;
private Demo04(){};
public static Demo04 getInstance(){
if(INSTANCE == null){
synchronized (Demo04.class){
if(INSTANCE == null){
try{
Thread.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
}
INSTANCE = new Demo04();
}
}
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(Demo04.getInstance().hashCode())
).start();
}
}
}
对 INSTANCE 进行上锁可以防止指令重排,保证对象的完整性。 延伸:DCL模式为什么要加上volatile ? 答:我们要从java对象创建过程和CPU乱序执行两个方面考虑。
java对象创建过程可分为:
1:内存中分配空间
2:初始化对象
3:变量与对象关联
当发生指令重排是顺序变为
1:内存中分配空间
3:变量与对象关联
2:初始化对象
当第一个线程访问时,发生指令重排,对象刚创建一半,还未对对象内部的值进行初始化赋值。此时第二个线程进行访问,此时他读取到的就是创建到一半的对象,初始化为空的对象。最终就会导致对象不完整。
静态内部类
加载外部类时不会加载内部类,只有第一次调用getInstance方法时,JVM才加载 Singleton04Holder 并初始化INSTANCE ,只有一个线程可以获得对象的初始化锁,其他线程无法进行初始化,保证对象的唯一性。
public class Demo04 {
private Demo04 () {
}
private static class Demo04Holder {
private final static Demo04 INSTANCE = new Demo04 ();
}
public static Demo04 getInstance() {
return Demo04Holder.INSTANCE;
}
public static void main(String[] args) {
for(int i=0; i<100; i++) {
new Thread(()->{
System.out.println(Demo04.getInstance().hashCode());
}).start();
}
}
}
|