参考文章:
(1)https://blog.csdn.net/li295214001/article/details/48135939
(2)http://love-love-l.blog.163.com/blog/static/21078304201001804211901/
(3)https://blog.csdn.net/eff666/article/details/67640648
(4)https://blog.csdn.net/jason0539/article/details/23297037
参考图书:
《软件设计模式与体系结构》
概念:
单例模式是值确保一个类仅有一个唯一的实例,并且提供一个全局的访问点!
(一)懒汉式单例:(又分为线程安全的和线程不安全的)
(1.1) 懒汉式线程不安全的:
public class Sington {
// 私有化构造器
private Sington() {
}
// 提供一个静态的该类对象
private static Sington instance = null;
// 生成该属性的public的方法,以供外界通过这个方法获得该类的实例
public static Sington getInstance() {
if(instance == null) {
//如果当前属性值为null,返回一个新构建的
instance= new Sington();
}
//如果不是那么就不再构建,直接返回已经存在的
return instance;
}
public static void main(String[] args) {
// 创建两个对象
Sington s1 = Sington.getInstance();
Sington s2 = Sington.getInstance();
// 判断是否为同一个Sington对象,如果结果为true,说明则为单例
System.out.println(s1 == s2);
}
}
结果为true!!
这种方式,大家也看到了,并没有对多线程进行考虑,一旦有多个线程同时请求,那就并不能保证单一性!!
使用上面的方式测试多线程:
public class Sington {
// 私有化构造器
private Sington() {
}
// 提供一个静态的该类对象
private static Sington instance = null;
// 生成该属性的public的方法,以供外界通过这个方法获得该类的实例
public static Sington getInstance() {
if(instance == null) {
//如果当前属性值为null,返回一个新构建的
instance= new Sington();
}
//如果不是那么就不再构建,直接返回已经存在的
return instance;
}
public static void main(String[] args) {
// 创建两个对象
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
Sington s1 = Sington.getInstance();
System.out.println(s1);
}
},"t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
Sington s2 = Sington.getInstance();
System.out.println(s2);
}
},"t2");
t1.start();
t2.start();
}
}
两个输出的对象:不一致
s1 | cn.gxm.mode.test.Sington@322c0983 |
s2 | cn.gxm.mode.test.Sington@310b8809 |
(1.2)懒汉式线程安全的(分为三种)
(1.2.1)java为上面出现的情况提供了一个关键字synchronized来处理上面的情况
接下来,是懒汉式单例的(考虑线程安全的方式)
public class Sington {
// 私有化构造器
private Sington() {
}
// 提供一个静态的该类对象
private static Sington instance = null;
//对该方法加入synchronized关键字,为防止多线程的对象创建
public static synchronized Sington getInstance() {
if(instance == null) {
//如果当前属性值为null,返回一个新构建的
instance= new Sington();
}
//如果不是那么就不再构建,直接返回已经存在的
return instance;
}
public static void main(String[] args) {
/**
* 此时创建的两个对象是利用多线程创建的,但是加上synchronized关键字之后,
* 第一个线程在创建的时候,第二个线程是无法进入对象的该方法的!!
*/
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
Sington s1 = Sington.getInstance();
System.out.println(s1);
}
},"t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
Sington s2 = Sington.getInstance();
System.out.println(s2);
}
},"t2");
t1.start();
t2.start();
}
}
结果:
s1 | cn.gxm.mode.test.Sington@322c0983 |
s2 | cn.gxm.mode.test.Sington@322c0983 |
(1.2.2)懒汉式的处理多线程的第二种方式:(双重检查锁)
对于懒汉式的处理多线程的第一种方式,相信大家多看到了,是可以解决多线程的问题,但是,比较的耗费资源,因为每一个线程通过对象调用该对象,都需要排队等待,因为前面有一个线程执行了同步锁!接下来,对上面的方式进行部分的优化,代码我就不贴全部了,只贴改了的部分,其它都一样!
//对该方法加入synchronized关键字,为防止多线程的对象创建
public static Sington getInstance() {
//如果第二个线程也进来,但是当前对象不为空,那么就可以直接拿走
//不需要在等到进入后,得知,不为空再拿走!!
if(instance == null) {
//只是锁住代码块
synchronized(Sington.class) {
//如果当前属性值为null,返回一个新构建的
instance= new Sington();
}
}
//如果不是那么就不再构建,直接返回已经存在的
return instance;
}
(1.2.3)懒汉式的处理多线程的第三种方式:静态内部类
这种方法肯定安全,因为至始至终就一个对象!!
public class Sington {
// 私有化构造器
private Sington() {
}
// 提供一个静态的内部类
private static class staticInsideClass{
private static final Sington instance = new Sington();
}
//通过静态内部类的fina返回对象实例
public static Sington getInstance() {
return staticInsideClass.instance;
}
public static void main(String[] args) {
/**
* 此时创建的两个对象是利用多线程创建的,但是加上synchronized关键字之后,
* 第一个线程在创建的时候,第二个线程是无法进入对象的该方法的!!
*/
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
Sington s1 = Sington.getInstance();
System.out.println(s1);
}
},"t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
Sington s2 = Sington.getInstance();
System.out.println(s2);
}
},"t2");
t1.start();
t2.start();
}
}
结果:一致!
s1 | cn.gxm.mode.test.Sington@14e672aa |
s2 | cn.gxm.mode.test.Sington@14e672aa |
(二)第二种饿汉式单例模式(就是不管你有没有用,我一上来就已经把对象创建好,就那一个对象,用你就拿去!)
public class Sington {
// 私有化构造器
private Sington() {
}
//一开始就在其类的内部创建好,等用了,就给
//至始至终就这一个对象
private final static Sington instace = new Sington();
public static Sington getInstance() {
return Sington.instace;
}
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
Sington s1 = Sington.getInstance();
System.out.println(s1);
}
},"t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
Sington s2 = Sington.getInstance();
System.out.println(s2);
}
},"t2");
t1.start();
t2.start();
}
}
结果:一致!
s1 | cn.gxm.mode.test.Sington@310b8809 |
s2 | cn.gxm.mode.test.Sington@310b8809 |
实际使用举例:
Client:
package cn.gxm.mode.test;
import java.awt.Button;
import java.awt.FlowLayout;
import java.awt.TextArea;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
public class Client extends JFrame implements ActionListener{
private TextArea area = new TextArea("click to get connection");
private Button connectionButton = new Button("create connection");
private Button exitButton = new Button("exit");
public Client() {
super("客户端");
setBounds(200,300, 400, 400);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(new FlowLayout());
connectionButton.addActionListener(this);
exitButton.addActionListener(this);
add(area);
add(connectionButton);
add(exitButton);
setVisible(true);
validate();
}
@Override
public void actionPerformed(ActionEvent e) {
if(e.getSource() == connectionButton) {
LoginService login = LoginService.getInstance();
area.setText(login.getMesssage());
}else if(e.getSource() == exitButton) {
dispose();
}
}
public static void main(String[] args) {
Client c= new Client();
}
}
LoginService:
package cn.gxm.mode.test;
import java.awt.Button;
import java.awt.FlowLayout;
import java.awt.TextArea;
import java.awt.TextField;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
public class LoginService extends JFrame {
private TextField username = new TextField("张三");
private TextField password = new TextField("123456");
private Button loginButton = new Button("Login");
private Button exitButton = new Button("exit");
//测试是否已经在连接了(默认为false)
private static boolean isLogin = false;
//告诉客户端请求链接的信息
public String messsage;
private static LoginService instance = null;
public String getMesssage() {
return messsage;
}
public void setMesssage(String messsage) {
this.messsage = messsage;
}
public static LoginService getInstance() {
if(instance == null) {
synchronized (LoginService.class) {
instance = new LoginService();
}
}
return instance;
}
private LoginService() {
super("请求网路登陆端");
setBounds(200,300, 400, 400);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(new FlowLayout());
loginButton.addActionListener(new MyActionLister());
exitButton.addActionListener(new MyActionLister());
add(loginButton);
add(exitButton);
add(username);
add(password);
setVisible(true);
validate();
}
class MyActionLister implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
if(e.getSource() == loginButton) {
if(isLogin) {
//说明已经有connection了,则告诉客户端错误信息
messsage = "连接已经开启了,请不要重复开启!!";
}else {
isLogin = true;
messsage = "连接开启成功!!";
}
}else if(e.getSource() == exitButton) {
dispose();
//重置isLogin
isLogin = false;
}
}
}
}
应用场景:
1、比如英雄联盟客户端,你只能打开一个
2、数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。