一、单例设计模式
核心思想: 在程序运行中,该类的对象无论怎么创建,始终保持有且仅有一个该类的对象
在应用整个生命周期内只能存在一个实例。单例模式是一种被广泛应用的设计模式。能够避免实例对象的重复创建,减少创建实例的系统开销,节省内存。
单例模式和静态类的区别
静态类就是一个类里面都是静态方法和静态属性,构造器被private修饰,因此不能被实例化。Math类就是一个静态类
1)单例模式会提供给你一个全局唯一的对象,静态类只是提供给你静态方法,这些方法不用创建对象,通过类就可以直接调用;
2)单例模式灵活性高,方法可以被override,因为静态类都是静态方法,所以不能被override;
3)如果是一个非常重要的对象,单例模式可以懒加载,静态类就无法做到。
什么时候该用静态类,什么时候该用单例模式?
如果你只是想用一些工具方法,那么最好用静态类,静态类比单例类更快,因为静态类的绑定是在编译期进行的。
如果你要维护状态信息,或者访问资源时,应该使用单例模式。就是,当你需要面向对象的能力时(继承、多态),选用单例类,当你仅仅是提供一些方法时选用静态类。
编写单例类:
1. 私有化构造方法
2.自己类内部创建对象 并且 私有化private 静态化static
3.给类外部 提供一个获取该对象的方法
饿汉式单例类:
所谓饿汉式就是立即加载,一般情况下在调用getInstance
方法之前就已经产生了实例,也就是在类加载的时候就已经产生了。这种模式的缺点是占用资源,当单例类很大的时候,其实我们是想使用的时候再产生实例。因此这种方式适合占用资源少,在初始化的时候就会被用到的类。
class Single {
// 声明一个本类对象 私有化 静态化
// 外界无法访问 随着类的加载只加载一次
private static Single single = new Single();
// 私有化构造方法 外界无法创建本类对象
private Single {
}
// 对外提供一个访问本类私有化对象的方法(返回本类的对象)
public static Single getInstance(){
return single;
}
}
懒汉式单例类:
懒汉模式就是延迟加载,也叫懒加载。在程序需要用到的时候在创建实例,这样保证了内存不会被浪费。针对懒汉式这里给出了5种实现方式,有些实现方式是线程不安全的,在多线程并发的环境下可能出现资源同步问题。
第一种:单线程下没问题,多线程下有问题
// 单例模式的懒汉实现1--线程不安全
class SingletonLazy1 {
private static SingletonLazy1 singletonLazy;
private SingletonLazy1() {
}
public static SingletonLazy1 getInstance() {
if (null == singletonLazy) {
try {
// 模拟在创建对象之前做一些准备工作
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
singletonLazy = new SingletonLazy1();
}
return singletonLazy;
}
}
模拟10个异步线程进行测试:
public class SingletonLazyTest {
public static void main(String[] args) {
Thread2[] ThreadArr = new Thread2[10];
for (int i = 0; i < ThreadArr.length; i++) {
ThreadArr[i] = new Thread2();
ThreadArr[i].start();
}
}
}
// 测试线程
class Thread2 extends Thread {
@Override
public void run() {
System.out.println(SingletonLazy1.getInstance().hashCode());
}
}
运行结果
124191239
124191239
872096466
1603289047
1698032342
1913667618
371739364
124191239
1723650563
367137303
可以看到他们的hashcode不都是一样的,说明在多线程环境下产生了多个对象,不符合单例模式的要求。
第二种方法:使用synchronized关键字对getInstance方法进行同步。
// 单例模式的懒汉实现2--线程安全
// 通过设置同步方法,效率太低,整个方法被加锁
class SingletonLazy2 {
private static SingletonLazy2 singletonLazy;
private SingletonLazy2() {
}
public static synchronized SingletonLazy2 getInstance() {
try {
if (null == singletonLazy) {
// 模拟在创建对象之前做一些准备工作
Thread.sleep(1000);
singletonLazy = new SingletonLazy2();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return singletonLazy;
}
}
使用上面的测试类,测试结果:
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
可以看到,这种方式线程是安全的的。但是缺点就是效率太低,是同步运行的,下个线程想要取得对象,就必须等上一个线程释放,才可以继续执行。
第三种方式:我们不对方法加锁,而是将里面的代码加锁,也可以实现线程安全。但这种方法和方法二一样,也是同步运行的,效率也很低。
// 单例模式的懒汉实现3--线程安全
// 通过设置同步代码块,效率也太低,整个代码块被加锁
class SingletonLazy3 {
private static SingletonLazy3 singletonLazy;
private SingletonLazy3() {
}
public static SingletonLazy3 getInstance() {
try {
synchronized (SingletonLazy3.class) {
if (null == singletonLazy) {
// 模拟在创建对象之前做一些准备工作
Thread.sleep(1000);
singletonLazy = new SingletonLazy3();
}
}
} catch (InterruptedException e) {
// TODO: handle exception
}
return singletonLazy;
}
}
方法四:我们继续优化代码,只给创建对象加锁,但是这样能保证线程安全吗?
// 单例模式的懒汉实现4--线程不安全
// 通过设置同步代码块,只同步创建实例的代码
// 但是还是有线程安全问题
class SingletonLazy4 {
private static SingletonLazy4 singletonLazy;
private SingletonLazy4() {
}
public static SingletonLazy4 getInstance() {
try {
if (null == singletonLazy) { //代码1
// 模拟在创建对象之前做一些准备工作
Thread.sleep(1000);
synchronized (SingletonLazy4.class) {
singletonLazy = new SingletonLazy4(); //代码2
}
}
} catch (InterruptedException e) {
// TODO: handle exception
}
return singletonLazy;
}
}
测试结果:
1210004989
1425839054
1723650563
389001266
1356914048
389001266
1560241484
278778395
124191239
367137303
从结果上看,这种方式不能保证线程安全,为什么呢?
我们假设有两个线程A和B同时走到了‘代码1’,因为此时对象还是空的,所以都能进到方法里面,线程A首先抢到锁,创建了对象。释放锁后线程B拿到锁走到‘代码2’,也创建了一个对象,因此多线程环境下就不能保证单例了。
方法五:继续优化。既然存在上述问题,那我们在同步代码块里面再做一次null判断不就可以了。这种方式就是我们的DCL双重检查锁机制。
//单例模式的懒汉实现5--线程安全
//通过设置同步代码块,使用DCL双检查锁机制
//使用双检查锁机制成功的解决了单例模式的懒汉实现的线程不安全问题和效率问题
//DCL 也是大多数多线程结合单例模式使用的解决方案
class SingletonLazy5 {
private static SingletonLazy5 singletonLazy;
private SingletonLazy5() {
}
public static SingletonLazy5 getInstance() {
try {
if (null == singletonLazy) {
// 模拟在创建对象之前做一些准备工作
Thread.sleep(1000);
synchronized (SingletonLazy5.class) {
if(null == singletonLazy) {
singletonLazy = new SingletonLazy5();
}
}
}
} catch (InterruptedException e) {
// TODO: handle exception
}
return singletonLazy;
}
}
运行结果:保证单例(自检)
DCL双重检查锁机制很好的解决了懒加载单例模式的效率问题和线程安全问题。这也是我们最常用到的方式。
静态内部类实现单例模式
//使用静态内部类实现单例模式--线程安全
class Singleton {
private Singleton() {
}
private static class SingletonInner {
private static Singleton singleton = new Singleton();
}
public static Singleton getInstance() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return SingletonInner.singleton;
}
}
可以看到使用这种方式我们没有显示的进行任何同步操作,那他是如何保证线程安全的呢?
和饿汉式一样,是靠JVM保证类的静态成员只能被加载一次的特点,这样就从JVM层面保证了只会有一个实例对象。
那么问题来了,这种方式和饿汉式又有什么区别呢?不也是立即加载吗?
实则不然,加载一个类时,其内部类不会同时被加载。一个类的内部类被加载,当且仅当其某个静态成员(静态域、构造器、静态方法等)被调用时发生。
可以说这种方式是实现单例模式的最优解。
静态代码块实现单例模式。这种方式和饿汉式类似,是一种饿汉模式。
//使用静态代码块实现单例模式
class SingletonStaticBlock {
private static SingletonStaticBlock singletonStaticBlock;
static {
singletonStaticBlock = new SingletonStaticBlock();
}
public static SingletonStaticBlock getInstance() {
return singletonStaticBlock;
}
}
---------------------
作者:萌中芢
来源:CSDN
原文:https://blog.csdn.net/qq_42857603/article/details/83375057
版权声明:本文为博主原创文章,转载请附上博文链接!
序列化与反序列化
为什么要提序列化与反序列化呢?
因为单例模式虽然能保证线程安全,但是在序列化和反序列化的情况下会出现生成多个对象的情况。
public class SingletonStaticInnerSerializeTest {
public static void main(String[] args) {
try {
SingletonStaticInnerSerialize serialize = SingletonStaticInnerSerialize.getInstance();
System.out.println(serialize.hashCode());
//序列化
FileOutputStream fo = new FileOutputStream("tem");
ObjectOutputStream oo = new ObjectOutputStream(fo);
oo.writeObject(serialize);
oo.close();
fo.close();
//反序列化
FileInputStream fi = new FileInputStream("tem");
ObjectInputStream oi = new ObjectInputStream(fi);
SingletonStaticInnerSerialize serialize2 = (SingletonStaticInnerSerialize) oi.readObject();
oi.close();
fi.close();
System.out.println(serialize2.hashCode());
} catch (Exception e) {
e.printStackTrace();
}
}
}
//使用匿名内部类实现单例模式,在遇见序列化和反序列化的场景,得到的不是同一个实例
//解决这个问题是在序列化的时候使用readResolve方法,即去掉注释的部分
class SingletonStaticInnerSerialize implements Serializable {
/**
* 2018年03月28日
*/
private static final long serialVersionUID = 1L;
private static class InnerClass {
private static SingletonStaticInnerSerialize singletonStaticInnerSerialize = new SingletonStaticInnerSerialize();
}
public static SingletonStaticInnerSerialize getInstance() {
return InnerClass.singletonStaticInnerSerialize;
}
// protected Object readResolve() {
// System.out.println("调用了readResolve方法");
// return InnerClass.singletonStaticInnerSerialize;
// }
}
可以看到:
865113938
1078694789
结果表明的确是两个不同的实例对象,违背了单例模式,那么如何解决这个问题呢?
在反序列化中使用readResolve()方法,将上面的注释去掉,再次运行
865113938
调用了readResolve方法
865113938
readResolve()方法到底是何方神圣,其实当JVM从内存中反序列化的“组装”一个新对象时,就会自动调用这个readResolve()方法来返回我们指定好的对象,单例规则也就得到了保证。readResolve()的出现允许程序员自行控制通过反序列化得到的对象。
单例模式相关内容摘自他处
原文:https://blog.csdn.net/qq_42857603/article/details/83375057
二、抽象类
抽象: 描述不清的即为抽象
抽象类 关键词 abstract
abstract 修饰类 该类即为抽象类
abstract 修饰方法 该方法则为抽象方法
注意: 抽象方法没有方法的实现部分
注意:
1. 有抽象方法的类 必须定义为抽象类
2.抽象类中不一定有抽象方法
3.抽象类不能直接创建对象
4.强制子类重写父类的抽象方法
5.使用多态形式创建对象
核心: 抽象类不能直接使用 只能使用抽象类的子类
抽象类允许构造方法的存在 是因为抽象类需要借助子类创建对象,为了保证继承的完整性
abstract class Animal {
final int num = 10; // 常量必须初始化赋初值
// 构造方法
public Animal() {
System.out.println("我是Animal 无参的构造方法");
}
// 声明一个抽象方法(没有实现部分)
public abstract void speak();
public void fun() {
System.out.println("我是fun方法");
}
}
注意: abstract 不能与 private final static 共存
abstract抽象类 强制子类去重写抽象方法
private私有化 只能奔雷访问 无法被继承重写
final 修饰抽象方法 则该方法无法被修改 被重写
static 修饰抽象方法 则可使用类名直接调用 抽象方法没有实现部分,不能直接调用
三、模板设计模式
思想: 大部分内容一样 只需要修改一部分内容
abstract class LOStudy {
// 学习方法
public void study() {
System.out.println("报名学计算机");
// 选择学科是需要变化的 声明一个抽象方法
// 调用抽象方法
chooseSubject();
System.out.println("毕业找工作");
}
// 声明抽象方法
public abstract void chooseSubject();
}
class LSK extends LOStudy {
// 重写抽象方法
@Override
public void chooseSubject() {
System.out.println("学Java");
}
}
class LNA extends LOStudy {
@Override
public void chooseSubject() {
System.out.println("学H5");
}
}
计算一段程序的执行时间: System.cuttrntTimeMillis() 方法
long time = System.cuttrntTimeMillis() ;
获取的是 当前时间 到 1970年1月1日 的时间 (格林尼治时间)
long start = System.currentTimeMillis();
// 执行某一个程序
chengXu();
long stop = System.currentTimeMillis();
System.out.println("该循环耗时:" + (stop - start) + "ms");
四、接口
狭义: Java中的接口使用interface关键词来声明
interface 接口名 { }
广义: 看做 一个规则、规范 需要来遵守的
注意:
1.只能声明 抽象方法(JDK 1.8 可以声明静态方法 和 默认方法)
2.可以声明变量 (实际为常量)
3.接口不能被继承 只能用来实现
4.接口没有构造方法 (接口是用的是实现 不用继承)
5.接口中声明的变量 默认是 public static final 修饰
默认就是静态常量
6.声明抽象方法时 默认是 public abstract修饰
接口的运行与类一样 翻译成.class文件被执行
接口的实现类:
接口的实现类 接口使用implements 关键词 实现
// 声明一个接口
interface InterA {
int num = 10;
public abstract void fun();
}
// 接口的实现类
class InterAImpl implements InterA {
// 实现接口的抽象方法
@Override
public void fun() {
System.out.println(num);
}
}
抽象类和接口的区别:
1.使用方法
抽象类是继承,接口是实现
2.成员变量
抽象类可以是常量也可以是变量,接口只能是常量
3.成员方法
抽象类可以使成员方法也可以是抽象方法,接口只能使抽象方法
4.构造方法
抽象类有构造方法 接口中没有构造方法
类与类之间的关系: 只允许单继承
类与接口之间的关系: 实现关系 可以多实现
接口和接口的关系: 可以单继承 也可以多继承
// 类和接口的关系
interface InterB {
public abstract void fun1();
}
interface InterC {
public abstract void fun2();
}
class TestA implements InterB,InterC {
@Override
public void fun2() {
}
@Override
public void fun1() {
}
}
// 接口和接口的关系
interface interD {
public abstract void fun1();
}
interface interE {
public abstract void fun2();
}
interface interF extends interD,interE {
}
class interFImpl implements interF {
@Override
public void fun1() {
}
@Override
public void fun2() {
}
}
一个类即继承父类又是接口的实现类时 先写继承再写实现
class Abc extends AAA implements BBB { }
默认方法 用default关键词修饰
默认方法不需要必须在实现类中重写
调用父类中的默认方法 : 接口名.super.默认方法名