单例模式能保证某个类在程序中只存在 唯一一份实例 ,而不会创建出多个实例。
饿汉式 : 直接就将对象创建出来,不管后面会不会用到,需要时就直接返回。因为在类加载时就创建好对象了,后续不需要再创建,相当于只有读操作,所以是 线程安全 的
// 通过 Singleton 这个类来实现
class Singleton{
// 这个 instance 就是该类的唯一实例
private static Singleton instance = new Singleton();// 类成员
// 为了防止再获得他的对象 (new),将构造方法私有化
private Singleton(){}
// 提供方法让外界拿到 instance 对象
public static Singleton getInstance(){
return instance;
}
}
懒汉式: 当需要用到时,再创建实例,后续不再创建
// 通过 Singleton 这个类来实现
class Singleton{
// 这个 instance 就是该类的唯一实例
private static volatile Singleton2 instance = null;// 类成员
// 为了防止再获得他的对象,将构造方法私有化
private Singleton(){}
// 提供方法让外界拿到 instance 对象
// 只有当真正要用到实例时,才会创建实例
public static Singleton getInstance(){
// 判断是否已经创建过对象了
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
但是我们可以发现在懒汉式中, 该方法有读,还有修改对象,而且不是原子的,线程不安全。
如何实现一个线程安全的单例模式?
加锁(synchronized),将读写操作封装成一个原子操作
synchronized (Singleton.class){
// 看是否要创建实例
if (instance == null) {
// 初始化之后,就不会再进行修改,线程安全
instance = new Singleton();
}
}
这样虽然没问题,但是频繁的获取和释放锁对象,会造成很大的开销,所以在外层加上一层判定,如果已经创建好了实例,就不去竞争锁,直接返回。
// 看要不要竞争锁
if (instance == null) {
synchronized (Singleton.class){
// 看是否要创建实例
if (instance == null) {
instance = new Singleton();
}
}
}
当多个线程同时读 instance ,会有内存可见性问题,当还没有创建好对象时,多个读到的都是 null ,即使多个线程中有一个线程创建了instance 实例,其他线程还是会去竞争锁对象。所以加上 volatile 关键字,避免无用的锁竞争。
private static volatile Singleton instance = null;
完整代码:
// 通过 Singleton 这个类来实现
class Singleton{
private static volatile Singleton instance = null;
private Singleton() {}
public static Singleton getInstance(){
// 看要不要加锁
if (instance == null) {
synchronized (Singleton.class){
// 看是否要创建实例
if (instance == null) {
// 初始化之后,就不会再进行修改,线程安全
instance = new Singleton();
}
}
}
return instance;
}
}
阻塞队列是一种特殊的队列,除了具有队列的性质外,还具有阻塞的功能。例如下面的典型案例 – 生产者消费者模型
java标准库中的阻塞队列(BlockingDeque)
BlockingDeque<String> queue = new LinkedBlockingDeque<>();
queue.put("hello");// 入队列
String s = queue.take();// 出队列
实现一个阻塞队列: 普通循环队列 + 线程安全( synchronized) + 阻塞功能( wait、notify)
// 实现一个阻塞队列
class MyBlockingQueue{
// 数组 -- 循环队列
private int[] data = new int[1000];
private int size = 0;// 元素个数
private int head;// 队首下标
private int tail;// 队尾下标
// 入队列
// 每个都在操作公共变量,就给整个方法加锁
public synchronized void put(int val) throws InterruptedException {
if (size == data.length) {
// 队满,阻塞
wait();
}
data[tail++] = val;
// tail 达到末尾
if (tail >= data.length) {
tail = 0;
}
size++;
// 入队列成功,队列非空,唤醒 take()
// 如果take()处于阻塞态,就能唤醒,不处于阻塞态,也没有副作用
notify();
}
// 出队列
public synchronized Integer take() throws InterruptedException {
if (size == 0) {
wait();
}
int val = data[head];
head++;
if (head == data.length) {
head = 0;
}
size--;
// take成功之后,队列非满,唤醒 put的等待
notify();
return val;
}
}
基于上述阻塞队列,实现一个简单的生产者消费者模型
private static MyBlockingQueue queue = new MyBlockingQueue();
public static void main(String[] args) {
// 生产者线程
Thread producer = new Thread(() -> {
int num = 0;
while (true) {
try {
System.out.println("生产了 " + num);
queue.put(num);
num++;
// 生产慢
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
producer.start();
// 消费者线程
Thread customer = new Thread(() -> {
while (true) {
try {
// 消费慢
//Thread.sleep(500);
int num = queue.take();
System.out.println("消费了 " + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
customer.start();
}
什么是定时器
定时器也是软件开发中的一个重要组件,类似于一个 “闹钟”,达到一个设定的时间之后,就执行某个指定的代码
标准库中提供了一个 Timer 类,Timer 类的核心方法为 schedule
schedule 包含两个参数,第一个参数指定要执行的任务,第二个参数指定多长时间之后执行 (毫秒)
Timer timer = new Timer();
// new TimerTask()表示一个任务
timer.schedule(new TimerTask(){
@Override
public void run() {
System.out.println("我是要执行的任务");
}
},3000);// 3000ms后执行该任务
Timer 类的实现:
管理很多任务
1.描述一个任务(创建一个类 MyTask)
// 表示一个任务
class MyTask{
// 任务具体要干啥
private Runnable runnable;
// 任务什么时候干
private long time;
// delay 是一个时间间隔,不是绝对的时间
public MyTask(Runnable runnable,long delay) {
this.runnable = runnable;
this.time = System.currentTimeMillis() + delay;
}
public void run(){
// 通过这个方法,执行任务
runnable.run();
}
public long getTime(){
return this.time;
}
}
2.组织一个任务 (数据结构)
因为堆要进行比较,所以放入的元素如果是自定义类型,要指定比较方式,所以让MyTask类实现Comparable接口,并重写compareTo()方法指定比较方式(按时间顺序排序)
// 表示一个任务
class MyTask implements Comparable<MyTask>{
// ...... 前面一样
// 重写compareTo方法,指定比较方式
@Override
public int compareTo(MyTask o) {
return (int)(this.time - o.time);
}
}
在MyTimer类中,使用一个 带有阻塞功能的优先级队列 来放入任务
class MyTimer{
// 带有阻塞功能的优先级队列 -- 要考虑线程安全问题,可能在多个线程进行注册任务,同时还有线程来执行
private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
}
schedule方法 – 按照传入的参数,封装一个MyTask 对象,放入优先级队列中
// 把任务放进队列
public void schedule(Runnable runnable,long delay){
MyTask task = new MyTask(runnable, delay);
queue.put(task);
}
执行时间到了的任务 – 需要有一个线程去扫描,看是否有任务到了执行时间
但是这里不能让该线程一直去扫描,例如“3:50”该做作业,从“3:30”开始,就一直拿手机看时间是否到了,这显然是不科学的,属于“忙等”。
正确做法应该是:看排在最前面的任务,执行时到到没,没到则等待该任务的对应时间wait(time),然后再看是否到了,相当于“定闹钟”操作
private Object locker = new Object();// 一个锁对象
// 需要执行最靠前的任务
// 需要有一个线程来检查 小根堆的顶元素(任务),看是否需要执行了
public MyTimer(){
Thread t = new Thread(() -> {
while (true){
try {
// 取出堆顶任务
MyTask task = queue.take();
// 看是否到执行时间了
long curTime = System.currentTimeMillis();
// 如果时间还没到
if (curTime < task.getTime()){
// 将任务塞回优先级队列
queue.put(task);
// 等待相应时间 wait()
synchronized (locker){
locker.wait(task.getTime() - System.currentTimeMillis());
}
}else {
// 时间到了 --- 执行该任务
task.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
可以看到,新任务的执行时间更靠前,所以需要唤醒一下线程,让他再扫描一下堆顶元素,看是否到执行时间了,所以在schedule方法中,需要执行唤醒操作
public void schedule(Runnable runnable,long delay){
MyTask task = new MyTask(runnable, delay);
queue.put(task);
// 每次任务插入成功后,唤醒一下线程,让他检查一下堆顶元素是否到执行时间了
synchronized (locker){
locker.notify();
}
}
整体代码
// 描述一个任务
class MyTask implements Comparable<MyTask>{
private Runnable runnable;
private long time;
public MyTask(Runnable runnable,long delay) {
this.runnable = runnable;
this.time = System.currentTimeMillis() + delay;
}
public void run(){
runnable.run();
}
public long getTime(){
return this.time;
}
@Override
public int compareTo(MyTask o) {
return (int)(this.time - o.time);
}
}
// 定时器类
class MyTimer{
private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
public void schedule(Runnable runnable,long delay){
MyTask task = new MyTask(runnable, delay);
queue.put(task);
synchronized (locker){
locker.notify();
}
}
private Object locker = new Object();
public MyTimer(){
Thread t = new Thread(() -> {
while (true){
try {
MyTask task = queue.take();
long curTime = System.currentTimeMillis();
if (curTime < task.getTime()){
queue.put(task);
synchronized (locker){
locker.wait(task.getTime() - System.currentTimeMillis());
}
}else {
task.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
简单来说线程池就是,把线程创建好,放在池子里。线程用完了,不是还给系统,而是放回池子里,以备下一次用。后面需要用线程时,不必从系统这边申请,而是直接从池子里拿,一定程度上减少了开销。
public static void main(String[] args) throws InterruptedException {
// 创建一个固定线程的线程池
ExecutorService pool = Executors.newFixedThreadPool(10);
// 创建一个自动扩容的线程池
// Executors.newCachedThreadPool();
// 创建一个只有一个线程的线程池
// Executors.newSingleThreadExecutor();
// 创建一个带有定时器功能的线程池
// Executors.newScheduledThreadPool();
for (int i = 0; i < 100; i++) {
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello threadPoll");
}
});
}
}
实现一个线程池
描述一个任务(Runnable)
组织任务(BlockingQueue)
private static BlockingDeque<Runnable> queue = new LinkedBlockingDeque<>();
描述工作线程
// 描述一个工作线程,工作线程的功能就是从队列中取任务来执行
static class Worker extends Thread{
@Override
public void run() {
// 拿到上面的队列,取出任务执行
while (true) {
try {
// 循环获取任务
// 如果队列空,则会阻塞
Runnable runnable = MyThreadPool.queue.take();
runnable.run();// 执行该任务
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
组织工作线程
// 创建一个数据结构来组织线程
private List<Thread> workers = new ArrayList<>();
public MyThreadPool(int n) {
// 创建若干个线程,放到上述数组
for (int i = 0; i < n; i++) {
Worker worker = new Worker();
worker.start();
workers.add(worker);
}
}
需要实现往线程池添加任务
// 创建一个方法,允许程序员放任务到线程池
public void submit(Runnable runnable){
try {
queue.put(runnable);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
整体代码
class MyThreadPool {
private static BlockingDeque<Runnable> queue = new LinkedBlockingDeque<>();
static class Worker extends Thread{
@Override
public void run() {
while (true){
try {
Runnable runnable = queue.take();
runnable.run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
private List<Thread> workers = new ArrayList<>();
public MyThreadPool(int n) {
for (int i = 0; i < n; i++) {
Worker worker = new Worker();
worker.start();
workers.add(worker);
}
}
public void submit(Runnable runnable){
try {
queue.put(runnable);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}