Go vs Java线程池

Go vs Java线程池

定义

  • 线程池(Thread Pool)是一种多线程编程中的资源管理技术,它预先创建一定数量的线程,并将它们放在一个池(pool)中待命。
  • 当有新的任务需要执行时,线程池会选择一个空闲线程来执行任务,而不是每次都创建新的线程。
  • 使用线程池的主要目的是为了减少线程的创建和销毁所带来的系统开销,提高系统性能,并更好地控制系统资源。

Java

工具类线程池实现

  • 方法名称说明
    public static ExecutorService newCachedThreadPool()创建一个没有上限的线程池(创建上限是int的最大值
    public static ExecutorService newFixedThreadPool(int nThreads)创建指定线程个数的线程池
    • Executors:线程池的工具类通过调用方法返回不同类型的线程池对象。
  • 工具类代码步骤

    • 创建线程池:ExecutorService pool = Executors.newCachedThreadPool();
    • 提交任务:pool.submit(new MyThread());,submit的对象是实现线程类的类
    • 销毁线程池:pool.shutdown();
  • 代码

    public class Main {
        public static void main(String[] args){
            //创建线程池
            ExecutorService pool = Executors.newFixedThreadPool(3);
            //提交任务
            pool.submit(new MyThread());
            pool.submit(new MyThread());
            pool.submit(new MyThread());
            pool.submit(new MyThread());
            pool.submit(new MyThread());
            //销毁线程池
            pool.shutdown();
        }
    }
    

自定义线程池

线程池的设计思路

线程池的思路和生产者消费者模型是很接近的

  1. 准备一个任务容器
  2. 一次性启动多个(2个)消费者线程
  3. 刚开始任务容器是空的,所以线程都在wait
  4. 直到一个外部线程向这个任务容器中扔了一个"任务",就会有一个消费者线程被唤醒
  5. 这个消费者线程取出"任务",并且执行这个任务,执行完毕后,继续等待下一次任务的到来

在整个过程中,都不需要创建新的线程,而是循环使用这些已经存在的线程。

代码实现

实现思路:

  • 创建一个线程池类(ThreadPool)
  • 在该类中定义两个成员变量poolSize(线程池初始化线程的个数) , BlockingQueue(任务容器)
  • 通过构造方法来创建两个线程对象(消费者线程),并且启动
  • 使用内部类的方式去定义一个线程类(TaskThread),可以提供一个构造方法用来初始化线程名称
  • 两个消费者线程需要不断的从任务容器中获取任务,如果没有任务,则线程处于阻塞状态。
  • 提供一个方法(submit)向任务容器中添加任务
  • 定义测试类进行测试

线程池类

public class ThreadPool {

    // 初始化线程个数
    private static final int DEFAULT_POOL_SIZE = 2 ;

    // 在该类中定义两个成员变量poolSize(线程池初始化线程的个数) , BlockingQueue<Runnable>(任务容器)
    private int poolSize = DEFAULT_POOL_SIZE ;
    private BlockingQueue<Runnable> blockingQueue = new LinkedBlockingQueue<Runnable>() ;

    // 无参构造方法
    public ThreadPool(){
        this.initThread();
    }

    // 有参构造方法,通过构造方法来创建两个线程对象(消费者线程),并且启动
    public ThreadPool(int poolSize) {
        if(poolSize > 0) {
            this.poolSize = poolSize ;
        }
        this.initThread();
    }

    // 初始化线程方法
    public void initThread(){
        for(int x = 0 ; x < poolSize ; x++) {
            new TaskThread("线程--->" + x).start();
        }
    }

    // 提供一个方法(submit)向任务容器中添加任务
    public void submit(Runnable runnable) {

        try {
            blockingQueue.put(runnable);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    // 使用内部类的方式去定义一个线程类
    public class TaskThread extends Thread {

        // 提供一个构造方法,用来初始化线程名称
        public TaskThread(String name) {
            super(name);
        }

        @Override
        public void run() {

            while(true) {

                try {

                    // 两个消费者线程需要不断的从任务容器中获取任务,如果没有任务,则线程处于阻塞状态。
                    Runnable task = blockingQueue.take();
                    task.run();

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }

        }
    }

}

测试类

public class ThreadPoolDemo01 {

    public static void main(String[] args) {

        // 创建线程池对象,无参构造方法创建
        // ThreadPool threadPool = new ThreadPool();
        ThreadPool threadPool = new ThreadPool(5);

        // 提交任务
        for(int x = 0 ; x < 10 ; x++) {
            threadPool.submit( () -> {
                System.out.println(Thread.currentThread().getName() + "---->>>处理了任务");
            });
        }

    }

}

使用无参构造方法创建线程池对象,控制台输出结果

线程--->0---->>>处理了任务
线程--->1---->>>处理了任务
线程--->0---->>>处理了任务
线程--->1---->>>处理了任务
线程--->0---->>>处理了任务
线程--->1---->>>处理了任务
线程--->0---->>>处理了任务
线程--->1---->>>处理了任务
线程--->0---->>>处理了任务
线程--->1---->>>处理了任务

通过控制台的输出,我们可以看到在线程池中存在两个线程,通过这2个线程处理了10个任务。

使用有参构造方法创建线程池对象,传递的参数是5,控制台输出结果

线程--->3---->>>处理了任务
线程--->4---->>>处理了任务
线程--->2---->>>处理了任务
线程--->0---->>>处理了任务
线程--->2---->>>处理了任务
线程--->4---->>>处理了任务
线程--->3---->>>处理了任务
线程--->1---->>>处理了任务
线程--->2---->>>处理了任务
线程--->0---->>>处理了任务

通过控制台的输出,我们可以看到在线程池中存在两个线程,通过这5个线程处理了10个任务。


线程池多大才合适

  • 计算公式(根据项目类型分类)
    • CPU密集型运算: 最大并行数 + 1
    • I/O密集型运算:最大并行数*期望CPU利用率*(总时间(CPU计算时间+等待时间))/CPU计算时间
  • 什么是最大并行数?
    • 最大并行数和自身电脑CPU配置有关,例如:4核8线程中,最大并行数就是8
    • 代码获取该数据:int count = Runtime.getRuntime().availableProcessors();

Go

  • Go语言没有内置任何线程池的函数,这是因为Go语言中存在goroutine和channel。使得线程池的实现十分的便捷简单。

  • 区别:

    • 基本原理和Java一样,只是实现的方式不同罢了。
  • 代码实现

  • 队列实现:将要处理的任务定义为空接口即可

    //队列
    package worker
    
    import (
    	"fmt"
    	"sync"
    )
    
    // Queue 创建工作
    type Queue struct {
    	//存储任务(数组队列
    	elements []interface{} //切片
    	lock     sync.Mutex    //任务锁
    	limit    int
    }
    
    // CreatQueue 建队
    func CreatQueue(limit int) *Queue {
    	return &Queue{
    		elements: make([]interface{}, 0, 1024),
    		lock:     sync.Mutex{},
    		limit:    limit,
    	}
    }
    
    // Push 入队
    func (q *Queue) Push(task interface{}) error {
    	//判满
    	if q.limit != -1 && len(q.elements) >= q.limit {
    		return fmt.Errorf("队列已经满员")
    	}
    	q.lock.Lock()
    	defer q.lock.Unlock()
    	q.elements = append(q.elements, task)
    	return nil
    }
    
    // Pop 出队
    func (q *Queue) Pop() (interface{}, error) {
    	//判空
    	if len(q.elements) == 0 {
    		return nil, fmt.Errorf("队列已空,读取不到任务")
    	}
    	task := q.elements[0]
    	q.elements = q.elements[1:]
    	return task, nil
    }
    
    // Len 队列长度
    func (q *Queue) Len() int {
    	return len(q.elements)
    }
    
    
  • 线程池代码实现

    package worker
    
    import (
    	"log"
    	"math"
    	"sync"
    )
    
    // Task  添加进池子的函数定义
    type Task func() interface{}
    
    // Pool 创建工作池
    type Pool struct {
    	worker  int              //定义并发数量
    	Tasks   *Queue           //任务队列
    	events  chan struct{}    //任务通知
    	Results chan interface{} //结果存储通道
    	wg      sync.WaitGroup   //同步锁
    }
    
    // NewPool 创建池子
    func NewPool(worker int) *Pool {
    	return &Pool{
    		worker:  worker,
    		Tasks:   CreatQueue(-1),
    		events:  make(chan struct{}, math.MaxInt),
    		Results: make(chan interface{}, worker*3),
    		wg:      sync.WaitGroup{},
    	}
    }
    
    // AddTask 将任务加入池子
    func (p *Pool) AddTask(task Task) {
    	err := p.Tasks.Push(task)
    	if err != nil {
    		log.Fatal(err)
    		return
    	}
    	p.events <- struct{}{}
    }
    
    // Start 启动线程池
    func (p *Pool) Start() {
    	//启动线程
    	for i := 0; i < p.worker; i++ {
    		p.wg.Add(1)
    		go func() {
    			for range p.events {
    				task, err := p.Tasks.Pop()
    				if err != nil {
    					continue
    				}
    				//处理任务(任务类型转换
    				if task, ok := task.(Task); ok {
    					//将处理结果放入Result管道中
    					p.Results <- task()
    				}
    			}
    			//结束线程
    			p.wg.Done()
    		}()
    	}
    }
    
    // Wait 关闭线程池
    func (p *Pool) Wait() {
    	//这个排序很有讲究
    	/*
    			1.先关闭events,使得不可再读取内容
    			2.结束线程
    			3.关闭结果通道
    		(2和3互换导致,task()向关闭的结果通道发送数据,引发panic)
    		(1不在第一位,就会产生线程死锁,原因是events有缓冲的)
    	*/
    	close(p.events)
    	p.wg.Wait()
    	close(p.Results)
    }
    
  • main函数代码

    package main
    
    import (
    	"fmt"
    	"src/worker"
    	"sync"
    )
    
    func main() {
    	//初始化池子
    	pool := worker.NewPool(5)
    
    	//将任务添加到线程池中
    	pool.AddTask(func() interface{} {
    		return 1
    	})
    	pool.AddTask(func() interface{} {
    		return 2
    	})
    	pool.AddTask(func() interface{} {
    		return 3
    	})
    
    	//启动线程
    	pool.Start()
    
    	var wg sync.WaitGroup
    	wg.Add(1)
    	go func() {
    		for result := range pool.Results {
    			fmt.Println(result)
    		}
    		wg.Done()
    	}()
    	//关闭线程
    	pool.Wait()
    
    	wg.Wait()
    
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我不吃牛肉!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值