长乐钟声花外尽,龙池柳色雨中深
单例模式一般分为立即加载和延迟加载,通俗的说就是“饿汉模式”和“懒汉模式”。
立即加载/“饿汉模式”:立即加载就是使用类的时候已经将对象创建完毕,常见的实现办法就是new实例化。
测试代码:
public class SingleTest {
//立即加载==恶汉模式
private static SingleTest singleTest = new SingleTest();
public SingleTest() {
}
public static SingleTest getInstance() {
return singleTest;
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println(SingleTest.getInstance().hashCode());
}
}
class Run {
public static void main(String[] args) {
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
MyThread thread3 = new MyThread();
thread1.start();
thread2.start();
thread3.start();
}
}
运行结果:
由此可见,我们实现了立即加载的单例模式。
延迟加载/“懒汉模式” :延迟加载就是在调用get()方法时实例才被创建,常见的办法就是在get()方法中进行new实例化。
测试代码:
public class SingleTest {
private static SingleTest singleTest;
public SingleTest() {
}
public static SingleTest getInstance() {
//延迟加载==懒汉模式
if (singleTest != null) {
}else {
singleTest = new SingleTest();
}
return singleTest;
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println(SingleTest.getInstance().hashCode());
}
}
class Run {
public static void main(String[] args) {
MyThread thread1 = new MyThread();
thread1.start();
}
}
运行结果:
这样虽然是获取到了一个实例,但是这里是有严重问题,我们在该例中只有一个县城,要是多线程的情况下,就会实例出多个对象,继续测试:
public class SingleTest {
private static SingleTest singleTest;
public SingleTest() {
}
public static SingleTest getInstance() {
try {
//延迟加载==懒汉模式
if (singleTest != null) {
}else {
//模拟在创建对象之前做的一些准备工作
Thread.sleep(3000);
singleTest = new SingleTest();
}
} catch (Exception e) {
e.printStackTrace();
}
return singleTest;
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println(SingleTest.getInstance().hashCode());
}
}
class Run {
public static void main(String[] args) {
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
MyThread thread3 = new MyThread();
thread1.start();
thread2.start();
thread3.start();
}
}
运行结果:
出现了多个实例,这里就违反了单例模式的意义。
那么,延迟加载的在多线程的环境下如何才能保证单例呢?首先想到应该是加入synchronized关键字,既然多个线程都要进入getInstance()方法,那么给getInstance()方法加上synchronized关键字。
测试代码:
public class SingleTest {
private static SingleTest singleTest;
public SingleTest() {
}
//就是加了synchronized关键字
synchronized public static SingleTest getInstance() {
try {
//延迟加载==懒汉模式
if (singleTest != null) {
}else {
//模拟在创建对象之前做的一些准备工作
Thread.sleep(3000);
singleTest = new SingleTest();
}
} catch (Exception e) {
e.printStackTrace();
}
return singleTest;
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println(SingleTest.getInstance().hashCode());
}
}
class Run {
public static void main(String[] args) {
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
MyThread thread3 = new MyThread();
thread1.start();
thread2.start();
thread3.start();
}
}
运行结果:
这里虽然是解决了延迟加载多线程下的单例模式,但是想到加了synchronized关键字之后的getInstance() 方法变成同步了,下一个线程要想获取到对象,就必须等上一个线程释放锁之后才行,这样大大降低了效率。如果这时你想只同步某些重要的代码,以此来提高效率,继续测试:
public class SingleTest {
private static SingleTest singleTest;
public SingleTest() {
}
public static SingleTest getInstance() {
try {
//延迟加载==懒汉模式
if (singleTest != null) {
}else {
//模拟在创建对象之前做的一些准备工作
Thread.sleep(3000);
//使用synchronized (SingleTest.class)同步一些代码
synchronized (SingleTest.class) {
singleTest = new SingleTest();
}
}
} catch (Exception e) {
e.printStackTrace();
}
return singleTest;
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println(SingleTest.getInstance().hashCode());
}
}
class Run {
public static void main(String[] args) {
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
MyThread thread3 = new MyThread();
thread1.start();
thread2.start();
thread3.start();
}
}
运行结果:
这样虽然效率提高了,但并非单例了。
难道,延迟加载和多线程就不能较好的实现单例模式了吗?很显然,答案是有的。那就是使用DCL(Double-Check Locking)双检查锁机制。
测试代码:
public class SingleTest {
private volatile static SingleTest singleTest; //加入volatile关键字
public SingleTest() {
}
public static SingleTest getInstance() {
try {
//延迟加载==懒汉模式
if (singleTest != null) {
}else {
//模拟在创建对象之前做的一些准备工作
Thread.sleep(3000);
//使用synchronized (SingleTest.class)同步一些代码
synchronized (SingleTest.class) {
if (singleTest == null) {
singleTest = new SingleTest();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return singleTest;
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println(SingleTest.getInstance().hashCode());
}
}
class Run {
public static void main(String[] args) {
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
MyThread thread3 = new MyThread();
thread1.start();
thread2.start();
thread3.start();
}
}
运行结果:
使用双重检查锁功能,成功的解决了延迟加载的多线程问题。同时,DCL也是大多数多线程结合单例模式使用的解决方案。
当然,也可以使用static代码块来实现单例模式。因为静态代码块在使用类的时候就已经执行了,所以利用这个特性来实现单例模式。
测试代码:
public class SingleTest {
private static SingleTest singleTest = null;
public SingleTest() {
}
static {
singleTest = new SingleTest();
}
public static SingleTest getInstance() {
return singleTest;
}
}
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0 ;i < 3 ; i++) {
System.out.println(SingleTest.getInstance().hashCode());
}
}
}
class Run {
public static void main(String[] args) {
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
MyThread thread3 = new MyThread();
thread1.start();
thread2.start();
thread3.start();
}
}
运行结果:
使用enum枚举数据类型实现单例模式:因为枚举enum和静态代码块的特性相似,在使用枚举是,构造方法会被自动调用,也可以用这个特性来实现单例模式。
测试代码:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class SingleTest {
public enum SingleEnumTest {
connectionFactory;
private Connection connectionn;
private SingleEnumTest() {
try {
System.out.println("调用了SingleTest的构造方法");
String url = "jdbc:mysql://localhost:3306/test?characterEncoding=utf-8";
String username = "root";
String password = "root";
String driverName = "com.mysql.jdbc.Driver";
Class.forName(driverName);
connectionn = DriverManager.getConnection(url, username, password);
} catch (Exception e) {
e.printStackTrace();
}
}
public Connection getConnectionn() {
return connectionn;
}
}
public static Connection getConnection() {
return SingleEnumTest.connectionFactory.getConnectionn();
}
}
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0;i < 5; i++) {
System.out.println(SingleTest.getConnection().hashCode());
}
}
}
class Run {
public static void main(String[] args) {
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
MyThread thread3 = new MyThread();
thread1.start();
thread2.start();
thread3.start();
}
}
这样,也实现了单例模式。