乐观学习,乐观生活,才能不断前进啊!!!
我的主页:optimistic_chen
我的专栏:c语言 ,Java
欢迎大家访问~
创作不易,大佬们点赞鼓励下吧~
文章目录
- 前言
- 单例模式
- 实现单例模式
- 饿汉模式·
- 懒汉模式
- 阻塞队列
- 生产者消费者模型
- 标准库中的阻塞队列
- 模拟实现阻塞队列(生产者消费者模型)
- 线程池
- 为什么从线程池去线程更高效?
- 标准库中的线程池
- 实现线程池
- 完结
前言
之前博客对多线程的是什么和基本内容都有详细了解,目前对于多线程的运用还很浅显,不能发挥出多线程应有的实力。这篇博客将带来多线程的基本应用,它会用到什么地方?又会带来什么高效的运行效率?我们又会学到什么?诸位尽情期待…
单例模式
单例模式是设计模式之一
两个新名词,什么是设计模式?什么又是单例模式呢?
设计模式相当于棋谱,是行业内大佬为新人撰写的一些高效便捷的思路和案例。棋手除了不断下棋提高实力外,就是尽可能多的研读棋谱,解决问题;而对于程序员来讲,设计模式就是棋手的棋谱,面对一些棘手的问题时,设计模式就是我们的制胜法宝,更加重要的是,设计模式可以大大减少程序员代码风格的不同(因为大家都是根据“棋谱”解决问题)
而单例模式就是众多“棋谱”中的一种,保证某个类在程序中只存在唯⼀⼀份实例(对象), ⽽不会创建出多个实例(只能new一个对象)
实现单例模式
单例模式的实现方式很多,最常见的就是”饿汉”和“懒汉“两种:主要区别就是创建对象的时机:创建对象早,就是饿汉模式(懂得都懂);反之,创建对象迟,就是懒汉模式。
这里一个关键是:构造方法设为 private(使单例模式生效)
饿汉模式·
在初始化(类加载)的时候就new好对象(提早)
class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {//获取实例
return instance;//读操作
}
}
实例提前创建过,就不涉及线程安全问题,只是一个单纯的读操作。
懒汉模式
使用时再 new 对象(能不创建就不创建)
class Singleton {
private static volatile Singleton instance = null;
Object locker=new Object();
private Singleton() {
}
public static Singleton getInstance() {//获取实例
if (instance == null) {
synchronized (locker) {
if (instance == null) {//当instance为空,创建实例
instance = new Singleton();
}
}
}
return instance;
}
}
阻塞队列
阻塞队列能是⼀种线程安全的数据结构,也遵守“先进先出”的基本原则,并且具有以下特性:
• 天然线程安全
• 当队列满的时候, 继续⼊队列就会阻塞, 直到有其他线程从队列中取⾛元素.
• 当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插⼊元素.
最主要的应用场合是 生产者消费者模型
生产者消费者模型
<举个例子> 过年包饺子,我负责擀饺子皮,妈妈和爸爸负责包饺子,持续一段时间后,整个工作结束。整个场景的资源是“饺子皮”,我是“生产者”,爸爸妈妈是“消费者”,这个场景就是“阻塞队列” |
生产者消费者模型的优势:
- 解耦合
- 削峰填谷
生产者消费者模型要付出的代价:
1. 整体结果趋于复杂化,需要更多机器,生产环境难以管理
2. 影响效率
标准库中的阻塞队列
public interface BlockingQueue<E> extends Queue<E>{
}
BlockingQueue 是⼀个接⼝.所以不能直接去new, 其真正实现的类是 LinkedBlockingQueue.
本来,队列入队和出队操作是offer和poll,但是我们这里是多线程情况,得使用带有阻塞功能的put和take。
比如:当队列为空时,却被take时,就会出现阻塞
这是完美运行的生产者消费者模型:
public static void main(String[] args) throws InterruptedException {
BlockingQueue<Integer> queue=new LinkedBlockingQueue<>(1000);
Thread producer=new Thread(()->{
int n=0;
while(true){
try {
queue.put(n);
n++;
System.out.println("生产元素"+n);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"producer");
Thread consumer=new Thread(()->{
while(true){
try {
Integer n=queue.take();
System.out.println("消费元素"+n);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"consumer");
producer.start();
consumer.start();
}
模拟实现阻塞队列(生产者消费者模型)
import java.util.concurrent.BlockingQueue;
class MyBlockQueue{
private String[] data=null;
private int head;
private int tail;
private int size;
public MyBlockQueue(int capacity){
data=new String[capacity];
}
public void put(String elem) throws InterruptedException {
synchronized (this){
while(size>=data.length){
//队列满了,阻塞
this.wait();//此时阻塞,当队列不满时,唤醒
}
data[tail]=elem;
tail++;
if(tail>=data.length){
tail=0;
}
size++;
this.notify();
}
}
public String take() throws InterruptedException {
synchronized (this){
while(size==0){//while二次判定队列是否为空
//队列为空,阻塞
this.wait();//队列不空时,唤醒
}
String ret=data[head];
head++;
if(head>=data.length){
head=0;
}
size--;
this.notify();
return ret;
}
}
}
public class Demo12 {
public static void main(String[] args) {
MyBlockQueue queue=new MyBlockQueue(1000);
Thread producer=new Thread(()->{
int n=0;
while(true){
try {
queue.put(n+"");
n++;
System.out.println("生产元素"+n);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"producer");
Thread consumer=new Thread(()->{
while(true){
try {
String n=queue.take();
System.out.println("消费元素"+n);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"consumer");
producer.start();
consumer.start();
}
}
线程池
对比常量池来看,字符串常量,在Java程序运行最初的时候就构建好了,等程序运行的时候,这些常量就加载到内存中;后续使用这些字符串时,直接从内存中获取,避免了字符串构造/销毁的开销。
线程池也是这样,提前创建好要使用的线程,以后要用的时候就能减少每次创建、销毁线程的损耗,提高性能。
<举个例子>其实大家可以带入“鱼塘”,想吃鱼了,捞一条上来。鱼塘里很多鱼,减少了外出购买的时间,是不是降低了损耗... |
为什么从线程池去线程更高效?
首先,一个操作系统是它的内核与配套的应用程序组成。内核给应用程序提供稳定的运行环境,同时管理硬件设备。
创建线程需要操作系统内核的配合,但是内核只有一个,如果同时出现多个要求创建线程的需求,这个时候还能保证高效吗?但是如果从线程池中取现成的线程,就不需要内核的参与,就不会出现“拥挤”的情况。
<举个例子>使用线程池就是去银行ATM机上取钱,创建线程是去银行柜台取钱。生活中,我们都知道ATM的效率高。 |
所以,使用线程池就可以省下应用程序切换到内核运行这样的消耗。
标准库中的线程池
核心方法:submit(Runnable)
通过Runnnable描述一段要执行的任务,通过submit把任务放到线程池中,此时线程池里的线程就会执行任务。本质上还是生产者消费者模型,submit在生产任务,线程池在消费任务。
构造这个类时,构造方法比较复杂,它的参数特别多…
主要看参数最多的构造方法,掌握整个其他的也就拿下了。
BlockingQueue workQueue:线程池允许我们程序员自己传一个工作队列,这可操作性就很高,我们可自主选择队列的基本参数,包括它的底层实现。
ThreadFactory threadFactory:创建线程的工厂,设计模式的一种,和前面的单例模式属于并列关系。为了统一构造并初始化线程。
工厂方法的核心:通过静态方法,把构造对象new的过程、各种初始化的过程,都封装为一个方法;提供多组静态方法,实现不同情况的构造。提供工厂方法的类就是工厂类。
RejectedExecutionHandler handler:拒绝策略,入队时,队列满了,不会真的造成阻塞(拒绝等待),而是执行下面拒绝策略的代码(提高效率)
实现线程池
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
class MyThreadPool{
private BlockingQueue<Runnable> queue=new LinkedBlockingQueue<>();
public void submit(Runnable runnable) throws InterruptedException {
queue.put(runnable);
}
public MyThreadPool(int n){
for(int i = 0; i<n; i++){
Thread t=new Thread(()->{
while(true){
try {
Runnable runnable=queue.take();
runnable.run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
}
public class Demo14 {
public static void main(String[] args) throws InterruptedException {
MyThreadPool pool=new MyThreadPool(4);
for(int i=0;i<100;i++){
pool.submit(()->{
System.out.println("hello "+Thread.currentThread().getName());
});
}
}
}
完结
可以点一个免费的赞并收藏起来~
可以点点关注,避免找不到我~ ,我的主页:optimistic_chen
我们下期不见不散 ~ ~ ~