package com.second.app.thread.singleton;
/**
* @author gyz
* @date 2021/2/5 9:57
*/
public class HungryInstance {
private static final HungryInstance hungryInstance = new HungryInstance();
private HungryInstance() {
}
public static HungryInstance getInstance() {
return hungryInstance;
}
}
多线程测试一下:
package com.second.app.thread.singleton;
/**
* @author gyz
* @date 2021/2/5 10:10
*/
public class Main {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(HungryInstance.getInstance());
}
}, "线程A").start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(HungryInstance.getInstance());
}
}, "线程B").start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(HungryInstance.getInstance());
}
}, "线程C").start();
}
}
懒汉模式不是立即加载,而是采用延时加载的方法,直接上代码。
package com.second.app.thread.singleton;
/**多线程场景下--不安全
- @author gyz
- @date 2021/2/5 10:18
*/
public class LazyInstance {
private static LazyInstance lazyInstance = null;
private LazyInstance() {
}
public static LazyInstance getLazyInstance() {
if (lazyInstance == null) {
try {
Thread.sleep(300);
lazyInstance = new LazyInstance();
} catch (InterruptedException exception) {
exception.printStackTrace();
}
}
return lazyInstance;
}
}
多线程测试一下:由结果可见,三个线程尝试获取这个单例,但是创建了三个对象,很明显是不对的。
为什么会造成这种情况?
所以怎么保证线程饿汉模式下线程的安全?
package com.second.app.thread.singleton;
/**
- @Author soul yzg
- @Date 2021/2/6 17:42
- 努力学习 天天进步
*/
public class HungryInstance {
private static HungryInstance instance = null;
private HungryInstance() {
}
public static synchronized HungryInstance getInstance() {
try {
if (instance == null) {
Thread.sleep(600);
instance = new HungryInstance();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return instance;
}
}
看起来没什么问题,线程同步了,率先执行的线程尝试new一个对象,释放同步锁,后续线程拿到同步锁,进行非空判断。此时对象已经存在。所以满足全局唯一实例。
这边尝试用同步代码块来解决试试:
package com.second.app.thread.singleton;
/**
* @Author soul yzg
* @Date 2021/2/6 17:42
* 努力学习 天天进步
*/
public class HungryInstance {
private static HungryInstance instance = null;
private HungryInstance() {
}
public static HungryInstance getInstance() {
synchronized (HungryInstance.class) {
if (instance == null) {
instance = new HungryInstance();
}
}
return instance;
}
}
当然肯定还有更好的做法,接着往下写。
package com.second.app.thread.singleton;
/**
* @Author soul yzg
* @Date 2021/2/6 17:42
* 努力学习 天天进步
*/
public class HungryInstance {
private static volatile HungryInstance instance ;
private HungryInstance() {
}
/**
* 双重校验
* @return
*/
public static HungryInstance getInstance() {
//第一个校验,会先判断当前是堆内存中否存在当前对象,如果不存在,才会进入判断内,
//此时并发条件下,可能会存在多个线程进入此方法体,所以这边加入一个同步方法块,只允许同一时刻只有 一个线程进入同步代码块,
//进入方法块中的时候,会在进行第二层判断,首先如果没有第二层判断会怎样?
//假设没有第二层判断,如果第一个线程创建实例后释放锁,下一个线程占有锁,会再一次创建一个实例对象。因为你不能保证同一个时刻只有一个线程进入
//if(instance == null)判断,这时候如果有多个线程,那么就会存在这样的情况。
//所以这边为什么会加上第二层判断,
if (instance == null) {
synchronized (HungryInstance.class) {
if (instance == null) {
instance = new HungryInstance();
}
}
}
return instance;
}
}
至于为什么加volatile关键字,请参考我的另外一篇博客。
volatile关键字浅谈
package com.second.app.thread.singleton;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
/**
* @Author soul yzg
* @Date 2021/2/8 8:12
* 努力学习 天天进步
*/
public enum MyObject {
/**
*
*/
connectionFactory;
private Connection connection;
private MyObject() {
try {
System.out.println("调用MyObject的构造");
String url = "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT";
String username = "root";
String password = "123456";
String driverName = "com.mysql.cj.jdbc.Driver";
Class.forName(driverName);
connection = DriverManager.getConnection(url, username, password);
System.out.println("连接创建成功");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
public Connection getConnection() {
return connection;
}
}
package com.second.app.thread.singleton;
/**
* @Author soul yzg
* @Date 2021/2/8 8:19
* 努力学习 天天进步
*/
public class Run {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(MyObject.connectionFactory.getConnection().hashCode());
}
}, "线程a").start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(MyObject.connectionFactory.getConnection().hashCode());
}
}, "线程b").start();
}
}
package com.second.app.thread.singleton;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
/**
* @Author soul yzg
* @Date 2021/2/8 8:35
* 努力学习 天天进步
*/
public class MyObject1 {
public enum MyEnumSingleton {
/**
*
*/
connectionFactory;
private Connection connection;
private MyEnumSingleton() {
try {
System.out.println("调用MyObject的构造");
String url = "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT";
String username = "root";
String password = "123456";
String driverName = "com.mysql.cj.jdbc.Driver";
Class.forName(driverName);
connection = DriverManager.getConnection(url, username, password);
System.out.println("连接创建成功");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
public Connection getConnection() {
return connection;
}
}
public static Connection getConnection() {
return MyEnumSingleton.connectionFactory.getConnection();
}
}