单例模式(多例,线程安全)

本文深入解析Java中的单例模式,包括饱汉式和饿汉式的实现方式,并通过代码示例说明其原理。同时,文章还介绍了多例模式的实现,控制实例个数为确定值,如3个实例。并通过模拟数据库连接的例子,展示如何在多线程环境下实现线程安全的单例模式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

单例模式的本质是控制实例的个数,例如限制代码中的实例个数为确定的n个,一般情况下为1个。看代码:

class ConnectionManager {
    private static ConnectionManager manager = new ConnectionManager();
    private ConnectionManager() {
    }

    public static ConnectionManager getInstance() {
        return manager;
    }
}

这种属于“饱汉式”的单例模式,不管你什么时候调用,当加载这个类的时候,就初始化了该类的唯一对象。这种写法是线程安全的。在下面的这种情况下,JVM隐含的为您执行的同步。
1 由静态初始化器(在静态字段上或static{}块中的初始化器)初始化数据时
2 访问final字段时

class ConnectionManager {
    private static ConnectionManager manager = null;
    private ConnectionManager() {
    }

    public static ConnectionManager getInstance() {
    if(manager==null){
        manager = new ConnectionManager();
    }
        return manager;
    }
}

这种写法属于“饿汉式”的单例模式,只有当需要的时候才去创建。当然,上述代码不是线程安全的。(既不是静态字段,也不是static{}字段),下面给出线程安全的代码:(用synchronized锁住ConnectionManager,表明一个时刻只能有一个线程访问ConnectionManager这个类 ,volatile修饰manager表明当manager被其他线程修改了,所有的线程都可以看到)

class ConnectionManager {
    private volatile static ConnectionManager manager = null;
    private ConnectionManager() {
    }

    public static ConnectionManager getInstance() {
            if(manager==null){
                synchronized(ConnectionManager.class){
                if(manager==null)
                manager = new ConnectionManager();
            }
        }
        return manager;
    }
}

下面将给出一个多例模式。

package com.guo.task;
/**
 * 显示如何扩展单例模式,控制单例的个数为3个
 * @author guo
 */
public class OneExtend{
    //定义一个缺省的key值的前缀
    private final static String DEFAULT_PREKEY="Cache";
    //定义缓存实例的容器
    private static Map<String,OneExtend> map=new HashMap<String,OneExtend>();
    //用来记录当前正在使用第几个实例,到了控制的最大输入,就返回从1开始
    private static int num=1;
    //定义最大的实例的数目
    private final static int NUM_MAX=3;

    public static OneExtend getInstance(){
        String key=DEFAULT_PREKEY+num;
        OneExtend oneExtend=map.get(key);
        if(oneExtend==null){
            oneExtend =new OneExtend();
            map.put(key,oneExtend);
        }
        //把当前的实例序号加1
        num++;
        if(num>NUM_MAX){
            num=1;
        }
        return oneExtend;
    }
}

当然,上述代码也不是线程安全的。具体的修改方式就留给大家来改吧。下面将给出一段强大的代码。该代码用于模拟数据库连接。比如假设有5个数据库连接,但是同时可能有8个人来获取连接。获取到连接的人,进行自己的业务操作,没获取到连接的人,则等待直到有人释放了连接,然后获取连接,处理自己的业务。

package com.thread;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MultiModle {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        ExecutorService exec = Executors.newCachedThreadPool();

        Runnable runnable = new Runnable() {

            public void run() {
                try {
                    Connection con = ConnectionManager.getInstance()
                            .getConnection();
                    System.out.println(Thread.currentThread().getId()
                            + "获取到连接.." + con);
                    System.out.println("使用连接中..");
                    Thread.sleep(1000);//模拟业务操作
                    ConnectionManager.getInstance().releaseConnection(con);
                    System.out.println(Thread.currentThread().getId()
                            + "使用结束,释放连接" + con);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (NotFoundException e) {
                    e.printStackTrace();
                }

            }
        };
        for (int i = 0; i < 8; i++) {
            exec.execute(runnable);
        }
        exec.shutdown();
    }

}

class NotFoundException extends Exception {
    public NotFoundException() {
        super("the connection is not founded.");
    }
}

class Connection {
    boolean isUsed;
    private int id;

    public Connection(int id) {
        this.id = id;
    }

    public String toString() {
        return id + "号连接";
    }
}

class ConnectionManager {
    private static ConnectionManager manager = new ConnectionManager();
    private int num = 5;
    private List<Connection> list;

    private ConnectionManager() {
        list = new ArrayList<Connection>();
        for (int i = 0; i < num; i++) {
            list.add( new Connection(i));
        }
    }

    public static ConnectionManager getInstance() {
        return manager;
    }

    public synchronized Connection getConnection() throws InterruptedException {
        Connection con = null;
        while (con == null) {
            for (int i = 0; i < num; i++) {
                if (!list.get(i).isUsed) {
                    con = list.get(i);
                    con.isUsed=true;
                    break;
                }
            }
            if (con == null) {
                System.out.println(Thread.currentThread().getId()
                        + "号线程正在等待...");
                wait();
            }
        }
        return con;
    }

    public synchronized void releaseConnection(Connection con)
            throws NotFoundException {
        for (int i = 0; i < num; i++) {
            if (list.get(i) == con) {
                con.isUsed = false;
                notifyAll();
                return;
            }
        }
        throw new NotFoundException();
    }
}

运行结果如下所示:

10获取到连接..0号连接
使用连接中..
13获取到连接..1号连接
使用连接中..
16获取到连接..2号连接
使用连接中..
12获取到连接..4号连接
使用连接中..
11号线程正在等待...
15获取到连接..3号连接
使用连接中..
17号线程正在等待...
14号线程正在等待...
10使用结束,释放连接0号连接
12使用结束,释放连接4号连接
15使用结束,释放连接3号连接
16使用结束,释放连接2号连接
14获取到连接..0号连接
使用连接中..
13使用结束,释放连接1号连接
17获取到连接..1号连接
使用连接中..
11获取到连接..2号连接
使用连接中..
11使用结束,释放连接2号连接
14使用结束,释放连接0号连接
17使用结束,释放连接1号连接
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值