单例模式
饿汉式
public class SingleTon1 implements Serializable {
private static final long serialVersionUID = -3865630706729115005L;
//new一个私有化对象
private static SingleTon1 singleTon1=new SingleTon1();
//私有化构造
private SingleTon1(){
}
//提供外界调用方法
public static SingleTon1 getInstance(){
return singleTon1;
}
}
懒汉式-线程不安全
public class SingleTon2 {
private volatile static SingleTon2 singleTon2 = null;
private SingleTon2() {
}
public static SingleTon2 getInstance() {
if (singleTon2 == null) {
synchronized (SingleTon2.class) {
if (singleTon2 == null) {
singleTon2 = new SingleTon2();
}
}
}
return singleTon2;
}
}
Double check 机制,提高代码性能,避免每个线程都拿到锁
volatile 关键字
1、保证线程可见性
public class Demo3 {
static class MyTest {
volatile public int number = 0;
public void changeNumber(){
number = 100;
}
}
public static void main(String[] args) {
//主线程
final MyTest myTest = new MyTest();
//子线程
new Thread(new Runnable() {
public void run() {
System.out.println(String.format("线程%s开始执行", Thread.currentThread().getName()));
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
myTest.changeNumber();
System.out.println(String.format("线程%s的number:%d", Thread.currentThread().getName(), myTest.number));
}
}).start();
while (myTest.number == 0){
}
System.out.println("执行完毕");
}
}
属性不加volatile修饰,副本线程number改变,主线程不知道,while判断进入死循环
2、不保证原子性
singleTon2 = new SingleTon2();
反编译后可知:new命令分为三步: 1、分内存 2、初始化 3、存入
1、分配对象内存
2、调用构造器方法,执行初始化方法
3、将对象引用赋值给变量
多线程时存在线程安全问题
解决方法
方法一:使用 synchronized 关键字
//给函数增加synchronized修饰,相当于加锁了
public synchronized void incr(){
number++;
}
方法二:使用AtomicInteger
public class Demo8 {
static class MyTest {
public volatile AtomicInteger number = new AtomicInteger();
public void incr(){
number.getAndIncrement();
}
}
public static void main(String[] args) {
MyTest myTest = new MyTest();
for (int i = 1; i <= 10; i++){
new Thread(() -> {
for (int j = 1; j <= 1000; j++){
myTest.incr();
}
}, "Thread"+String.valueOf(i)).start();
}
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
//等线程执行结束了,输出number值
System.out.println("当前number:" + myTest.number);
}
}
3、禁止指令重排
指令重排不影响最后结果但是会自行优化,导致顺序变动
指令重排不影响单线程结果,但在多线程中存在问题
例如:
双重检查锁的代码中,如果线程一获取锁进入到创建实例,此时发生指令重排,线程一执行到t3,线程二刚好进入,由于此时对象已经不为空,所以线程二可以自由访问对象,然该对象还未初始化,所以线程二访问就出现了问题。
静态内部类
public class SingleTon3 implements Serializable {
private static final long serialVersionUID = -7317901076499532518L;
/**
* 静态内部类
*/
public static class MySingletonHandler {
private static SingleTon3 instance = new SingleTon3();
}
private SingleTon3() {
}
public static SingleTon3 getInstance() {
return MySingletonHandler.instance;
}
}
静态代码块
public class SingleTon4 {
private static SingleTon4 instance=null;
private SingleTon4(){
}
/**
* 静态代码块
*/
static {
instance=new SingleTon4();
}
public static SingleTon4 getInstance(){
return instance;
}
}
枚举
public enum Singleton5 implements Serializable {
INSTANCE; //对象
public void doSomething() {
System.out.println("doSomething");
}
}
细节:前四种单例模式,在序列化与反序列化中存在问题。对象不唯一
只有枚举的单例模式,不存在任何问题
别说差点,差点就是永远
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理