- 提升程序的性能:
1、多线程
2、单例模式
单例模式
- 单例模式:整个程序的运行中只存储一个对象
简单来说,当我这个对象创建好了之后,在整个程序中,其他人想用的时候都用的是我创建的这个对象
单例模式的创建方式:
1、饿汉方式 —— 上来直接先创建一个对象
package ThreadLocal;
/**
* 饿汉方式
* */
public class ThreadLocalDemo80 {
static class Singleton{
// 1、创建私有的构造函数
private Singleton(){
} // 因为单例模式不能让别人new它,所以声明为private类
// 2、定义私有变量
// 饿汉方式是会随着类的启动而启动的(只加载一次,所以是线程安全的)
private static Singleton singleton = new Singleton();
// 3、提供公共的获取实例的方法
public static Singleton getInstance(){
return singleton;
}
}
public static void main(String[] args) {
Singleton singleton = Singleton.singleton;
System.out.println(singleton);
}
}
- 饿汉方式缺点分析:
程序启动之后就会创建,但创建完了有可能不会使用,从而浪费了系统资源
2、懒汉方式 —— 当程序启动之后并不会进行初始化,而是当对象被调用后才会被初始化
package ThreadLocal;
/**
* 非线程安全单例模式 —— 懒汉方式
* 虽然下面代码写出来后s1和s2比较后结果是一个对象
* 当有两个线程进来时,两个线程第一次拿到的都是一个Null,并且都会new 一个Singleton
* 此时两个线程的s1 和 s2 就是两个对象了
* **/
public class ThrreaLocalDemo85 {
static class Singleton{
// 1、创建一个私有的构造函数,防止在其他地方被初始化
private Singleton(){
}
// 2、创建一个私有的类对象
// 懒汉方式这里不进行初始化
private static volatile Singleton singleton = null;
// 3、提供统一的访问入口
public static Singleton getInstance(){
// 判断是否是初次访问
if(singleton == null){
// 第一次访问
singleton = new Singleton();
}
return singleton;
}
}
// public static void main(String[] args) {
// Singleton s1 = Singleton.getInstance();
// Singleton s2 = Singleton.getInstance();
// System.out.println(s1 == s2);
// }
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();
// 使用主线程执行任务
s2 = Singleton.getInstance();
// 等待t1执行完
t1.join();
System.out.println(s1 == s2);
}
}
解决饿汉模式出现的线程不安全问题:
- 方法1:直接给 “统一的访问入口”加锁,即
public static Singleton getInstance()
但此种方式意味着不管是不是第一次访问,都会排队执行(因噎废食),性能不佳 - 方法2:双重校验锁
但是仅仅使用双重校验锁还是有问题,因为singleton = new Singleton()
的执行是非原子性的
// 3、提供统一的访问入口
public static Singleton getInstance(){
// 判断是否是初次访问
if(singleton == null){
synchronized (Singleton.class) {
if (singleton == null) {
// 第一次访问
singleton = new Singleton();
}
}
}
return singleton;
}
- 当执行
singleton = new Singleton()
时,首先(1)要在内存中开辟空间,然后(2)进行初始化,最后(3)再将变量 singleton 指向内存区域。
但是编程语言会有指令重排序,会将原本的(1)-(2)-(3)顺序可能排序成为(1)-(3)-(2)
对于下面的代码,当有两个线程同时进来的时候:
synchronized (Singleton.class) {
if (singleton == null) {
// 第一次访问
singleton = new Singleton();
}
}
- 线程1首先开辟内存空间,然后将便改良指向了内存空间,此时线程1暂停执行;线程2进来后此时的singleton对象已经指向了内存空间,
singleton == null
为false,就会判定线程2不是第一次访问,此时线程2就会返回出去一个空对象,会造成线程不安全
- 方法3:双重校验锁 配合使用 volatile
对私有的类对象使用volatile修饰
private static volatile Singleton singleton = null;
自定义阻塞队列
生产者消费者模型 —— 生产者生产数据(添加数据),消费者消费生产者生产的数据(取出元素)
阻塞指的是,当数据量满了之后,不要尝试给队列添加数据,而是进行阻塞等待(sleep(不合适)、wait() + norify、LockSupport())。在取出数据时,当队列为空,就进行阻塞等待
自定义阻塞队列: 可以使用链表和数组实现
package ThreadLocal;
import java.util.Random;
/**
* 使用数组实现一个阻塞队列,循环队列
* */
public class ThreadDemo89 {
static class MyBlockingQueue{
private int[] values; // 实际存储数据的数组
private int first; // 对首
private int last; // 队尾
private int size; //队里元素的实际大小
public MyBlockingQueue(int initial){
// 初始化变量
values = new int[initial];
first = 0;
last = 0;
size = 0;
}
// 添加元素(队尾添加)
public void offer(int val){
synchronized (this) {
// 判断边界值
if (size == values.length) {
// 队列已满,阻塞等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 添加元素到队尾
values[last++] = val;
size++;
// 判断当前的值是否为最后一个元素
if(last == values.length){
last = 0;
}
// 尝试唤醒一下消费者(因为有可能这是第一条数据,此时消费者是阻塞状态的)
this.notify();
}
}
// 查询方法
public int poll(){
int result = -1; // 若返回-1 标识队列为空
synchronized (this) {
// 判断边界值
if (size == 0) {
// 队列为空,阻塞等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 取元素
result = values[first++];
size--;
// 判断是否为最后一个元素
if (first == values.length) {
first = 0;
}
// 尝试去唤醒生产者(当队列满了之后,生产者是进入休眠的)
this.notify();
}
return result;
}
}
public static void main(String[] args) {
MyBlockingQueue myBlockingQueue = new MyBlockingQueue(100);
// 让生产者每隔500毫秒生产一个数据
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
while(true){
int num = new Random().nextInt(10);
System.out.println("生成了随机数:" + num);
myBlockingQueue.offer(num);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t1.start();
// 消费之一直去消费
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
while(true){
int result = myBlockingQueue.poll();
System.out.println("消费了数据:" + result);
}
}
});
t2.start();
}
}