保证了一个类只有一个实例,并且提供了一个全局访问点。单例模式的主要作用是节省公共资源,方便控制,避免多个实例造成的问题。
实现单例模式的三点:
1.静态类:第一次运行初始化,全局使用
2.懒汉模式(线程不安全):懒汉模式是指在第一次获取实例时才创建对象,实现了延迟加载,构造函数返回当前对象实例,但多个访问者同时获取对象实例,就会有多个同样的实例并存,不满足单例
public class LazySingleton {
// 私有化构造器
private LazySingleton() {}
// 定义一个静态变量存储唯一的LazySingleton对象
private static LazySingleton instance = null;
// 提供一个公共的静态方法获取LazySingleton对象
public static LazySingleton getInstance() {
// 如果instance为空,则创建一个新的对象
if (instance == null) {
instance = new LazySingleton();
}
// 返回instance对象
return instance;
}
}
懒汉模式(线程安全):加锁,保证线程安全,但是锁占用,导致资源浪费
3.饿汉模式(线程安全):与第一种方式基本一致,在程序启动时直接运行加载,但是可能造成资源浪费。
public class HungrySingleton {
// 私有化构造器
private HungrySingleton() {}
// 定义一个静态变量存储唯一的HungrySingleton对象,并且直接初始化
private static HungrySingleton instance = new HungrySingleton();
// 提供一个公共的静态方法获取HungrySingleton对象
public static HungrySingleton getInstance() {
// 直接返回instance对象
return instance;
}
}
4.使用类的静态内部类:既保证线程安全,又保证懒汉模式,没有加锁影响性能(推荐)
public class SingletonInnerStatic {
private static class SingletonHoler {
// 静态初始化器,由JVM来保证线程安全
private static SingletonInnerStatic instance =
new SingletonInnerStatic();
}
// 私有构造函数
private SingletonInnerStatic() {
}
// 获取对象实例的方法
public static SingletonInnerStatic getInstance() {
return SingletonHoler.instance;
}
}
5.双重锁校验:
public class SingletonDoubleCheck {
// 类引用
private static volatile SingletonDoubleCheck singletonDoubleCheck;
// 构造函数私有化
private SingletonDoubleCheck() {
}
// 双重校验 + 锁实现单例
public static SingletonDoubleCheck getInstance() {
// 第一次校验是否为null
if (singletonDoubleCheck == null) {
// 不为空则加锁
synchronized (SingletonDoubleCheck.class) {
// 第二次校验是否为null
if (singletonDoubleCheck == null) {
singletonDoubleCheck = new SingletonDoubleCheck();
}
}
}
return singletonDoubleCheck;
}
}
第一次校验是否为null:
主要是为了实现返回单例,避免多余的加锁操作,以及锁的等待和竞争,如果条件不成立就说明已经生成实例,直接返回即可,提高程序执行的效率。
第二次校验是否为null:
第二次校验是关键,这里防止了多线程创建多个实例(一般为两个),这里的特殊情况是这样的:在未创建实例的情况下,A线程和B线程都通过了第一次校验(singletonDoubleCheck
为空),这时如果通过竞争B线程拿到了锁就会执行一次new
操作,生成一个实例,然后B执行完了A就会拿到资源的锁,如果没有第二次判断的话,这时A线程也会执行一次new
操作,这里就出现了第二个类实例,违背了单例原则。所以说两次校验都是必不可少的。
提一下上述代码中类引用中的
volatile
关键字是不能少的: 常见的,该关键字能够实现变量在内存中的可见性(告诉JVM在使用该关键字修饰的变量时在内存中取值,而不是用特定内存区域的副本,因为真实的值可能已经被修改过了),它的另外一种作用是防止JVM对指令进行重排。 其实,在new
一个对象的时候会有如下步骤(指令): 1. 分配内存空间 2. 初始化引用 3. 将引用指向内存空间 正常的逻辑肯定以为是这样执行的 1 -> 2 -> 3,但是偏偏JVM拥有指令重排的能力,所以说执行顺序是随机的,可能是 1 -> 3 -> 2,这样的话在多线程环境下可能会拿到空引用:线程A先执行了1,3步骤,紧接着线程B执行getInstance
,发现不为null(这里的==是判断实际的值,即引用指向的内存空间),就会返回引用,然而此时引用未初始化。所以说volatile
在这里保证指令的执行顺序,在多线程情况下不可少。
6.AtomicReference是一个支持原子操作的对象引用变量,它可以利用CAS(比较并交换)技术来保证线程安全和高效性。一个基本的AtomicReference单例的代码示例如下:
import java.util.concurrent.atomic.AtomicReference;
public class Singleton {
private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<>();
private Singleton() {}
public static Singleton getInstance() {
for (;;) {
Singleton singleton = INSTANCE.get();
if (singleton != null) {
return singleton;
}
singleton = new Singleton();
if (INSTANCE.compareAndSet(null, singleton)) {
return singleton;
}
}
}
}
要使用这个单例,只需调用Singleton.getInstance()
即可。
7.枚举类实现单例模式是一种简洁、安全、有效的方法,它可以防止反射和序列化攻击,保证线程安全和唯一性。一个基本的枚举单例的代码示例如下:
public enum Singleton {
INSTANCE;
public void doSomething() {
// ...
}
}
要使用这个单例,只需调用Singleton.INSTANCE.doSomething()
即可。
有以下几种方法可以测试单例的有效性,即是否能保证在多线程环境下,只有一个对象实例被创建和返回。:
public class SingletonAtack {
public static void main(String[] args) throws Exception {
//正常单例对象
Singleton single1 = Singleton.getInstance();
//1. 反射攻击
Class clazz = Singleton.class;
Constructor cons = clazz.getDeclaredConstructor(null);
cons.setAccessible(true);
Singleton single2 = (Singleton) cons.newInstance(null);
//2. 序列化攻击
FileOutputStream fos = new FileOutputStream("a.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(single1);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("a.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
Singleton single3 = (Singleton) ois.readObject();
//3. clone攻击
Singleton single4 = (Singleton) single1.clone();
}
}
public class Singleton {
private static volatile Singleton instance;
private Singleton() {
// 防止反射攻击
if (instance != null) {
throw new RuntimeException("单例模式不允许多个实例");
}
}
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
public class Singleton implements Serializable {
private static final long serialVersionUID = 1L;
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
// 防止序列化攻击
private Object readResolve() throws ObjectStreamException {
return instance;
}
}
public class Singleton implements Cloneable{
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
// 防止克隆攻击
@Override
protected Object clone() throws CloneNotSupportedException{
return instance;
}
}
public class LogTest {
// 使用LogTest类的名称作为日志对象的标识符
private static final Logger logger = Logger.getLogger(LogTest.class.getName());
public static void main(String[] args) {
// 使用日志对象记录一条信息级别的消息
logger.info("This is a log message");
}
}
public class DriverTest {
public static void main(String[] args) throws SQLException {
// 注册MySQL驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
// 获取MySQL驱动实例
Driver driver = DriverManager.getDriver("jdbc:mysql://localhost:3306/test");
// 使用驱动实例连接数据库
Connection conn = driver.connect("jdbc:mysql://localhost:3306/test", null);
}
}
public class Cache {
// 创建并初始化一个缓存实例作为静态变量
private static final Cache INSTANCE = new Cache();
// 创建一个Map用于存储键值对数据
private Map<String, Object> data;
// 私有化构造器,防止外部创建新的缓存实例
private Cache() {
data = new HashMap<>();
}
// 提供一个公共方法用于获取缓存实例
public static Cache getInstance() {
return INSTANCE;
}
// 提供一个公共方法用于向缓存中添加数据
public void put(String key, Object value) {
data.put(key, value);
}
// 提供一个公共方法用于从缓存中获取数据
public Object get(String key) {
return data.get(key);
}
}
public class ThreadPoolTest {
// 创建并初始化一个固定大小为10的线程池作为静态变量
private static final ExecutorService executor = Executors.newFixedThreadPool(10);
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
// 向线程池提交20个任务,并由线程池分配给空闲线程执行
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " is running");
}
});
}
// 关闭线程池,不再接受新任务,并等待已提交任务完成后退出
executor.shutdown();
}
}
public class RuntimeTest {
public static void main(String[] args) throws IOException{
// 获取单例的Runtime实例
Runtime runtime = Runtime.getRuntime();
// 使用Runtime实例执行一个命令
Process process = runtime.exec("notepad.exe");
}
}
public class DesktopTest {
public static void main(String[] args) throws IOException{
// 获取单例的Desktop实例
Desktop desktop = Desktop.getDesktop();
// 使用Desktop实例打开一个文件
File file = new File("test.txt");
desktop.open(file);
}
}