当前位置: 首页> 健康> 养生 > 二维码生成器支持微信扫码_深圳和胜建设公司_怎么给客户推广自己的产品_做网站推广需要多少钱

二维码生成器支持微信扫码_深圳和胜建设公司_怎么给客户推广自己的产品_做网站推广需要多少钱

时间:2025/7/9 21:56:48来源:https://blog.csdn.net/weixin_74085729/article/details/144150116 浏览次数:0次
二维码生成器支持微信扫码_深圳和胜建设公司_怎么给客户推广自己的产品_做网站推广需要多少钱

目录

1. 单例模式

1) 饿汉模式

2) 懒汉模式

2. 阻塞队列

1) 阻塞队列的特性

2) 模拟实现阻塞队列

3. 定时器

4. 线程池

1) ThreadPoolExecutor 类

2) 模拟实现线程池


1. 单例模式

单例模式是最经典的设计模式之一。

单例模式,顾名思义,就是这个类在整个程序中只能有一个实例。

具体来说就是,约定一个类,只能有一个对象,通过编码技巧,让编译器强制进行检查,提前在类里把对象给创建好,然后将构造方法设为 private。

单例模式有两种实现方式,一种是饿汉模式,一种是懒汉模式。

懒就是,非必要,不采取行动,举个例子,在早上你可以一直睡觉,直到上学快要迟到了,你再起床,这样做的话,就能休息时间最大化,保证今天的学习效率。

而饿的话,就是截然相反,早上就非常早起床,然后马上去上学,起太早的话,可能就会睡眠不足,导致上课的时候昏昏欲睡,学习效率低。

1) 饿汉模式

我们可以具体来实现一下单例模式,用饿汉模式的方式,这个比较简单。

因为是单例模式嘛,而考虑到是饿汉的方式来实现,我们就可以不管三七二十一,先提前创建好对象,等到有人想要获取对象的时候,就直接返回创建好的对象,但是只做这些还不足以让它成为单例模式,我们还需要将它的构造方法设置成私有的,并且要将获取对象的方法设置成 public,并且用 static 修饰,这样就可以通过 类名.方法名 来调用获取实例的方法了。

最后写出来就是这个样子的:

// 期望这个类,能够有唯一实例
class Singleton {private static final Singleton instance = new Singleton();// 通过这个方法获取到刚才的实例// 后续如果想使用这个类的实例,就都通过 getInstance 方法来获取public static Singleton getInstance() {return instance;}// 把构造方法设为 private,此时类外面的其他代码,就无法 new 出这个类的对象了private Singleton() {}
}

2) 懒汉模式

接下来再来看看单例模式如何用懒汉的方式来实现。

懒,就是非必要,不采取行动。

那我们就可以先不初始化对象,等到有人调用获取对象的方法的时候,再来创建对象,然后再返回创建好的对象,那整个代码就跟饿汉方式差不多,就是延迟了创建对象的时机。

写出来就是这样的。 

这下修改完代码后,就才真正没问题了。

class SingletonLazy {private static volatile SingletonLazy instance = null;public static SingletonLazy getInstance() {// 是否是首次创建对象(从而判断要不要加锁)if (instance == null) {// 保证串行化执行,不会 new 多个对象synchronized (SingletonLazy.class) {if (instance == null) {instance = new SingletonLazy();}}}return instance;}private SingletonLazy() {}
}

饿汉方式实现的单例模式会涉及到线程安全的问题,需要注意三点:

1. 加锁,保证 if 条件和 new 操作是原子的,只会创建出一个对象。

2. 两层 if,第一个 if 用来判断是否要加锁,第二个 if 用来判断是否要创建对象。

3. volatile,用来禁止指令重排序问题。

而饿汉模式天然就是线程安全的,因为它只涉及到了多个线程的读取操作。

2. 阻塞队列

阻塞队列是多线程代码中常用到的一种数据结构。

1) 阻塞队列的特性

阻塞队列的特性有两个:

1. 线程安全

2. 带有阻塞特性

     a) 当队列为空时,从队列中取元素的话,就会阻塞,阻塞到其他线程往队列里添加元素为止

     b) 当队列为满时,往队列中放元素的话,就会阻塞,阻塞到其他线程从队列中取走元素为止

阻塞队列最大的意义,就是用来实现 "生产者消费者模型"(是一种常见的编码方式)。

怎么理解生产者消费者模型呢?举个例子:

那么为什么要使用生产者消费者模型呢?生产者消费者模型的优点是什么?

生产者消费者模型的优点:

1. 解耦合

两个模块,联系越紧密,耦合就越高。

2. 削峰填谷

峰:短时间内请求量比较多

谷:请求量比较少

