单例:保证在内存之中只有一个实例。
应用场景:整个项目中只需要有一个实例存在,即可使用单例。比如工具类,配置加载类等
一、饿汉式
1.如何保证内存中只有一个实例,将该类的构造器私有化。
例如:private Person(){}
2.其他类如何获取该实例
再类中提供静态方法,返回该实例。
private static final Person INSTANCE = new Person();//定义该实例
public static Person getInstance(){return person} //提供获取方法
3.如何判断通过getIntance方法获取到的对象是相等的
Person person1=Person.getInstance();
Person person2=Person.getInstance();
System.out.println(person1=person2);//两个相等即可表明是同一个实例
完整代码:
public class Person {
private static final Person INSTANCE = new Person();
private Person() {
}
public static Person getInstance() {
return INSTANCE;
}
public static void main(String[] args) {
Person person1 = Person.getInstance();
Person person2 = Person.getInstance();
System.out.println(person1 == person2);
}
}
这种写法优点:在类加载到内存的时候,只会加载一次,加载的时候会就会实例化Person,只实例化一次,此时JVM保证了线程安全,从而保证了在内存中只有一个实例,推荐使用。
有个小缺点:没有使用的时候,依然实例化了;
因为在类加载后就直接实例化,所以称为饿汉式。
二、懒汉式
懒汉式:什么时候用,什么时候初始化。
上代码:
public class Person {
private static Person INSTANCE;//不能加final 因为final需要初始化
private Person() {
}
public static Person getInstance() {
if(Objects.nonNull(INSTANCE)){
INSTANCE = new Person();
}
return INSTANCE;
}
public static void main(String[] args) {
Person person1 = Person.getInstance();
Person person2 = Person.getInstance();
System.out.println(person1 == person2);
}
}
它的缺点:线程不安全,同时多个线程创建该实例,内存可能会有多个实例。
验证它的缺点:
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(Person.getInstance().hashCode());//同一个类的不同对象的hashCode是不同的
}
).start();
}
}
很难测试出来,需要加入以下代码。
public static Person getInstance() {
if (null == INSTANCE) {
try {
Thread.sleep(5);//让cpu切换线程
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Person();
}
return INSTANCE;
}
三、线程安全的懒汉模式
给方法加锁
import java.util.Objects;
public class Person {
private static Person INSTANCE;
private Person() {
}
//方法加锁
public static synchronized Person getInstance() {
if (null == INSTANCE) {
try {
Thread.sleep(5);//让cpu切换线程
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Person();
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(Person.getInstance().hashCode());
}
).start();
}
}
}
缺点:加锁效率大大降低。
四 加锁的同时提高效率的懒汉模式
在需要的地方加锁
package com.tavon.tank;
import java.util.Objects;
public class Person {
private static Person INSTANCE;
private Person() {
}
//方法加锁
public static Person getInstance() {
if (null == INSTANCE) {
synchronized (Person.class){
try {
Thread.sleep(5);//让cpu切换线程
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Person();
}
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(Person.getInstance().hashCode());
}
).start();
}
}
}
缺点:无法保证并发时候,在内存中只会有一个实例。
四 加锁的同时提高效率的懒汉模式 线程安全
双重判断:
import java.util.Objects;
public class Person {
private static volatile Person INSTANCE;//为什么要加volatile 防止指令重排。(如果JIT优化就一定要加)
private Person() {
}
//方法加锁
public static Person getInstance() {
if (null == INSTANCE) {
synchronized (Person.class) {
if (null == INSTANCE) {
try {
Thread.sleep(5);//让cpu切换线程
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Person();
}
}
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(Person.getInstance().hashCode());
}
).start();
}
}
}
此时有人问,第一次判断还有必要么,当然有,第一次判断是为了提高性能,不必每次都要执行锁。
五、静态内部类
public class Person {
private static class TPerson{
private final static Person INSTANCE = new Person();
}
private Person() {
}
//方法加锁
public static Person getInstance() {
return TPerson.INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(Person.getInstance().hashCode());
}
).start();
}
}
}
没有缺点,最完美 使用的时候才会被加载,类只被加载一次,这是由虚拟机保证的。所以比第一种还好
六、枚举方式
public enum Person {
INSTANCE;
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(Person.INSTANCE.hashCode());
}
).start();
}
}
}