linux入门---线程池的模拟实现

目录标题

  • 什么是线程池
  • 线程的封装
    • 准备工作
    • 构造函数和析构函数
    • start函数
    • join函数
    • threadname函数
    • 完整代码
  • 线程池的实现
    • 准备工作
    • 构造函数和析构函数
    • push函数
    • pop函数
    • run函数
    • 完整的代码
  • 测试代码

什么是线程池

在实现线程池之前我们先了解一下什么是线程池,所谓的池大家可以理解为一次性申请和创建很多的东西然后将其保存起来等未来需要的时候就不需要再创建和申请了,直接从保存的地方拿就行了,在生活中我们也经常用到池化技术比如说去菜场里面买菜并不是只卖一顿的菜而是买好几天要吃的菜,因为这么做的话可以大幅度的减少去菜场所带来的时间消耗,那么在计算机中也是同样的道理比如说使用函数new向操作系统申请10字节的空间时,操作系统并不是真的从内存中给你现场申请10字节的空间,而是当前的进程早就提前申请好了一大堆空间,把这部分空间保存下来,一旦你再申请的话将该空间的一部分给你就行并不会立马跑到内存上进行申请,这样做可以有效的提高程序的效率,那么这就是一个池化技术,那么这里的线程池也是同样的道理,我们可以先创建多个线程并将其保存下来,然后再创建一个容器用来接收任务,一旦有任务了就自动唤醒对应的线程将其进行处理即可无需再次创建,比如说下面的图片:
在这里插入图片描述
那么接下来我们将一步一步的实现线程池,首先来完成线程的封装。

线程的封装

准备工作

因为创建线程需要对应的执行函数和参数以及对应的pid_t变量,所以类中得有一个function对象和一个void*类型的指针和一个pid_td对象:

typedef std::function<void *(void *)> func_t;
 class Thread
{
 public:
 private:
     func_t func_;
     void *args_;
     pthread_t tid_;
 };

此外我们还希望该类能够有一个自己的名字,所以类中还有一个string的对象用来记录线程的名字,这里我们希望名字的形式为thread 1,thread 2…以此类推,所以我们在类中我们再添加一个静态的整形变量用来记录当前是创建的第几个线程,所以当前类的代码如下:

typedef std::function<void *(void *)> func_t;
 class Thread
{
 public:
 private:
     std::string name_;
     func_t func_;
     void *args_;
     pthread_t tid_;
     static int threadnum;
 };
 int Thread::threadnum = 1;

构造函数和析构函数

对于该类的设计我们是这样向的,创建类的时候并不创建对应的线程执行函数这个交给后面的start函数,构造函数要干的事情就是将该线程对应的名字创建好即可,那么这里创建一个char类型的数组,然后使用snprintf函数将对应的名字输入到这个数组里面,最后用这个数组的起始的地址构造string对象即可,那么这里的代码如下:

Thread()//构造函数初始化名字
{
    char namebuffer[num];//这里的num为全局变量后面会写
    snprintf(namebuffer, sizeof namebuffer, "thread-%d", threadnum++);
    name_ = namebuffer;
}

对于析构函数当前没有什么内容需要我们手动释放,所以析构函数什么都不用干:

~Thread()
{
    // do nothing
}

start函数

start函数干的事情就是使用pthread_start函数创建线程执行对应的函数,因为pthread_start函数需要调用对象以及函数的参数,所以strat函数需要两个参数,并将两个参数的值赋值给类中对应的对象:

void start(func_t func, void *args = nullptr)//start函数开始创建线程执行任务
{
    func_=func;
    args_=args;
}

然后我们就应该调用函数pthread_create来创建线程,这里本可以直接使用类内对象func_和args来完成线程的创建,但是我们这里另寻一路来带着大家回顾更多的知识,我们创建一个名为start_routine的类内函数,通过让pthread_create函数调用这个该来实现func_函数对象的调用

 void *start_routine(void *args) 
{}