在 Java 标准库里,提供了现成的阻塞队列 BlockingQueue,来让我们使用。

标准库里,针对于阻塞队列提供了两种最主要的实现方式: 1. 基于链表  2. 基于数组

虽然 BlockingQueue 继承了 Queue,但是不建议在 BlockingQueue 里使用 Queue 的方法,因为这些方法都不具备 "阻塞" 特性。

put 方法是阻塞式的入队列,take 方法是阻塞式的出队列。

2) 模拟实现阻塞队列

了解完上面这些,我们自己也可以模拟实现一个阻塞队列,阻塞队列就是比普通队列多了线程安全以及阻塞特性,那我们就可以先实现一个队列(只用实现 put 和 take 方法),然后再加上线程安全(synchronized),再加上阻塞就好了(wait & notify)。

首先,普通队列有两种实现方式,一种是基于数组,另一种是基于链表。

基于链表实现的话,就是头插尾删,蛮简单的,

基于数组实现的话,就是要实现一个循环数组。

这里我们使用数组来实现好了,添加元素就是往 tail 下标处添加元素,删除元素就是删除 head 处元素,直接 head++ 即可。

有了上面基础,我们就可以直接开始敲代码了,只用实现 put 和 take 方法就行。

首先搭好框架,然后再来开始写代码。

// 不写作泛型了,直接让这个队列里面存储字符串
class MyBlockingQueue {// 此处这里的最大长度,也可以指定构造方法,也可以构造方法的参数来判定private String[] data = new String[1000];private int head = 0;// 队列的起始位置private int tail = 0;// 队列的结束位置的下一个位置private int size = 0;// 记录当前队列有效元素个数// 提供核心方法,入队列和出队列public void put(String elem) {}public String take() {}
}

大致写好了普通队列,接下来再保证线程安全,首先是涉及到要修改变量的操作,就得加锁,因为两个方法大部分都有修改操作,那我们就可以直接创建个 Object 对象,然后加锁把方法包裹起来。

然后也要注意内存可见性和指令重排序的问题,以防万一,给那几个变量加上 volatile 会比较好。

最后再加上阻塞,使用 wait & notify 来完成就好。

官方文档建议,在使用 wait 的时候,最好搭配 while 来使用(将 if 换成 while)

完整代码:

// 不写作泛型了,直接让这个队列里面存储字符串
class MyBlockingQueue {// 此处这里的最大长度,也可以指定构造方法,也可以构造方法的参数来判定private String[] data = new String[1000];private volatile int head = 0;// 队列的起始位置private volatile int tail = 0;// 队列的结束位置的下一个位置private volatile int size = 0;// 记录当前队列有效元素个数private Object locker = new Object();// 提供核心方法,入队列和出队列public void put(String elem) throws InterruptedException {synchronized (locker) {// 首先判断,队列满不满while (size == data.length) {// 阻塞, 等到有其他线程取元素的时候,再唤醒locker.wait();}// 队列没满,就真正的往里面放元素data[tail] = elem;tail = (tail + 1) % data.length;size++;locker.notify();// 这个 notify 用来唤醒 take 的 wait}}public String take() throws InterruptedException {synchronized (locker) {while (size == 0) {// 队列为空,阻塞,等到后面有其他线程添加元素后再唤醒locker.wait();}// 队列不空,就需要把 head 位置的元素给删除掉,并且返回String ret = data[head];head = (head + 1) % data.length;size--;// 这个 notify 用来唤醒 put 的 waitlocker.notify();return ret;}}
}

借助这个阻塞队列,我们就可以实现一个简单的生产者消费者模型,就是一个线程往里面添加元素,另一个线程从里面消费元素。

