JavaWeb语法四:多线程案例
创始人
2024-04-29 21:38:32
0

目录

1.单例模式

1.1:饿汉模式

1.2:懒汉模式

 2.阻塞式队列

2.1:生产者消费者模型

2.2:阻塞队列的模拟实现

3.线程池

3.1:标准库中的线程池

3.2:模拟实现线程池


前言:前一篇我们讲了线程不安全的原因以及解决办法,接下来我们将讲多线程案例,进一步来了解线程。

1.单例模式

单例模式(单个实例,单个类,只能被创建一个实例。)是校招中最常考的设计模式。而设计模式是什么意思勒?

设计模式:就是针对一些典型的场景,给出一些典型的方案,就想棋谱一样。

1.1:饿汉模式

class singleton{private  static  singleton instance=new singleton();private singleton() {}public  static singleton getInstance(){return instance;}
}
public class ThreadDemo1 {public static void main(String[] args) {//singleton s=new singleton();//这里为啥报错了?singleton s1=singleton.getInstance();singleton s2=singleton.getInstance();System.out.println(s1==s2);//这个结果是true为啥了}
}

这个饿汉模式是怎么实现了只能创造一个实例,且实例是唯一的。

1.private static singleton instance=new singleton();

static 修饰的成员instance是类属性,相当于这个属性对应的内存空间在类对象中,而在这里类对象又是唯一的实例(是在类加载阶段,就把实例创建出来)这就让这个实例是唯一的。

类加载:运行一个java程序,就需要让java进程能够找到并读取对应的.class文件。就会读取文件内容并解析,构造成类对象,这一系列的过程就是类加载。

2.private singleton(){}:有private 修饰的构造方法是私有的,只能在singleton这个类进行使用,无法进行new 一个对象。

3. 只能调用getInstance 这个 方法来获得instance这个实例对象。所以是s1和s2是指向同一个实例对象。所以返回的true。


1.2:懒汉模式

class singletonLazy{private  static singletonLazy instance=null;public static singletonLazy getInstance(){if(instance==null){instance=new singletonLazy();}return  instance;}private singletonLazy(){}
}
public class ThreadDemo2 {public static void main(String[] args) {singletonLazy s1=singletonLazy.getInstance();singletonLazy s2=singletonLazy.getInstance();System.out.println(s1==s2);}
}

这个懒汉模式,在类加载的时候不创建实例,第一次使用的时候才创建实例。但这个是线程不安全的。那我们要了解一下,为啥是线程不安全的勒?

为啥是线程不安全的?

线程不安全是首次创建实例时,如果在多个线程中同时调用getInstance,就可能导致创建出多个实例。

 线程一调用com的时候,instance这个对象是 null,在进行创建instance这个实例的时候,线程二进行com的时候,那时候instance这个对象还是 null,也会进行创建instance这个实例。这样可能导致创建多个实例。

为了解决线程不安全。使用双重if判定。

给instance加上volatile。

 2.阻塞式队列

阻塞队列是一种特殊的队列,遵循"先进先出"的原则。

当队列满的时候,继续入队列就会阻塞,直到有其他线程从队列中取走元素。

当队列空的时候,继续出队列也会阻塞,直到其他线程往队列中插入元素。

阻塞队列的一个典型应用场景就是 "生产者消费者模型",这是一种非常典型的开发模型。

2.1:生产者消费者模型

优点一:能够让多个服务器之间解耦合。

 A 只需要和阻塞队列进行交互,不需要知道B是怎么运行的。哪怕B挂了,出现了问题,跟A也没有什么关系,A和B是陌生人。

B 只需要和阻塞队列进行交互,不需要知道A是怎么运行的,哪怕A挂了,出现了问题,跟B也没有什么关系,B和A是陌生人。

优点二:能过对于请求削峰填谷。

假如未使用生产者消费者模型

 

 A暴涨=>B暴涨

A作为入口服务器,计算量很轻,请求暴涨,问题不大,但B作为应用服务器,计算量可能很大,需要的系统资源也更多,如果请求更多,需要的资源进一步增加,如果主机的硬件不够,程序就会面临挂的风险。

A请求暴涨=>阻塞队列的请求暴涨。由于阻塞队列没啥计算量,就只是单纯的存个数据,就能抗住更大的压力,而B这边仍然按照原来的速度来消费数据,不会因为A的暴涨而引来暴涨,这样B就被保护的很好,就不会因为这种请求的波动而引起来崩溃。

”削峰“:请求不会一直暴涨下去,而带来的这种峰值也不会持续很久,就一阵,过去就恢复了。

”填谷":当请求很少的时候,B依然按照原来的频率来处理之前积压的数据。

2.2:阻塞队列的模拟实现

通过循环队列的方式方法来实现。

使用synchronized进行加锁控制。

put插入元素的时候,判定如果队列满了,就进行wait。

take 取出元素的时候,判定如果队列为空,就进行wait。

class  BlockingQueue {int[] queue = new int[3];int prev = 0;int tail = 0;int size = 0;Object lock=new Object();//入队列public void put (int value) throws InterruptedException {//判断队列是否为满synchronized (lock) {if (size >= queue.length) {lock.wait();//队列满了,进入阻塞队列}queue[tail] = value;size++;tail++;if (tail >= queue.length) {tail = 0;}//此处唤醒put队列为空的阻塞等待lock.notify();}}public Integer take() throws InterruptedException {//判断队列是否为空synchronized (lock) {if (size == 0) {lock.wait();//队列为空进入阻塞队列}int val = queue[prev];prev++;size--;if (prev >= queue.length) {prev = 0;}lock.notify();//此处唤醒put队列未满的阻塞队列return val;}}
}
public class ThreadDemo3 {public static void main(String[] args) throws InterruptedException {BlockingQueue blockingQueue=new BlockingQueue();//生产者Thread t=new Thread(()->{int num=0;while(true){try {System.out.println("生产了"+num);blockingQueue.put(num);num++;} catch (InterruptedException e) {throw new RuntimeException(e);}}});Thread t1=new Thread(()->{try {while(true) {int num = blockingQueue.take();System.out.println("消费了" + num);Thread.sleep(30);}} catch (InterruptedException e) {throw new RuntimeException(e);}});t.start();t1.start();}
}

标准库里面的阻塞队列

BlockingQueue blockingQueue=new LinkedBlockingQueue<>();

3.线程池

把线程提前创建好,放到线程池中,后面需要用线程,直接从池子取,就不必从系统这边申请。线程用完,也不是还给系统,而是返回池子中,以备下次再用。

3.1:标准库中的线程池

 创建固定线程数的线程池

ExecutorService pool= Executors.newFixedThreadPool(10)

这里10是指定创建10个线程的线程池。

创建线程数目动态增长

ExecutorService pool=Executors.newCachedThreadPool();

创建只包含单个线程的线程池

ExecutorService pool=Executors.newSingleThreadExecutor();


3.2:模拟实现线程池

 核心操作为submit,江任务加入到线程池中。

使用Worker类描述一个工作线程,使用runable描述一个任务。

使用一个BlockingQueue组织所有的任务。

每一个woker线程要做的事情,不停的从blockqueue中取任务并执行。

指定一个线程池中最大的线程数当当前线程数超过这个最大值时,就不再新增线程。
 

public class MyThreadPool {//使用一个数据结构来组织一个任务。BlockingDeque deque=new LinkedBlockingDeque<>();static  class  worker extends Thread {private BlockingDeque deque = null;public worker(BlockingDeque deque) {this.deque = deque;}@Overridepublic void run() {//循环去线程池中拿出任务while (true) {Runnable runnable = null;try {runnable = deque.take();runnable.run();} catch (InterruptedException e) {throw new RuntimeException(e);}}}}//创建一个数据结构来组织线程Listworks=new ArrayList<>();
//在构造方法中,就这些线程放到上述的线程池中public MyThreadPool(int n) {for(int i=0;i

总结:

以上就是我总结的多线程案例,若有错误或者有不对的,请各位铁子留言纠错,若感觉不错,请一键三连。

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
客厅放八骏马摆件可以吗(家里摆... 今天给各位分享客厅放八骏马摆件可以吗的知识,其中也会对家里摆八骏马摆件好吗进行解释,如果能碰巧解决你...
苏州离哪个飞机场近(苏州离哪个... 本篇文章极速百科小编给大家谈谈苏州离哪个飞机场近,以及苏州离哪个飞机场近点对应的知识点,希望对各位有...