我们知道pthread_create函数对要调用的函数是有要求的,要求它只有一个参数并且函数的返回值和参数的类型都得是void*,但是类内函数有一个特点就是自动的隐藏了一个this指针,也就是说上面的start_routine函数看上去只有一个参数实际上他有两个,所以这是不符合pthread_create函数的要求的,那么这里的解决办法就是将该函数改成静态函数,这样就没有了this指针但是这种做法又带来了另外一个问题就是func_是类内的非静态对象,静态函数是没有资格调用他的,所以为了解决这个问题我们在pthread_create函数传递参数的时候就传递一个this指针过去,这样静态函数就可以通过这个this指针访问非静态的成员或者函数:

static void *start_routine(void *args) 
{
    Thread *_this = static_cast<Thread *>(args);
}
void start(func_t func, void *args = nullptr)//start函数开始创建线程执行任务
{
    func_=func;
    args_=args;
    int n = pthread_create(&tid_, nullptr, start_routine, this);
    assert(n == 0);                                            
    (void)n;
}

可是这里还存在一个问题func_对象是私有的,通过类对象是访问不到的,所以这里我们还得创建一个接口函数用来访问func_对象,比如说下面的代码:

void *callback() 
{
	return func_(args_);
}//该函数调用类内的函数对象

然后就可以通过this指针调用这个callback函数调用func_对象:

void *callback() 
{
	return func_(args_);
}//该函数调用类内的函数对象
static void *start_routine(void *args) 
// 参数为指向该类对象的this指针所以可以访问到类内部的callback函数
{
    Thread *_this = static_cast<Thread *>(args);
    return _this->callback();
}
void start(func_t func, void *args = nullptr)//start函数开始创建线程执行任务
{
    func_=func;
    args_=args;
    int n = pthread_create(&tid_, nullptr, start_routine, this); 
    assert(n == 0);                                            
    (void)n;
}

那么这就是start函数的实现过程。

join函数

该函数的实现就非常的简单,直接调用pthread_join函数即可,然后创建一个变量记录一下返回值并判断返回值是否为0:

void join()
{
    int n = pthread_join(tid_, nullptr);
    assert(n == 0);
    (void)n;
}

threadname函数

该函数的作用就是返回线程的名字,所以该函数的实现就直接返回类内的string对象即可,那么这里的代码如下:

string threadname()
{
    return name_;
}

完整代码

#include <iostream>
#include <string>
#include <cstring>
#include <cassert>
#include <functional>
#include <pthread.h>
using namespace std;

namespace ThreadNs
{
    typedef std::function<void *(void *)> func_t;
    const int num = 1024;
    class Thread
    {
    private:
        // 在类内创建线程,想让线程执行对应的方法,需要将方法设置成为static
        static void *start_routine(void *args) 
        // 参数为指向该类对象的this指针所以可以访问到类内部的callback函数
        {
            Thread *_this = static_cast<Thread *>(args);
            return _this->callback();
        }
    public:
        Thread()//构造函数初始化名字
        {
            char namebuffer[num];
            snprintf(namebuffer, sizeof namebuffer, "thread-%d", threadnum++);
            name_ = namebuffer;
        }

        void start(func_t func, void *args = nullptr)//start函数开始创建线程执行任务
        {
            func_=func;
            args_=args;
            int n = pthread_create(&tid_, nullptr, start_routine, this); 
            assert(n == 0);                                            
            (void)n;
        }

        void join()
        {
            int n = pthread_join(tid_, nullptr);
            assert(n == 0);
            (void)n;
        }

        string threadname()
        {
            return name_;
        }

        ~Thread()
        {
            // do nothing
        }
        void *callback() { return func_(args_);}//该函数调用类内的函数对象
    private:
        std::string name_;
        func_t func_;
        void *args_;
        pthread_t tid_;
        static int threadnum;
    };
    int Thread::threadnum = 1;
} // end namespace ThreadNs

线程池的实现

准备工作

既然是线程池,那么肯定得有一个变量用来记录池内线程的个数,还得有一个数组用来存储创建出来的Thread对象的指针,因为线程要获取任务,所以我们还得添加一个队列用来存储待处理的任务,因为在获取数据和存放数据的过程中可能会遇到多线程所带来的线程安全问题所以得添加锁变量,又因为存放数据和获取数据的过程是互斥的所以这个锁变量只能有一个,那么当前的代码就如下:

template<class T>
class ThreadPool
{
public:

private:
    int _num;//表示线程池中线程的个数
    queue<T> _task_queue;//装载任务的队列
    vector<Thread*> _threads;//装载线程
    pthread_mutex_t _mutex;
    pthread_cond_t _cond;
};

构造函数和析构函数

构造函数只有一个参数用来表示当前的内存池有多少个线程并将该参数赋值给_num,然后在函数里面对条件变量和锁进行初始化,然后创建一个循环不断的创建Thread对象并将该对象的地址尾差到vector中

ThreadPool(int num=5)
:_num(num)
{
    pthread_mutex_init(&_mutex,nullptr);
    pthread_cond_init(&_cond,nullptr);
    //创建num个线程对象并将对象放到vector
    for(int i=0;i<num;i++)
    {
        _threads.push_back(new Thread());
    }
}

析构函数干的事情就是将条件变量和锁变量进行销毁,然后通过for循环将vector对象中指针指向的空间进行回收即可,那么这里的代码如下:

~ThreadPool()
{
    pthread_mutex_destroy(&_mutex);
    pthread_cond_destroy(&_cond);
    for(const auto& t:_threads)
    {
        delete t;
    }
}

push函数

该函数的作用就是往任务队列中查入数据,所以该函数需要一个const T&类型的参数:

void push(const T&in)
{
}

因为插入数据的过程可能会出现线程安全问题,所以函数的第一步就是先对其进行枷锁,然后调用队列中的push函数将参数插入到队列里面,此时的队列中肯定有数据,所以在此之后就使用pthread_cond_signal函数将条件变量上等待的线程进行唤醒最后解锁即可:

void push(const T&in)
{
    pthread_mutex_lock(&_mutex);
    _task_queue.push(in);
    pthread_cond_signal(&_cond);
    pthread_mutex_unlock(&_mutex);
}

pop函数

pop函数的作用是获取队列中的数据并删除,因为该函数是线程函数调用的,而多个线程在获取任务的时候要通过锁来保持互斥的关系,所以调用该函数的时候不用担心线程安全问题,所以在函数里面创建一个变量用来保存队列中的首元素数据并将其该数据删除,最后返回该变量即可:

T pop()
{
    T t=_task_queue.front();
    _task_queue.pop();
    return t;
}

run函数

run函数的功能就是将线程池中的每个线程都运行起来从队列中获取任务,那么这个时候就可以通过for循环的形式来调用Thread对象中的start函数来实现,因为start函数需要一个函数指针和一个参数,所以这里我们还得创建一个指定形式的函数,并且该函数还得是static类型

static void* handlerTask(void*args)
{}

在这个函数里面我们就可以实现从队列中获取任务并执行,所以该函数里面肯定得访问到类中的其他数据,所以我们传递给该函数的参数就是ThreadPool类型的this指针,但是这里我们还想实现一个功能就是在执行对应任务的时候想知道是哪个线程所执行的也就是知道对应线程的名字,所以按设想来说这里应该将对象的名字也传递给handlerTask函数,但是该函数只有一个参数传递了名字就传递不了this指针所以这里我们的做法就是再创建一个类,该类中有一个string对象用来存储名字和一个ThreadPool类型的指针用来访问类中的其他资源,那么这里的代码如下:

template<class T>
class ThreadPool;
template<class T>
class ThreadDate
{
public:
    ThreadPool<T>* threadpool;
    string name;
    ThreadDate(ThreadPool<T>*tp,const string& n)
    :name(n)
    ,threadpool(tp)
    {}
};
template<class T>
class ThreadPool
static void* handlerTask(void*args)
{}
public:
ThreadPool(int num=5)
:_num(num)
{
    pthread_mutex_init(&_mutex,nullptr);
    pthread_cond_init(&_cond,nullptr);
    //创建num个线程对象并将对象放到vector
    for(int i=0;i<num;i++)
    {
        _threads.push_back(new Thread());
    }
}
void run()
{}
~ThreadPool()
{
    pthread_mutex_destroy(&_mutex);
    pthread_cond_destroy(&_cond);
    for(const auto& t:_threads)
    {
        delete t;
    }
}
private:
    int _num;//表示线程池中线程的个数
    queue<T> _task_queue;//装载任务的队列
    vector<Thread*> _threads;//装载线程
    pthread_mutex_t _mutex;
    pthread_cond_t _cond;
};

那么在run函数里面要干的事情就是new一个ThreadDate对象,然后讲该对象的地址作为参数传递给start函数,并打印一句话用来表明当前的线程已经运转起来了:

void run()
{
    //这个函数就是将每个线程都启动
    for(const auto&t:_threads)
    {
        ThreadDate<T>*td = new ThreadDate<T>(this,t->threadname());
        t->start(handlerTask,td);
        cout<< t->threadname() << " start... " <<endl;
    }
}

虽然handlerTask函数拥有了this指针,但是锁变量和条件变量等等都是私有的外界是无法访问到的,所以这里还得添加一些接口函数用来访问这些成员变量:

void lockQueue() { pthread_mutex_lock(&_mutex); }//对锁上锁
void unlockQueue() { pthread_mutex_unlock(&_mutex); }//对锁进行解锁
bool isQueueEmpty() { return _task_queue.empty(); }//判断当前的队列是否为空
void threadWait() { pthread_cond_wait(&_cond, &_mutex); }//把线程放到条件变量上进行等待

有了这些函数之后handlerTask函数就可以实现了,首先将参数的类型进行转换然后使用lockQueue函数进行枷锁,枷锁成功之后就创建一个循环使用isQueueEmpty函数来判断当前的队列中是否有任务,如果没有的话就使用threadWait函数将该线程挂起

static void* handlerTask(void*args)
    //因为该函数是静态成员函数所以无法访问类中的分静态成员函数所以得传递this指针。
    //所以这里要么提供一些访问成员变量的接口要么提供一些功能的函数。
{
   ThreadDate<T>* td=static_cast<ThreadDate<T>*>(args);
   while(true)
   {
       td->threadpool->lockQueue();
       while(td->threadpool->isQueueEmpty())
       {
           td->threadpool->threadWait();
       }
}

while循环结束就表明当前的队列存在数据,所以此时就可以通过 td中的成员变量访问到pop函数从而获取到队列中的任务,因为任务的执行并不会收到多线程之间的影响,所以得到任务之后就可以使用unlockQueue函数进行解锁,然后执行任务并打印执行之后的结果,最外层的while循环结束之后就可以使用delete销毁td指针指向的对象并返回nullptr:

static void* handlerTask(void*args)
 //因为该函数是静态成员函数所以无法访问类中的分静态成员函数所以得传递this指针。
 //所以这里要么提供一些访问成员变量的接口要么提供一些功能的函数。
 {
     ThreadDate<T>* td=static_cast<ThreadDate<T>*>(args);
     while(true)
     {
         td->threadpool->lockQueue();
         while(td->threadpool->isQueueEmpty())
         {
             td->threadpool->threadWait();
         }
         T t=td->threadpool->pop();
         td->threadpool->unlockQueue();
         string result=t();
         cout<<td->name<<" 处理了任务 "<<t.toTaskString()<<" 处理的结果为:"<<result<<endl;
     }
     delete td;
     return nullptr;
 }

完整的代码

#include<iostream>
#include<vector>
#include"Thread.hpp"
#include"LockGuard.hpp"
#include"Task.hpp"
#include<queue>
#include<string>
#include<unistd.h>
using namespace std;
using namespace ThreadNs;
template<class T>
class ThreadPool;
template<class T>
class ThreadDate
{
public:
    ThreadPool<T>* threadpool;
    string name;
    ThreadDate(ThreadPool<T>*tp,const string& n)
    :name(n)
    ,threadpool(tp)
    {}
};
template<class T>
class ThreadPool
{
    static void* handlerTask(void*args)
    //因为该函数是静态成员函数所以无法访问类中的分静态成员函数所以得传递this指针。
    //所以这里要么提供一些访问成员变量的接口要么提供一些功能的函数。
    {
        ThreadDate<T>* td=static_cast<ThreadDate<T>*>(args);
        while(true)
        {
            td->threadpool->lockQueue();
            while(td->threadpool->isQueueEmpty())
            {
                td->threadpool->threadWait();
            }
            T t=td->threadpool->pop();
            td->threadpool->unlockQueue();
            string result=t();
            cout<<td->name<<" 处理了任务 "<<t.toTaskString()<<" 处理的结果为:"<<result<<endl;
        }
        delete td;
        return nullptr;
    }
public:
    ThreadPool(int num=5)
    :_num(num)
    {
        pthread_mutex_init(&_mutex,nullptr);
        pthread_cond_init(&_cond,nullptr);
        //创建num个线程对象并将对象放到vector
        for(int i=0;i<num;i++)
        {
            _threads.push_back(new Thread());
        }
    }
    void run()
    {
        //这个函数就是将每个线程都启动
        for(const auto&t:_threads)
        {
            ThreadDate<T>*td = new ThreadDate<T>(this,t->threadname());
            t->start(handlerTask,td);
            cout<< t->threadname() << " start... " <<endl;
        }
    }
    T pop()
    {
        T t=_task_queue.front();
        _task_queue.pop();
        return t;
    }
    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
        for(const auto& t:_threads)
        {
            delete t;
        }
    }
    void push(const T&in)
    {
        pthread_mutex_lock(&_mutex);
        _task_queue.push(in);
        pthread_cond_signal(&_cond);
        pthread_mutex_unlock(&_mutex);
    }
    void lockQueue() { pthread_mutex_lock(&_mutex); }
    void unlockQueue() { pthread_mutex_unlock(&_mutex); }
    bool isQueueEmpty() { return _task_queue.empty(); }
    void threadWait() { pthread_cond_wait(&_cond, &_mutex); }
private:
    int _num;//表示线程池中线程的个数
    queue<T> _task_queue;//装载任务的队列
    vector<Thread*> _threads;//装载线程
    pthread_mutex_t _mutex;
    pthread_cond_t _cond;
};

测试代码

我们可以用下面的代码来进行测试:

#include<iostream>
#include"ThreadPool.hpp"
using namespace std;
int main()
{
    ThreadPool<Task>* tp=new ThreadPool<Task>();
    tp->run();
    int x, y;
    char op;
    while(1)
    {
        std::cout << "请输入数据1# ";
        std::cin >> x;
        std::cout << "请输入数据2# ";
        std::cin >> y;
        std::cout << "请输入你要进行的运算#";
        std::cin >> op;
        Task t(x, y, op, mymath);
        std::cout << "你刚刚录入了一个任务: " << t.toTaskString() << "确认提交吗?[y/n]# ";
        char confirm;
        std::cin >> confirm;
        if(confirm == 'y') tp->push(t);
        sleep(1);
    }
}

代码的运行结果如下:
在这里插入图片描述
可以看到符合我们的预期那么这就是实现的过程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/134370.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【postgresql】CentOS7 安装pgAdmin 4

CentOS7 安装PostgreSQL Web管理工具pgAdmin 4。 pgAdmin 是世界上最先进的开源数据库 PostgreSQL 最受欢迎且功能丰富的开源管理和开发平台。 下载地址&#xff1a; pgadmin-4 download pgAdmin 4分为桌面版和服务器版。 我们这里部署服务器版本。 安装RPM包。 安装源 s…

【数据结构】树与二叉树(十一):二叉树的层次遍历(算法LevelOrder)

文章目录 5.2.1 二叉树二叉树性质引理5.1&#xff1a;二叉树中层数为i的结点至多有 2 i 2^i 2i个&#xff0c;其中 i ≥ 0 i \geq 0 i≥0。引理5.2&#xff1a;高度为k的二叉树中至多有 2 k 1 − 1 2^{k1}-1 2k1−1个结点&#xff0c;其中 k ≥ 0 k \geq 0 k≥0。引理5.3&…

最长有效括号

给你一个只包含 ‘(’ 和 ‘)’ 的字符串&#xff0c;找出最长有效&#xff08;格式正确且连续&#xff09;括号子串的长度。 class Solution {public int longestValidParentheses(String s) {Stack<Integer> st new Stack<Integer>();int ans 0;for(int i 0…

Linux C 时间编程

时间编程 Linux中时间相关命令时间编程time  获取当前的时间gmtime  获取当前日期时间localtime  获取本地时间日期asctime  规格时间结构体为字符串 Linux中时间相关命令 1&#xff09;date&#xff1a;打印当前的系统时间。 2&#xff09;date -s 20231111&#xff…

封神教程:腾讯云3年轻量应用服务器老用户购买方法

腾讯云轻量应用服务器特价是有新用户限制的&#xff0c;所以阿腾云建议大家选择3年期轻量应用服务器&#xff0c;一劳永逸&#xff0c;免去续费困扰。腾讯云轻量应用服务器3年优惠可以选择2核2G4M和2核4G5M带宽&#xff0c;3年轻量2核2G4M服务器540元&#xff0c;2核4G5M轻量应…

Linux 进程优先级 | 环境变量

目录 进程优先级 基本概念 认识优先级 PRI and NI NI值的范围 查看进程优先级 用top命令更改已存在进程的nice&#xff1a; 如何修改优先级 其他概念 环境变量 基本概念 常见环境变量 和环境变量相关的命令 环境变量的组织方式 通过代码如何获取环境变量 环境变量通…

arcgis基础篇--实验

一、绘制带空洞的面要素 方法一&#xff1a;先绘制出一个面区域&#xff0c;然后在面上再绘制一个面区域代表面洞&#xff0c;两者位于同一个图层内&#xff0c;选中代表面洞的区域&#xff0c;选择【编辑器】-【裁剪】工具&#xff0c;将面裁剪出一个洞&#xff0c;随后删除代…

SpringBoot--中间件技术-2:整合redis,redis实战小案例,springboot cache,cache简化redis的实现,含代码

SpringBoot整合Redis 实现步骤 导pom文件坐标 <!--redis依赖--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId> </dependency>yaml主配置文件&#xff0c;配置…

基于python+TensorFlow+Django卷积网络算法+深度学习模型+蔬菜识别系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 介绍了TensorFlow在图像识别分类中的应用&#xff0c;并通过相关代码进行了讲解。通过TensorFlow提供的工具和库&am…

K8S知识点(八)

&#xff08;1&#xff09;实战入门-Label 通过标签实现Pod的区分&#xff0c;说白了就是一种标签选择机制 可以使用命令是否加了标签&#xff1a; 打标签&#xff1a; 更新标签&#xff1a; 筛选标签&#xff1a; 修改配置文件&#xff0c;重新创建一个pod 筛选&#xff1…

Python---split()方法 + join()方法

split()方法 split 英 /splɪt/ v. 分裂&#xff0c;使分裂&#xff08;成不同的派别&#xff09;&#xff1b;分开&#xff0c;使分开&#xff08;成为几个部份&#xff09;&#xff1b;&#xff08;使&#xff09;撕裂&#xff1b;分担&#xff0c;分享&#xff1b;划破&…

kubeadm部署k8s及高可用

目录 CNI 网络组件 1、flannel的功能 2、flannel的三种模式 3、flannel的UDP模式工作原理 4、flannel的VXLAN模式工作原理 5、Calico主要组成部分 6、calico的IPIP模式工作原理 7、calico的BGP模式工作原理 8、flannel 和 calico 的区别 Kubeadm部署k8s及高可用 1、…

如何用 GPTs 构建自己的知识分身?(进阶篇)

&#xff08;注&#xff1a;本文为小报童精选文章&#xff0c;已订阅小报童或加入知识星球「玉树芝兰」用户请勿重复付费&#xff09; 有了这些改进&#xff0c;你可以快速判断 GPT 助手给出的答案是真实还是「幻觉」了。 问题 在《如何用自然语言 5 分钟构建个人知识库应用&am…

KVM虚拟机迁移原理与实践

虚拟机迁移 迁移(migration)包括系统整体的迁移和某个工作负载的迁移&#xff0c;系统整体迁移是将系统上的所有软件&#xff0c;包括操作系统&#xff0c;完全复制到另一台物理硬件机器上&#xff0c;而工作负载迁移仅仅迁移特定的工作负载。 虚拟化技术的出现&#xff0c;丰…

Day03:注意事项、this关键字、构造器、JavaBean、String、ArrayList

OOP的注意事项 类名要跟class文件名一致&#xff08;一个class可以有多个类&#xff0c;但只有一个public&#xff0c;且与文件名一致&#xff09;&#xff0c;命名介意大驼峰&#xff1b;如果某个对象没有变量指向他&#xff0c;就成垃圾对象了&#xff08;空指针&#xff09…

Redis(三)

4、分布式锁 4.1 、基本原理和实现方式对比 分布式锁:满足分布式系统或集群模式下多进程可见并且互斥的锁。 分布式锁的核心思想就是让大家都使用同一把锁,只要大家使用的是同一把锁,那么我们就能锁住线程,不让线程进行,让程序串行执行,这就是分布式锁的核心思路 那么…

FM8317-USB TYPE-C PD 多协议控制器

产品描述&#xff1a; FM8317是一款集成了USB Type-C、USB Power Delivery&#xff08;PD3.0&#xff09;、PPS的多协议端口控制器&#xff0c;为AC-DC适配器、车载充电器等设备提供高性价比的USB Type-C 端口充电解决方案。 FM8317内置的Type-C协议可以支持Type-C设备插入自动…

Netty - 回顾Netty高性能原理和框架架构解析

文章目录 概述JDK 原生 NIO 程序的问题Why Netty使用场景Related ProjectsNetty 高性能设计I/O 模型【阻塞 I/O】&#xff1a;【I/O 复用模型】【基于 Buffer】 线程模型事件驱动模型Reactor 线程模型Netty的线程模型异步处理 Netty框架的架构设计功能特性模块组件Bootstrap、S…

Linux驱动开发——PCI设备驱动

目录 一、 PCI协议简介 二、PCI和PCI-e 三、Linux PCI驱动 四、 PCI设备驱动实例 五、 总线类设备驱动开发习题 一、 PCI协议简介 PCI (Peripheral Component Interconnect&#xff0c;外设部件互联) 局部总线是由Intel 公司联合其他几家公司一起开发的一种总线标准&#…

2560 动物保护宣传网站设计JSP【程序源码+文档+调试运行】

摘要 本文介绍了一个动物保护宣传网站的系统的设计与实现。该系统包括前台用户模块和后台管理员模块&#xff0c;具有用户注册/登录、新闻、资源库、法律法规、图片赏析、留言板、关于我们、用户后台等功能。通过数据库设计和界面设计&#xff0c;实现了系统的基本功能&#x…