// 不写作泛型了,直接让这个队列里面存储字符串
class MyBlockingQueue {// 此处这里的最大长度,也可以指定构造方法,也可以构造方法的参数来判定private String[] data = new String[1000];private volatile int head = 0;// 队列的起始位置private volatile int tail = 0;// 队列的结束位置的下一个位置private volatile int size = 0;// 记录当前队列有效元素个数private Object locker = new Object();// 提供核心方法,入队列和出队列public void put(String elem) throws InterruptedException {synchronized (locker) {// 首先判断,队列满不满while (size == data.length) {// 阻塞, 等到有其他线程取元素的时候,再唤醒locker.wait();}data[tail] = elem;tail = (tail + 1) % data.length;size++;locker.notify();}}public String take() throws InterruptedException {synchronized (locker) {while (size == 0) {// 队列为空,阻塞,等到后面有其他线程添加元素后再唤醒locker.wait();}String ret = data[head];head = (head + 1) % data.length;size--;locker.notify();return ret;}}
}public class Demo24 {public static void main(String[] args) {// 生产者,消费者,分别使用一个线程表示。(也可以使用多个线程)MyBlockingQueue queue = new MyBlockingQueue();// 消费者Thread t1 = new Thread(() -> {while (true) {try {String num = queue.take();System.out.println("消费元素:" + num);Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});// 生产者Thread t2 = new Thread(() -> {int num = 1;while (true) {try {queue.put(num + "");System.out.println("生产元素:" + num);num++;} catch (InterruptedException e) {throw new RuntimeException(e);}}});t1.start();t2.start();}
}

运行结果,发现生产者一下子把队列生产满了,后面就是消费者消费一个元素,生产者生产一个元素。

3. 定时器

定时器是一个日常开发的常见组件。

约定一个时间,等时间到了之后,就会执行某个代码逻辑。

这个就跟闹钟差不多。

定时器非常常见,尤其是在进行网络通信的时候。

举个例子:

而 Java 标准库也给我们提供现成的定时器 Timer 类,来让我们使用。

// 定时器
public class Demo25 {public static void main(String[] args) {Timer timer = new Timer();// 给定时器安排了一个任务,预定在 xxx 时间去执行。timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("3000");}}, 3000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("2000");}}, 2000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("1000");}}, 1000);System.out.println("程序启动!");}
}

了解上述内容后,我们自己也可以模拟实现一个定时器。

刚刚也说了,定时器内部包含了一个扫描线程,以及一个任务对象的类,来专门表示任务和时间。

那我们也可以根据这两点来实现定时器:

1. 定时器内部需要有一个线程,负责扫描时间

2. 定时器可以添加多个任务,所以需要用一个数据结构来存放任务对象

3. 需要创建一个类,来描述任务(必须包括时间和任务)

那该用哪个数据结构来组织任务对象呢?

定时器跟时间有关,并且时间短的任务先执行,那我们就可以使用优先级队列来组织任务对象。

根据这些,我们就可以敲代码了,首先创建出两个类 MyTimer 和 MyTimerTask,分别用来表示定时器和任务,MyTimer 里需要存放任务列表,所以还需要添加一个优先级队列 queue(记得要传比较器),而 MyTimerTask 里包含了时间和任务,并为它们提供构造方法,MyTimer 构造方法就需要创建出一个线程,用来扫描任务,还有一个 schedule 方法,用来添加任务。

// 模拟实现一个简单的定时器
class MyTimer {private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>(new Comparator<MyTimerTask>() {@Overridepublic int compare(MyTimerTask o1, MyTimerTask o2) {// 时间小的先执行,建立小根堆return (int) (o1.getTime() - o2.getTime());}});public MyTimer() {Thread thread = new Thread(() -> {// 写扫描线程的逻辑});// 不要忘记启动线程thread.start();}public void schedule(MyTimerTask task) {queue.offer(task);}
}class MyTimerTask {private long time;private Runnable runnable;public MyTimerTask(long time, Runnable runnable) {// 这里记录绝对时间方便我们计算this.time = time + System.currentTimeMillis();this.runnable = runnable;}// 提供 getter 方法public long getTime() {return time;}public Runnable getRunnable() {return runnable;}
}

然后再来实现扫描线程的工作。

这个其实也很简单,

先判断队列是否为空,如果队列为空,就需要阻塞等待,直到有线程调用 schedule 方法,

不空的话,只要不停地看一下任务队列里的队首元素,看一下该元素的时间到没到,到了的话就执行任务,执行完任务后将任务从队列里弹出,

没到的话,就等时间到了再来执行任务,(也可以看做是懒,非必要,不采取行动)

而前面也说了,要让线程阻塞等待,那就可以使用 wait,而使用 wait 就必须得先加锁,此时,我们发现其实要进行阻塞的这几处,都有修改操作,schedule 是要往队列里面添加新任务,而扫描线程扫描任务列表,当时间到了,就得执行任务,执行完任务后就得将任务出队,那这样的话,我们加锁和使用 wait 就是顺理成章的事情了。

写完后就是这样的:

// 模拟实现一个简单的定时器
class MyTimer {private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>(new Comparator<MyTimerTask>() {@Overridepublic int compare(MyTimerTask o1, MyTimerTask o2) {// 时间小的先执行,建立小根堆return (int) (o1.getTime() - o2.getTime());}});public MyTimer() {Thread thread = new Thread(() -> {synchronized (this) {// 写扫描线程的逻辑while (true) {// 首先判断队列为不为空,空的话,就阻塞等待// 直到有线程调用 schedule 方法为止while (queue.isEmpty()) {// 要加锁// 阻塞等待try {this.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}MyTimerTask task = queue.peek();if (System.currentTimeMillis() >= task.getTime()) {// 时间到了,需要执行任务, 然后出队列task.getRunnable().run();queue.poll();} else {// 没到时间的话,就进行等待try {// 如果添加了新的任务,也需要将线程唤醒// 重新更新一下,最早的任务是什么,以及更新等待时间this.wait(task.getTime() - System.currentTimeMillis());} catch (InterruptedException e) {throw new RuntimeException(e);}}}}});// 最后要记得启动线程thread.start();}public void schedule(Runnable runnable, long time) {synchronized (this) {queue.offer(new MyTimerTask(time, runnable));this.notify();}}
}class MyTimerTask {private long time;private Runnable runnable;public MyTimerTask(long time, Runnable runnable) {// 这里记录绝对时间方便我们计算this.time = time + System.currentTimeMillis();this.runnable = runnable;}// 提供 getter 方法public long getTime() {return time;}public Runnable getRunnable() {return runnable;}
}

写完之后,我们就可以来测试一下,看看代码是否正确,就写个例子,调用下方法就行。

没什么问题,一个简单的定时器就写好了

4. 线程池

线程诞生的意义,是因为进程太重量。

而线程因为创建/销毁的时候不需要申请/释放资源,所以比进程快,线程也叫做轻量级进程。

但是,如果频繁的创建销毁线程,此时这个开销也是不容忽视的。

此时,就有两种提高效率的方法,第一种:协程。第二种:线程池。

协程也被叫做轻量级线程,它相比于线程,把系统调度的过程,给省去了,就是让我们自己来调度

但是很遗憾,在 Java 协程的圈子里,很少有人会用协程。

主要有两个原因:第一,Java 官方没有实现协程,虽然有第三方库实现了,但是不够权威也不够靠谱。    第二,Java 有线程池,有线程池兜着底,让线程也不至于太慢。

那到底什么是线程池呢?

可以先看看,这里面的池是什么意思。

池其实是计算机中一种重要的思想方法,很多地方都会涉及到(如线程池、进程池、内存池、连接池)。

举个例子来说明吧:

那么线程池,就是在使用第一个线程的时候,提前把线程 2、3、4、5....给创建出来,

如果后续想要使用新的线程,不用重新创建,而是直接从线程池里面拿就好,就能降低创建线程的开销。

Java 标准库提供了写好的线程池,来让我们使用。

1) ThreadPoolExecutor 类

ThreadPoolExecutor 类的功能非常丰富,提供了很多参数,上述标准库的几个方法,就是给这个类填写了不同的参数用来构造线程池。

我们也可以来学学这个类(面试会考)。

ThreadPoolExecutor 的核心方法就两个:1. 构造(构造方法参数很多)      2. 注册任务(添加任务)

我们来看看它的构造方法,直接看最下面的构造方法就行,因为这个的参数涵盖了上面的参数。

(有一说一,第一次看到这么多参数的构造方法,天都要塌了,但是理解之后其实还好)

2) 模拟实现线程池

了解以上这些后,我们可以自己来实现一个线程池。

线程池:写一个固定线程数目的线程池(暂时不考虑线程的增加和减少)

(1) 提供构造方法,指定创建多少个线程

(2) 在构造方法中,把这些线程都创建好

(3) 有一个阻塞队列,能够用来存放要执行的任务

(4) 提供 submit 方法,用来添加任务

有了以上思路,就很好写代码了。

// 模拟实现一个简单的线程池
class MyThreadPool {// 将创建好的线程放在数组里面,等到需要使用的时候就拿出来用private List<Thread> threadList = new ArrayList<>();// 拒绝策略就是阻塞等待,直到别的线程使用 submit 方法添加任务为止BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);// 任务队列// 通过这个方法,把任务添加到队列中public void submit(Runnable runnable) throws InterruptedException {// 此处我们的拒绝策略,相当于第五种拒绝策略了,阻塞等待(这是下策)queue.put(runnable);}public MyThreadPool(int n) {// 创建出 n 个线程,负责执行上述队列中的任务for (int i = 0; i < n; i++) {Thread t = new Thread(() -> {// 让每个线程不停的从队列中消费任务,如果没有任务了,// 那此时线程就会阻塞等待,直到有其他线程调用 submit 方法为止while (true) {// 让这个线程,从队列中消费任务,并进行执行try {Runnable runnable = queue.take();runnable.run();} catch (InterruptedException e) {throw new RuntimeException(e);}}});t.start();threadList.add(t);}}
}

写个简单的代码来测试一下:

没啥问题。

关键字:二维码生成器支持微信扫码_深圳和胜建设公司_怎么给客户推广自己的产品_做网站推广需要多少钱

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: