单例模式:全局唯一并且所有程序都可以使用的对象,就是单例模式。
单例模式的两种实现方式:
- 饿汉方式(线程安全的)
- 懒汉方式
一、单例模式之饿汉方式
public class ThreadDemo84 {
//单例类
static class Singleton{
//单例模式的3个步骤
//1.将构造函数设置为私有的,不让外部可见(单例模式是不可以new的)
/**
* java 默认的构造函数是public的,所以要显示将构造方法设置为私有的,这样才可以将将public的构造方法覆盖掉
*/
private Singleton(){
}
//2.创建私有的静态的类变量(让底下的第三步方法返回)
private static Singleton singleton = new Singleton();
//3.给外部提供的获取单例的方法
//单例不能new,所以使用static
public static Singleton getInstance() {
return singleton;
}
}
static Singleton s1 = null;
static Singleton s2 = null;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
s1 = Singleton.getInstance();
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
s2 = Singleton.getInstance();
}
});
t2.start();
t1.join();
t2.join();
System.out.println(s1 == s2); //如果得到的是true,说明单例模式的书写是正确的
}
}
true
饿汉方式:
- 优点:实现简单,不存在线程安全的问题,因为饿汉的方式是随着程序的启动而初始化的,因为类加载是线程安全的,所以他是线程安全的
- 缺点:随着程序的启动而启动,有可能整个程序的运行周期里面都没有用到,这样就带来了不必要的开销
二、单例模式之懒汉方式
懒汉方式:不见兔子不撒鹰,他不会随着程序的启动而启动,而是等到有人调用它的时候,它才会初始化
/**
* 单例模式之懒汉方式1(这里的代码是线程不安全的)
*/
public class ThreadDemo85 {
static class Singleton{
//1.设置私有的构造函数,防止其他程序进行创建
private Singleton(){}
//2.提供一个私有的静态变量
//因为时懒汉方式,比较懒,所以先不会进行new对象而是设置为null
private static Singleton singleton = null;
//3.提供给外部调用(返回一个单例对象给外部)
public static Singleton getInstance() {
if (singleton == null) {
//第一次访问,进行实例化,并且只实例化一次
singleton = new Singleton();
}
return singleton;
}
}
private static Singleton s1 = null;
private static Singleton s2 = null;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
//调用单例对象
s1 = Singleton.getInstance();
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
s2 = Singleton.getInstance();
}
});
t2.start();
//等待线程执行结束
t1.join();
t2.join();
System.out.println(s1 == s2);
}
}
true
但是!!!上面的代码会存在一定的问题,上面的代码是线程不安全的,做一下修改就可以看出来线程不安全的问题的存在了。修改的代码见下面:
public class ThreadDemo86 {
static class Singleton{
//1.设置私有的构造函数,防止其他程序进行创建
private Singleton(){}
//2.提供一个私有的静态变量
//因为时懒汉方式,比较懒,所以先不会进行new对象而是设置为null
private static Singleton singleton = null;
//3.提供给外部调用(返回一个单例对象给外部)
public static Singleton getInstance() throws InterruptedException {
if (singleton == null) {
Thread.sleep(1000);
//第一次访问,进行实例化,并且只实例化一次
singleton = new Singleton();
}
return singleton;
}
}
private static Singleton s1 = null;
private static Singleton s2 = null;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
//调用单例对象
try {
s1 = Singleton.getInstance();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
s2 = Singleton.getInstance();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t2.start();
//等待线程执行结束
t1.join();
t2.join();
System.out.println(s1 == s2);
}
}
false
上面的结果是false,就说明不是线程安全的,线程不安全的原因就是两个线程同时进行操作,都操作到要创建单例模式的那一步的时候进行休眠了,这个时候判断的两个线程都是第一次创建单例模式,就会导致线程不安全的问题。
那么如何将上面的线程不安全的代码修改为线程安全的代码呢?见下面的代码:
/**
* 单例模式之懒汉方式2
*/
public class ThreadDemo87 {
static class Singleton{
//1.设置私有的构造函数,防止其他程序进行创建
private Singleton(){}
//2.提供一个私有的静态变量
//因为是懒汉方式,比较懒,所以先不会进行new对象而是设置为null
private static Singleton singleton = null;
//3.提供给外部调用(返回一个单例对象给外部)
//加锁保证线程安全!!!!!!
public static synchronized Singleton getInstance() throws InterruptedException {
if (singleton == null) {
Thread.sleep(1000); //可以不加
//第一次访问,进行实例化,并且只实例化一次
singleton = new Singleton();
}
return singleton;
}
}
private static Singleton s1 = null;
private static Singleton s2 = null;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
//调用单例对象
try {
s1 = Singleton.getInstance();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
s2 = Singleton.getInstance();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t2.start();
//等待线程执行结束
t1.join();
t2.join();
System.out.println(s1 == s2);
}
}
true
说明加上锁以后就变成了线程安全的,但是上面的修改方案粒度太多,性能不高
所以使用 双重校验锁来解决:
public class ThreadDemo88 {
static class Singleton{
//1.设置私有的构造函数,防止其他程序进行创建
private Singleton(){}
//2.提供一个私有的静态变量
//因为时懒汉方式,比较懒,所以先不会进行new对象而是设置为null
private static Singleton singleton = null;
//3.提供给外部调用(返回一个单例对象给外部)
//加锁保证线程安全!!!!!!
public static Singleton getInstance() throws InterruptedException {
if (singleton == null) {
synchronized (Singleton.class) {
//双重校验锁 上面的一行代码和下面的一行代码
if (singleton == null) {
//第一次访问,进行实例化,并且只实例化一次
singleton = new Singleton();
}
}
}
return singleton;
}
}
private static Singleton s1 = null;
private static Singleton s2 = null;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
//调用单例对象
try {
s1 = Singleton.getInstance();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
s2 = Singleton.getInstance();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t2.start();
//等待线程执行结束
t1.join();
t2.join();
System.out.println(s1 == s2);
}
}
true
但是上面的版本不是最终的版本,因为在jvm(会进行优化,导致指令重排序)里面还是存在一定的问题,但是代码演示不出来:
要解决上面的指令重排序的问题只需要加上关键字volatile,下面的代码就是懒汉方式的最终版本了。
public class ThreadDemo89 {
//单例模式的最终版本!!!!!
static class Singleton{
//1.设置私有的构造函数,防止其他程序进行创建
private Singleton(){}
//2.提供一个私有的静态变量
//因为时懒汉方式,比较懒,所以先不会进行new对象而是设置为null
//加上关键字volatile防止发生指令重排序的问题
private static volatile Singleton singleton = null;
//3.提供给外部调用(返回一个单例对象给外部)
public static Singleton getInstance() throws InterruptedException {
if (singleton == null) {
synchronized (Singleton.class) {
//双重校验锁
if (singleton == null) {
//第一次访问,进行实例化,并且只实例化一次
singleton = new Singleton();
}
}
}
return singleton;
}
}
private static Singleton s1 = null;
private static Singleton s2 = null;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
//调用单例对象
try {
s1 = Singleton.getInstance();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
s2 = Singleton.getInstance();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t2.start();
//等待线程执行结束
t1.join();
t2.join();
System.out.println(s1 == s2);
}
}
true
三、自定义阻塞队列
import java.util.Random;
/**
* 数组的方式实现阻塞式队列
*/
public class ThreadDemo91 {
//自定义阻塞队列
static class MyBlockQueue{
private int[] values; //存放数据的数组
private int first;
private int last;
private int size;
public MyBlockQueue(int maxSize) {
values = new int[maxSize];
size = 0;
first = 0;
last = 0;
}
/**
* 添加元素,将元素添加到队尾
* @param val
*/
public void offer (int val) throws InterruptedException {
synchronized (this) {
//判断容量是否达到最大值
if (size == values.length) {
//阻塞等待消费者先消费
this.wait();
}
values[last++] = val;
size++;
//判断是否是最后一个元素‘
if (last == values.length) {
//循环队列
last = 0;
}
//唤醒消费者取队列中的信息
this.notify(); //唤醒另一个
}
}
/**
* 取队首元素
* @return
*/
public int poll() throws InterruptedException {
int result = 0;
synchronized (this) {
//判断队列里面是否有元素
if (size == 0) {
//阻塞等待
this.wait();
}
result = values[first++];
size--;
//判断队首是不是最后一个元素
if (first == values.length) {
first = 0;
}
this.notify(); //唤醒生产者生产数据
}
return result;
}
}
public static void main(String[] args) {
MyBlockQueue myBlockQueue = new MyBlockQueue(100);
//生产者生产数据
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
//添加数据
while (true) {
int num = new Random().nextInt(10);
System.out.println("生产数据:"+num);
try {
myBlockQueue.offer(num); //存入数据
Thread.sleep(500); //休眠500ms,添加数据的速度远远慢于取数据的速度
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
int result = myBlockQueue.poll();
System.out.println("消费数据:" + result);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t2.start();
}
}
生产数据:6
消费数据:6
生产数据:6
消费数据:6
生产数据:9
消费数据:9
生产数据:9
消费数据:9
生产数据:3
消费数据:3
......