java多线程之线程安全的单例模式
概念:
java中单例模式是一种常见的设计模式,单例模式分三种:懒汉式单例、饿汉式单例、登记式单例三种。
单例模式有一下特点:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。
这里主要详细介绍两种:懒汉式和饿汉式
一、立即加载/饿汉式
在调用方法前,实例就已经被创建,代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
package
com.weishiyao.learn.day8.singleton.ep1;
public
class
MyObject {
// 立即加载方式==恶汉模式
private
static
MyObject myObject =
new
MyObject();
private
MyObject() {
}
public
static
MyObject getInstance() {
// 此代码版本为立即加载
// 此版本代码的缺点是不能有其他实例变量
// 因为getInstance()方法没有同步
// 所以有可能出现非线程安全的问题
return
myObject;
}
}
|
创建线程类
1
2
3
4
5
6
7
8
|
package
com.weishiyao.learn.day8.singleton.ep1;
public
class
MyThread
extends
Thread {
@Override
public
void
run() {
System.out.println(MyObject.getInstance().hashCode());
}
}
|
创建运行类
1
2
3
4
5
6
7
8
9
10
11
12
|
package
com.weishiyao.learn.day8.singleton.ep1;
public
class
Run {
public
static
void
main(String[] args) {
MyThread t1 =
new
MyThread();
MyThread t2 =
new
MyThread();
MyThread t3 =
new
MyThread();
t1.start();
t2.start();
t3.start();
}
}
|
运行结果
167772895
167772895
167772895
hashCode是同一个值,说明对象也是同一个,说明实现了立即加载型的单利模式
二、延迟加载/懒汉式
在调用方法以后实例才会被创建,实现方案可以是将实例化放到无参构造函数当中,这样只有当调用的时候才会创建对象的实例,代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
package
com.weishiyao.learn.day8.singleton.ep2;
public
class
MyObject {
private
static
MyObject myObject;
private
MyObject() {
}
public
static
MyObject getInstance() {
// 延迟加载
if
(myObject !=
null
) {
}
else
{
myObject =
new
MyObject();
}
return
myObject;
}
}
|
创建线程类
1
2
3
4
5
6
7
8
|
package
com.weishiyao.learn.day8.singleton.ep2;
public
class
MyThread
extends
Thread {
@Override
public
void
run() {
System.out.println(MyObject.getInstance().hashCode());
}
}
|
创建运行类
1
2
3
4
5
6
7
8
|
package
com.weishiyao.learn.day8.singleton.ep2;
public
class
Run {
public
static
void
main(String[] args) {
MyThread t1 =
new
MyThread();
t1.start();
}
}
|
运行结果
167772895
这样虽然取出了一个对象的实例,但是如果在多线程的环境中,就会出现多个实例的情况,这样就不是单例模式了
运行测试类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package
com.weishiyao.learn.day8.singleton.ep2;
public
class
Run {
public
static
void
main(String[] args) {
MyThread t1 =
new
MyThread();
MyThread t2 =
new
MyThread();
MyThread t3 =
new
MyThread();
MyThread t4 =
new
MyThread();
MyThread t5 =
new
MyThread();
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
|
运行结果
980258163
1224717057
1851889404
188820504
1672864109
既然出现问题,就要解决问题,在懒汉模式中的多线程的解决方案,代码:
第一种方案,最常见的,加synchronized,而synchronized可以加到不同的位置
第一种,方法锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
package
com.weishiyao.learn.day8.singleton.ep3;
public
class
MyObject {
private
static
MyObject myObject;
private
MyObject() {
}
synchronized
public
static
MyObject getInstance() {
// 延迟加载
try
{
if
(myObject !=
null
) {
}
else
{
// 模拟在创建对象之前做一些准备性的工作
Thread.sleep(
2000
);
myObject =
new
MyObject();
}
}
catch
(InterruptedException e) {
e.printStackTrace();
}
return
myObject;
}
}
|
这种synchronized的同步方案导致效率过于低下,整个方法都被锁住
第二种synchronized使用方案
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
package
com.weishiyao.learn.day8.singleton.ep3;
public
class
MyObject {
private
static
MyObject myObject;
private
MyObject() {
}
public
static
MyObject getInstance() {
// 延迟加载
try
{
synchronized
(MyObject.
class
) {
if
(myObject !=
null
) {
}
else
{
// 模拟在创建对象之前做一些准备性的工作
Thread.sleep(
2000
);
myObject =
new
MyObject();
}
}
}
catch
(InterruptedException e) {
e.printStackTrace();
}
return
myObject;
}
}
|
这种方法效率一样很低,方法内的所有代码都被锁住,只需要锁住关键代码就好,第三种synchronized使用方案
package com.weishiyao.learn.day8.singleton.ep3;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
public
class
MyObject {
private
static
MyObject myObject;
private
MyObject() {
}
public
static
MyObject getInstance() {
// 延迟加载
try
{
if
(myObject !=
null
) {
}
else
{
// 模拟在创建对象之前做一些准备性的工作
Thread.sleep(
2000
);
synchronized
(MyObject.
class
) {
myObject =
new
MyObject();
}
}
}
catch
(InterruptedException e) {
e.printStackTrace();
}
return
myObject;
}
}
|
这么写看似是最优方案了,但是,运行一下结果,发现,其实它是非线程安全的
结果:
1224717057
971173439
1851889404
1224717057
1672864109
Why?
虽然锁住了对象创建的语句,每次只能有一个线程完成创建,但是,当第一个线程进来创建完成Object对象以后,第二个线程进来还是可以继续创建的,因为我们紧紧只锁住了创建语句,这个问题解决方案
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
package
com.weishiyao.learn.day8.singleton.ep3;
public
class
MyObject {
private
static
MyObject myObject;
private
MyObject() {
}
public
static
MyObject getInstance() {
// 延迟加载
try
{
if
(myObject !=
null
) {
}
else
{
// 模拟在创建对象之前做一些准备性的工作
Thread.sleep(
2000
);
synchronized
(MyObject.
class
) {
if
(myObject ==
null
) {
myObject =
new
MyObject();
}
}
}
}
catch
(InterruptedException e) {
e.printStackTrace();
}
return
myObject;
}
}
|
只需要在锁里面再添加一个判断,就可以保证单例了,这个是DCL双检查机制
结果如下:
1224717057
1224717057
1224717057
1224717057
1224717057
三、使用内置静态类实现单例
主要代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
package
com.weishiyao.learn.day8.singleton.ep4;
public
class
MyObject {
// 内部类方式
private
static
class
MyObjectHandler {
private
static
MyObject myObject =
new
MyObject();
}
public
MyObject() {
}
public
static
MyObject getInstance() {
return
MyObjectHandler.myObject;
}
}
|
线程类代码
1
2
3
4
5
6
7
8
|
package
com.weishiyao.learn.day8.singleton.ep4;
public
class
MyThread
extends
Thread {
@Override
public
void
run() {
System.out.println(MyObject.getInstance().hashCode());
}
}
|
运行类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package
com.weishiyao.learn.day8.singleton.ep4;
public
class
Run {
public
static
void
main(String[] args) {
MyThread t1 =
new
MyThread();
MyThread t2 =
new
MyThread();
MyThread t3 =
new
MyThread();
MyThread t4 =
new
MyThread();
MyThread t5 =
new
MyThread();
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
|
结果
1851889404
1851889404
1851889404
1851889404
1851889404
通过内部静态类,得到了线程安全的单例模式
四、序列化和反序列化单例模式
内置静态类可以达到线程安全的问题,但如果遇到序列化对象时,使用默认方式得到的结果还是多例的
MyObject代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
package
com.weishiyao.learn.day8.singleton.ep5;
import
java.io.Serializable;
public
class
MyObject
implements
Serializable {
/**
*
*/
private
static
final
long
serialVersionUID = 888L;
// 内部类方式
private
static
class
MyObjectHandler {
private
static
MyObject myObject =
new
MyObject();
}
public
MyObject() {
}
public
static
MyObject getInstance() {
return
MyObjectHandler.myObject;
}
// protected MyObject readResolve() {
// System.out.println("调用了readResolve方法!");
// return MyObjectHandler.myObject;
// }
}
|
业务类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
package
com.weishiyao.learn.day8.singleton.ep5;
import
java.io.File;
import
java.io.FileInputStream;
import
java.io.FileNotFoundException;
import
java.io.FileOutputStream;
import
java.io.IOException;
import
java.io.ObjectInputStream;
import
java.io.ObjectOutputStream;
public
class
SaveAndRead {
public
static
void
main(String[] args) {
try
{
MyObject myObject = MyObject.getInstance();
FileOutputStream fosRef =
new
FileOutputStream(
new
File(
"myObjectFile.txt"
));
ObjectOutputStream oosRef =
new
ObjectOutputStream(fosRef);
oosRef.writeObject(myObject);
oosRef.close();
fosRef.close();
System.out.println(myObject.hashCode());
}
catch
(FileNotFoundException e) {
e.printStackTrace();
}
catch
(IOException e) {
e.printStackTrace();
}
FileInputStream fisRef;
try
{
fisRef =
new
FileInputStream(
new
File(
"myObjectFile.txt"
));
ObjectInputStream iosRef =
new
ObjectInputStream(fisRef);
MyObject myObject = (MyObject) iosRef.readObject();
iosRef.close();
fisRef.close();
System.out.println(myObject.hashCode());
}
catch
(FileNotFoundException e) {
e.printStackTrace();
}
catch
(IOException e) {
e.printStackTrace();
}
catch
(ClassNotFoundException e) {
e.printStackTrace();
}
}
}
|
结果
970928725
1099149023
两个不同的hashCode,证明并不是同一个对象,解决方案,添加下面这段代码
1
2
3
4
|
protected
MyObject readResolve() {
System.out.println(
"调用了readResolve方法!"
);
return
MyObjectHandler.myObject;
}
|
在反序列化的时候调用,可以得到同一个对象
System.out.println(myObject.readResolve().hashCode());
结果
1255301379
调用了readResolve方法!
1255301379
相同的hashCode,证明得到了同一个对象
五、使用static代码块实现单例
静态代码块中的代码在使用类的时候就已经执行了,所以可以应用静态代码快这个特性来实现单利模式
MyObject类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
package
com.weishiyao.learn.day8.singleton.ep6;
public
class
MyObject {
private
static
MyObject instance =
null
;
private
MyObject() {
super
();
}
static
{
instance =
new
MyObject();
}
public
static
MyObject getInstance() {
return
instance;
}
}
|
线程类
1
2
3
4
5
6
7
8
9
10
|
package
com.weishiyao.learn.day8.singleton.ep6;
public
class
MyThread
extends
Thread {
@Override
public
void
run() {
for
(
int
i =
0
; i <
5
; i++) {
System.out.println(MyObject.getInstance().hashCode());
}
}
}
|
运行类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package
com.weishiyao.learn.day8.singleton.ep6;
public
class
Run {
public
static
void
main(String[] args) {
MyThread t1 =
new
MyThread();
MyThread t2 =
new
MyThread();
MyThread t3 =
new
MyThread();
MyThread t4 =
new
MyThread();
MyThread t5 =
new
MyThread();
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
|
运行结果:
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
通过静态代码块只执行一次的特性也成功的得到了线程安全的单例模式
六、使用enum枚举数据类型实现单例模式
枚举enum和静态代码块的特性类似,在使用枚举时,构造方法会被自动调用,也可以用来实现单例模式
MyObject类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
package
com.weishiyao.learn.day8.singleton.ep7;
import
java.sql.Connection;
import
java.sql.DriverManager;
import
java.sql.SQLException;
public
enum
MyObject {
connectionFactory;
private
Connection connection;
private
MyObject() {
try
{
System.out.println(
"调用了MyObject的构造"
);
String name =
"root"
;
String password =
"111111"
;
String driverName =
"com.mysql.jdbc.Driver"
;
Class.forName(driverName);
connection = DriverManager.getConnection(url, name, password);
}
catch
(ClassNotFoundException e) {
e.printStackTrace();
}
catch
(SQLException e) {
e.printStackTrace();
}
}
public
Connection getConnection() {
return
connection;
}
}
|
线程类
1
2
3
4
5
6
7
8
9
10
|
package
com.weishiyao.learn.day8.singleton.ep7;
public
class
MyThread
extends
Thread {
@Override
public
void
run() {
for
(
int
i =
0
; i <
5
; i++) {
System.out.println(MyObject.connectionFactory.getConnection().hashCode());
}
}
}
|
运行类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package
com.weishiyao.learn.day8.singleton.ep7;
public
class
Run {
public
static
void
main(String[] args) {
MyThread t1 =
new
MyThread();
MyThread t2 =
new
MyThread();
MyThread t3 =
new
MyThread();
MyThread t4 =
new
MyThread();
MyThread t5 =
new
MyThread();
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
|
运行结果
调用了MyObject的构造
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
上面这种写法将枚举类暴露了,违反了“职责单一原则”,可以使用一个类将枚举包裹起来
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
package
com.weishiyao.learn.day8.singleton.ep8;
import
java.sql.Connection;
import
java.sql.DriverManager;
import
java.sql.SQLException;
public
class
MyObject {
public
enum
MyEnumSingleton {
connectionFactory;
private
Connection connection;
private
MyEnumSingleton() {
try
{
System.out.println(
"调用了MyObject的构造"
);
String name =
"root"
;
String password =
"111111"
;
String driverName =
"com.mysql.jdbc.Driver"
;
Class.forName(driverName);
connection = DriverManager.getConnection(url, name, password);
}
catch
(ClassNotFoundException e) {
e.printStackTrace();
}
catch
(SQLException e) {
e.printStackTrace();
}
}
public
Connection getConnection() {
return
connection;
}
}
public
static
Connection getConnection() {
return
MyEnumSingleton.connectionFactory.getConnection();
}
}
|
更改线程代码
1
2
3
4
5
6
7
8
9
10
|
package
com.weishiyao.learn.day8.singleton.ep8;
public
class
MyThread
extends
Thread {
@Override
public
void
run() {
for
(
int
i =
0
; i <
5
; i++) {
System.out.println(MyObject.getConnection().hashCode());
}
}
}
|
结果
调用了MyObject的构造
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
文章转自:http://www.jb51.net/article/81629.htm
关于serialVersionUID的介绍:https://blog.csdn.net/qq_27093465/article/details/78544505