多线程开发

Posted 聆听微风

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程开发相关的知识,希望对你有一定的参考价值。

多线程开发

异步:Async与Await简单用法

简单写法:

  1. 创建方法使用Async进行修饰,
  2. 在方法内部耗时操作前面使用Await修饰。

代码如下:

        public Form2()
        
            InitializeComponent();         
        

        private async void TaskTestFun()
        
            var task = await Task.Run(Fun).ConfigureAwait(true);
            Console.WriteLine(task.ToString());
            var d = 1;
            return;
        

        public string Fun()
        
            Thread.Sleep(5000);
            return "Sleep 5000 has Finished";
        

        private void button1_Click(object sender, EventArgs e)
        
            TaskTestFun();
        

运行现象描述:

当主进程进入TaskTestFun方法后,执行到创建Task方法后,就空闲了(表现为可以被拖动),这个时候,程序的运行指针还在var task那行代码上。

Task方法执行的时候,自己创建了一个工作子线程,用来执行动作,当动作执行完了以后,通知主进程获取var task的结果,让主线程继续执行下去。

图解如下:

  1. 当前主线程进入TaskTestFun()

  1. 主线程执行Task.Run方法(创建一个新的工作线程)

  1. 当前主界面可以拖动()说明主线程空闲,没有被阻塞=非阻塞

  2. 当Task任务结束即将以后

  3. 执行task完Return以后,工作子线程被销毁

  4. 最后输出子线程返回的信息

多线程并发(所有线程全结束了,才能继续往下运行)

要点:

  1. 创建List集合
  2. 使用WhenAll方法,等待所有的线程完成。

特点:这里跑的快的线程,先结束,然后要等待跑得慢的线程。大家一起结束才能继续往下面进行

    public partial class Form2 : Form
    

        public Form2()
        
            InitializeComponent();
            InitializeList();
        

        private void InitializeList()
        
            for (int i = 0; i < 9; i++)
            
                Targetlist.Add($" 任务i ");
            
        

        private List<string> Targetlist = new List<string>();

        List<Task<string>> Tasks = new List<Task<string>>();

        private async void TaskTestFun()
        
            Targetlist.ForEach(ItemActivation => Tasks.Add(Task.Run(Fun))) ;
            var task = await Task.WhenAll(Tasks).ConfigureAwait(false);
            foreach (var item in task)
            
                Console.WriteLine(task.ToString());
            
            return;
        

        public string Fun()
        
            Thread.Sleep(1000);
            return "This Task has Finished";
        

        private void button1_Click(object sender, EventArgs e)
        
            TaskTestFun();
        
    

异步回调:多线程多线程并发(快的线程,先结束,先往下跑)(主线程不等待所有线程都结束)

  1. 要点:

不再使用async 和await关键字

使用ContinueWith关键字

  1. 出现的问题:

    UI线程不再等待工作主线程任务是否结束,自己私下跑了

    public partial class Form2 : Form
    

        public Form2()
        
            InitializeComponent();
            InitializeList();
        

        private void InitializeList()
        
            for (int i = 0; i < 9; i++)
            
                Targetlist.Add($" 任务i ");
            
        

        private List<string> Targetlist = new List<string>();

        List<Task<string>> Tasks = new List<Task<string>>();

        private void TaskTestFun()
        
            int idx = 0;
            foreach (string item in Targetlist)
            
              Task.Run(Fun).ContinueWith(t => Console.WriteLine($"idx++ 当前任务结束"); );
            
            Console.WriteLine("所有任务已经结束");
            return;
        

        public string Fun()
        
            Thread.Sleep(1000);
            return "This Task has Finished";
        

        private void button1_Click(object sender, EventArgs e)
        
            TaskTestFun();
        
    

运行结果:

异步回调:多线程多线程并发(快的线程,先结束,先往下跑)(主线程等待所有线程都结束)

要点:

  1. 使用 Async 和Await 关键字标记UI主线程等待执行
  2. 任务方法的内部,编写事件。在外层注册事件和绑定回调的方法

情景介绍:幼儿园,有很多孩子在吃饭,吃完饭后自己要洗碗。当最后一个孩子把碗洗碗以后。老师会安排孩子们午休

输出结果:

代码逻辑:

编写自定义事件EatEvent,然后让Eat完成以后调用EatEvent注册的WashDishes方法。

主线程收到await Task.WhenAll(Tasks)影响,所以暂时不会继续往下走。

本质:任务动作方法,有编写触发的事件

    public partial class Form2 : Form
    
        /// <summary>
        ///  创建孩子集合
        /// </summary>
        List<Child> children = new List<Child>();



        public Form2()
        
            InitializeComponent();
            InitializeList();
        

        private void InitializeList()
        
            /// 初始化6个孩子
            for (int i = 0; i < 6; i++)
            
                Child child = new Child();
                ///注册洗碗事件
                child.EatFinish_Event += WashDishes;
                children.Add(new Child());
            
        

        /// <summary>
        /// 执行洗碗动作
        /// </summary>
        private void WashDishes(object sender, EatEvent e)
        
            Console.WriteLine("洗碗完成了");
        


        private void button1_Click(object sender, EventArgs e)
        
            TaskTestFun();
        

        private async void TaskTestFun()
         

            List<Task> Tasks = new List<Task>();
            foreach (var child in children)
            
                Tasks.Add(Task.Run(child.Eat));
            
            await Task.WhenAll(Tasks);
            Console.WriteLine("最后一个孩子吃完饭并且完成洗碗了");
            Console.WriteLine("老师让孩子去午休了");
            return;
        
    


    /// <summary>
    /// 自定义事件类
    /// </summary>
    public class EatEvent : EventArgs
    
    


    /// <summary>
    /// 孩子
    /// </summary>
    public class Child
    
        /// <summary>
        /// 吃饭用时
        /// </summary>
        public int Duration;

        public void Eat() 
            Thread.Sleep(Duration);
            Console.WriteLine("完成吃饭动作,将要洗碗");
            EatFinish_Event?.Invoke(this,new EatEvent());
        
        public Child() 

            Random rd = new Random();
            int n = rd.Next(5,10);
            Duration = n * 1000;
        

        public Action<object, EatEvent> EatFinish_Event;
    

ConfigureAwait用法

使用场合:含有UI的程序。(不含有UI界面的程序是没有影响的)

代码:ConfigureAwait(false)

    public partial class Form2 : Form
    
        public Form2()
        
            InitializeComponent();
        

        Stopwatch stopwatch = new Stopwatch();
        private void button1_Click(object sender, EventArgs e)
        
            stopwatch.Start();
            Console.WriteLine("main1 thread:" + Thread.CurrentThread.ManagedThreadId);
            TestConfigureAwait();
            Console.WriteLine("main2 thread:" + Thread.CurrentThread.ManagedThreadId);
        
        public async Task TestConfigureAwait()
        
            await Task.Run(() =>
            
                Thread.Sleep(1000);
                Console.WriteLine("Task thread :" + Thread.CurrentThread.ManagedThreadId);
            ).ConfigureAwait(false);
            Thread.Sleep(3000);
            Console.WriteLine("TestConfigureAwait thread : " + Thread.CurrentThread.ManagedThreadId);
            stopwatch.Stop();
            Console.WriteLine($"消耗时间stopwatch.Elapsed.TotalSeconds.ToString()");
        
    

输出结果:

代码:ConfigureAwait(true)

         public partial class Form2 : Form
    
        public Form2()
        
            InitializeComponent();
        

        Stopwatch stopwatch = new Stopwatch();
        private void button1_Click(object sender, EventArgs e)
        
            stopwatch.Start();
            Console.WriteLine("main1 thread:" + Thread.CurrentThread.ManagedThreadId);
            TestConfigureAwait();
            Console.WriteLine("main2 thread:" + Thread.CurrentThread.ManagedThreadId);
        
        public async Task TestConfigureAwait()
        
            await Task.Run(() =>
            
                Thread.Sleep(1000);
                Console.WriteLine("Task thread :" + Thread.CurrentThread.ManagedThreadId);
            ).ConfigureAwait(true);
            Thread.Sleep(3000);
            Console.WriteLine("TestConfigureAwait thread : " + Thread.CurrentThread.ManagedThreadId);
            stopwatch.Stop();
            Console.WriteLine($"消耗时间stopwatch.Elapsed.TotalSeconds.ToString()");
        
    

简单说明一下:

使用ConfigureAwait(false)时,下一句会从线程池里面取出一个线程执行下面的语句。ConfigureAwait(true)时,下面的代码会等到主线程,然后由主线程继续运行下去。

总结:对于带有UI的程序有效,为了减少时间损耗,建议尽量使用ConfigureAwait(false)。

Task Wait/WaitAny/WaitAll

Wait 阻塞

让调用的线程阻塞在Wait处。

只有三种方法可以让调用线程结束等待:

  1. 线程执行完毕
  2. 线程被取消
  3. 线程当前引发异常

代码案例:

    public partial class Form2 : Form
    
        public Form2()
        
            InitializeComponent();
            InitializeList();
        
        List<People> peoples = new List<People>();
        int count = 10;
        private void InitializeList()
        
            for (int i = 0; i < count; i++)
            
                peoples.Add(new People());
            
        


        private void button1_Click(object sender, EventArgs e)
        
            for (int i = 0; i < count; i++)
            
                Task task = Task.Run(() =>
                
                    peoples[i].Sleep();
                );
                task.Wait();
                Console.WriteLine("完成当前线程任务");

            
            Console.WriteLine("已经完成所有任务");
        
    

    public class People
    
        public People()  

        public void Sleep()
        
            Thread.Sleep(5000);
        
    

当前效果:

主要线程一直等待(阻塞),直到任务线程完成动作以后才能执行下面的语句。

WaitAny 阻塞部分线程

调用线程也会在线程阻塞。但是当有一个线程完成任务以后,调用线程就能被释放,继续下面的语句

代码:

    public partial class Form2 : Form
    

        Stopwatch Stopwatch = new Stopwatch();
        public Form2()
        
            InitializeComponent();
            InitializeList();
        
        List<People> peoples = new List<People>();
        int count = 10;
        private void InitializeList()
        
            for (int i = 0; i < count; i++)
            
                peoples.Add(new People());
            
        


        private void button1_Click(object sender, EventArgs e)
        
            Stopwatch.Start();
            List<Task> Tasks = new List<Task>();
            for (int i = 0; i < count-1; i++)
            
                Tasks.Add(
                   Task.Run(() =>
                      
                          peoples[i].Sleep();
                      
                      ));
            
            Task.WaitAny(Tasks.ToArray());
            Console.WriteLine("当前已经有一个任务已经完成");
        

        public class People
        
            public People()  

            public void Sleep()
            
                Console.WriteLine("执行睡觉任务");
                Thread.Sleep(3000);
            
        
    

当创建完所有的任务以后

此时先执行到Task.waitAny

主线程阻塞到Task.waitAny上

当有一个任务线程执行完成以后:主线程继续往下面执行,其他线程释放

WaitAll 阻塞全部的线程

当所有的工作线程都执行完成以后,主线程才能从阻塞的状态继续往下执行

task Result()

这个现在没学明白,后面再写

死锁

借用某个案例中的代码。

有可能的触发函数:ContinueWith(true) 切换主线程执行

原理:当主线程遇到task.result这类方法被阻塞线程时,子线程执行完成触发ContinueWith,调度主线程回来,但是主线程已经被阻塞。所以触发死锁

关键代码如下:

主线程函数:

        private void BtnDeadlock_Click(object sender, EventArgs e)
        
            #region 死锁演示
            Stopwatch sw = Stopwatch.StartNew();    //计时器
            resultTextBox.Clear();
            AppendLine("Wait开始......");

            for (int i = 0; i < Data.Books.Count; i++)
            
                var book = Data.Books[i];
                var idx = i + 1;
                var task = book.SearchAsync();
                AppendLine($"task.Result  task.Id.task.Status");
            

            sw.Stop();
            AppendLine($"Wait完成:Convert.ToSingle(sw.ElapsedMilliseconds) / 1000秒");
            #endregion
        

任务线程的执行函数:

        public async Task<string> SearchAsync()
        
            Stopwatch sw = Stopwatch.StartNew();
            await Task.Delay(Duration * 1000).ConfigureAwait(true);
            sw.Stop();
            return Result(sw.ElapsedMilliseconds);
        

当主线程到达SearchAsync方法后,遇到await关键字,知道当前方法为异步方法。所以继续往下执行,然后遇到task.Result需要获取线程返回结果,就被阻塞住了。与此同时,任务线程完成Delay任务,通过ConfigureAwait(true)方法来让主线程切换回来,切换失败。所以导致了死锁。

如果修改ConfigureAwait(false),那么导致的结果将是主线程一直阻塞,表现为界面卡住。

分析如下:ConfigureAwait(false),任务完成以后,当前系统从线程池里面抽取一个子线程来完成后续任务。但是当主线程只是在task.Result上等到需要的返回值以后,才能继续往下执行。所以表现为主线程能够往下执行,流程正确,但是主线程阻塞,界面卡住。

解决方法:

第一步:ConfigureAwait(false)

第二部:因为主线程需要接收到子线程的返回值来执行方法。

那么把子线程的执行方法封装为异步回调ContinueWith(),让主线程不要去接触这些内容,就能让主线程避免死锁

        private void BtnDeadlock_Click(object sender, EventArgs e)
        
            #region 死锁演示
            Stopwatch sw = Stopwatch.StartNew();    //计时器
            resultTextBox.Clear();
            AppendLine("Wait开始......");

            for (int i = 0; i < Data.Books.Count; i++)
            
                var book = Data.Books[i];
                var idx = i + 1;
                var task = book.SearchAsync().ContinueWith(t => InvworkAppendLine($"idx.t.Result"));
            

            sw.Stop();
            AppendLine($"Wait完成:Convert.ToSingle(sw.ElapsedMilliseconds) / 1000秒");
            #endregion
        

线程的暂停,继续和取消

ManualResetEvent

ManualResetEvent是一个通过信号机制,实现线程间状态同步的类。常用的方法有以下三个:

WaitOne:阻止当前线程,直到收到信号

Reset:将事件状态设置为非终止状态,导致线程阻止

Set:将事件状态设置为终止状态,从而允许继续执行一个或多个等待线程

CancellationTokenSource

CTS是用来发出取消标记,线程和任务根据CTS发出的信号,在自行决定在合适的位置取消线程或任务。

CTS是发出取消标记方式有2种 自动和手动:

1、手动通过调用Cancled()发出取消标记。

2、自动方式通过定时器、指定时间后取消信号。

    public partial class FormComm : Form
    

        private CancellationTokenSource? TokenSource;
        private ManualResetEvent? ManualReset;

        public FormComm()
        
            InitializeComponent();
        

        private void Print(string text)
        
            BeginInvoke(() =>
            
                richTextBox1.AppendText(text);
                richTextBox1.ScrollToCaret();
                richTextBox1.Refresh();
            );
        

        private void BtnStart_Click(object sender, EventArgs e)
        
            TokenSource = new();
            ManualReset = new(true);
            int i = 0;
            Task.Run(() =>
            
                while (!TokenSource.Token.IsCancellationRequested)
                
                    ManualReset.WaitOne();  //根据是否收到信号判断是否阻塞当前线程
                    Thread.Sleep(200);
                    Print($"线程【Environment.CurrentManagedThreadId】正在运行第++i次Environment.NewLine");
                
            , TokenSource.Token);
        

        private void BtnPause_Click(object sender, EventArgs e)
        
            ManualReset?.Reset();
        

        private void BtnContinue_Click(object sender, EventArgs e)
        
            ManualReset?.Set();
        

        private void BtnStop_Click(object sender, EventArgs e)
        
            TokenSource?.Cancel();
        
    

扩展:取消令牌CancellationTokenSource的用法

第一种用法:注册事件

当执行取消动作以后,将会执行注册的事件

        private void button1_Click(object sender, EventArgs e)
        
            CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(3000);
            CancellationToken cancellationToken = cancellationTokenSource.Token;
            cancellationToken.Register(() => System.Console.WriteLine("我被取消了."));
            System.Console.WriteLine("先等五秒钟.");
            System.Console.WriteLine("手动取消.");
            cancellationTokenSource.Cancel();

        

第二种用法:定时取消

通过构造函数,传入时间值

//设置3000毫秒(即3秒)后取消
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(3000);
CancellationToken cancellationToken = cancellationTokenSource.Token;
cancellationToken.Register(() => System.Console.WriteLine("我被取消了."));
System.Console.WriteLine("先等五秒钟.");
System.Console.WriteLine("手动取消.")
cancellationTokenSource.Cancel();

CancelAfter方法,传入时间值

CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
cancellationTokenSource.Token.Register(() => System.Console.WriteLine("我被取消了."));
//五秒之后取消
cancellationTokenSource.CancelAfter(5000);
System.Console.WriteLine("不会阻塞,我会执行.");

第三种用法:关联取消

设置一组关联的CancellationTokenSource,当其中的一个被取消了,那么其他的都被取消

        private void button1_Click(object sender, EventArgs e)
        
            //声明几个CancellationTokenSource
            CancellationTokenSource tokenSource = new CancellationTokenSource();
            CancellationTokenSource tokenSource2 = new CancellationTokenSource();
            CancellationTokenSource tokenSource3 = new CancellationTokenSource();

            tokenSource2.Token.Register(() => System.Console.WriteLine("tokenSource2被取消了"));

            //创建一个关联的CancellationTokenSource
            CancellationTokenSource tokenSourceNew = CancellationTokenSource.CreateLinkedTokenSource(tokenSource.Token, tokenSource2.Token, tokenSource3.Token);
            tokenSourceNew.Token.Register(() => System.Console.WriteLine("tokenSourceNew被取消了"));
            //取消tokenSource2
            tokenSource2.Cancel();
        

第四种用法 判断取消(一般作为多线程令牌)

CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken cancellationToken = tokenSource.Token;
//打印被取消
cancellationToken.Register(() => System.Console.WriteLine("被取消了."));
//模拟传递的场景
Task.Run(async ()=> 
    while (!cancellationToken.IsCancellationRequested)
    
        System.Console.WriteLine("一直在执行...");
        await Task.Delay(1000);
    
);
//5s之后取消
tokenSource.CancelAfter(5000);

多线程开发,先学会线程池吧

思维导图

前言

在实际开发场景中,我们经常要使用多线程开发应用,比如实现异步操作,或者为了提高程序的效率等等。但是以前我见过有实习生在使用的时候是直接new Runable(),然后start()。没有使用线程池,可能很多初学者对线程池在多线程开发中没有足够的认识,所以我写一篇文章讲讲线程池,希望对大家有所启发。

一、什么是线程池

线程池借鉴了"池化"技术的思想,线程池能够对线程的生命周期进行管理,对线程重复利用,并且能够以一种简单的方式将任务的提交与执行相解耦。

二、为什么使用线程池

一种技术的出现,肯定是要解决存在的问题。如果不用线程池,会怎么样呢?很简单,需要时创建线程,线程跑完销毁,如果频繁去做这两个动作,就会造成比较大的资源消耗。所以线程池主要就是解决这个问题。

因此在《java并发编程的艺术》书中就提到以下几点:

  • **降低资源消耗。**通过重复使用已创建的线程,降低线程创建和销毁造成的资源消耗。
  • **提高响应速度。**当有任务到达时,任务可以不需要的等到线程创建就能立即执行。
  • **提高线程的可管理性。**使用线程池可以进行统一的分配,调优和监控。

三、Executor

创建线程池主要使用ThreadPoolExecutor这个类,所以我们先看一张类图。

多线程开发,先学会线程池吧

一般来说,遵守面向接口编程的思想,我们都喜欢使用ExecutorService接口接收线程池实例。如下:

public static void main(String[] args) throws Exception {
    //创建线程池
    ExecutorService executor = new ThreadPoolExecutor(10101, TimeUnit.SECONDS, new LinkedBlockingDeque<>(10));
}

这里可以看到创建线程池是使用ThreadPoolExecutor构造器来创建。构造器的参数有什么意义呢,继续往下看。

3.1 七个关键参数

/**
* corePoolSize 核心线程数
* maximumPoolSize 最大线程数
* keepAliveTime 线程存活时间
* unit keepAliveTime的时间单位,有日,小时,分钟,秒等等
* workQueue 工作队列
* threadFactory 线程工厂,用于创建线程
* handler 饱和策略
*/

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)
 
{
 //省略...
}

那么这7个参数,在线程池工作时,起到什么作用呢?直接看一张图就明白了。

多线程开发,先学会线程池吧

这里有两个参数需要讲解一下,工作队列workQueue和饱和策略handler。

工作队列的类是BlockingQueue,是一个接口,我们先看看类图,看一下有哪些子类可以使用。

多线程开发,先学会线程池吧

可以看到有很多实现的子类,功能也各有不同。下面讲几个有代表性的。

DelayQueue是无界的队列,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走。

LinkedBlockingDeque是基于双向链表实现的双向并发阻塞队列,该阻塞队列同时支持FIFO和FILO两种操作方式,即可以从队列的头和尾同时操作(添加或删除);并且该阻塞队列是支持线程安全。可以指定队列的容量,如果不指定默认容量大小是Integer.MAX_VALUE

ArrayBlockingQueue是基于数组实现的有界阻塞队列,此队列按先进先出的原则对元素进行排序。新元素插入到队列的尾部,获取元素的操作则从队列的头部进行。

PriorityBlockingQueue是带优先级的无界阻塞队列,每次出队都返回优先级最高或者最低的元素(规则可以通过实现Comparable接口自己制定),内部是使用平衡二叉树实现的,遍历不保证有序。

饱和策略只要看RejectedExecutionHandler接口,以及其实现子类。

多线程开发,先学会线程池吧

饱和策略主要有四种,如果要自定义饱和策略也很简单,实现RejectedExecutionHandler接口,重写rejectedExecution()方法即可。下面介绍JDK里的四种饱和策略。

  • AbortPolicy,直接抛出异常,简单粗暴。
  • CallerRunsPolicy,在任务被拒绝添加后,会调用当前线程池的所在的线程去执行被拒绝的任务。
  • DiscardPolicy,什么都不做,既不抛出异常,也不会执行。
  • DiscardOldestPolicy,当任务被拒绝添加时,会抛弃任务队列中最旧的任务(也就是最先加入队列的任务),再把这个新任务添加进去。

3.2 Executors

Executors类提供了四种线程池,根据使用不同的参数去new ThreadPoolExecutor实现。简单介绍一下。

第一种是newFixedThreadPool,这是创建固定大小的线程池,核心线程数和最大线程数都设置相同的值,使用LinkedBlockingQueue作为工作队列,当corePoolSize满了之后就加入到LinkedBlockingQueue队列中。LinkedBlockingQueue默认大小为Integer.MAX_VALUE,所以会有OOM的风险。

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

第二种是newSingleThreadExecutor,创建线程数为1的线程池,并且使用了LinkedBlockingQueue,核心线程数和最大线程数都为1,满了就放入队列中,执行完了就从队列取一个。也就是创建了一个具有缓冲队列的单线程的线程池。跟上面的问题一样,队列的容量默认是Integer.MAX_VALUE,也会有OOM的风险。

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(11,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

第三种是newCachedThreadPool,创建可缓冲的线程池,没有大小限制。核心线程数是0,最大线程数是Integer.MAX_VALUE,所以当有新任务时,任务会放入SynchronousQueue队列中,SynchronousQueue只能存放大小为1,所以会立刻新起线程。如果在工作线程在指定时间(60秒)空闲,则会自动终止。

public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>(),
                                  threadFactory);
}

第四种是newScheduledThreadPool,支持定时及周期性任务执行的线程池。

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

3.3 使用规范

在阿里java开发规范中,是强制不允许使用Executors创建线程池,我们不妨看看。

多线程开发,先学会线程池吧

假如有人头铁不信,那我们写一段代码模拟一下。

public class ThreadTest {
    private static AtomicInteger num = new AtomicInteger();
    public static void main(String[] args) throws Exception {
        //创建线程池
        ExecutorService executor = Executors.newCachedThreadPool();
        while (true) {
            executor.execute(() -> {
                try {
                    System.out.println("线程数:" + num.incrementAndGet());
                    Thread.sleep(10000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
    }
}

然后设置JVM的参数-Xms5M -Xmx5M,运行一小段时间,就会看到报错了。

第二个问题是线程数的设置,设置多少线程数比较合适呢?

如果是cpu密集型的应用,cpu密集的意思是执行的任务大部分时间是在做计算和逻辑判断,这种情况显然不能设置太多的线程数,否则花在线程之间的切换时间就变多,效率就会变得低下。所以一般这种情况设置线程数为cpu核数+1即可。

cpu核数可以通过Runtime获取。

Runtime.getRuntime().availableProcessors()

如果是IO密集型的应用,IO密集的意思是执行的任务需要执行大量的IO操作,比如网络IO,磁盘IO,对CPU的使用率较低,因为在IO操作的特点需要等待,那么就可以把CPU切换到其他线程。所以可以设置线程数为CPU核数的两倍+1。

絮叨

经过学习之后,我们就要养成使用多线程不能直接new一个Thread,然后start(),要有使用线程池的意识。其次要理解线程池参数的意义,根据实际情况去设置。

并发编程往往是实际开发中比较容易出问题,希望看完这篇文章能减少一些不必要的错误。

觉得有用就点个赞吧,你的点赞是我创作的最大动力~

我是一个努力让大家记住的程序员。我们下期再见!!!

能力有限,如果有什么错误或者不当之处,请大家批评指正,一起学习交流!

以上是关于多线程开发的主要内容,如果未能解决你的问题,请参考以下文章

iOS多线程开发---线程管理

Android开发之路-多线程

iOS学习——多线程开发(NSThread)

iOS开发多线程在实际项目中的运用

多线程开发,先学会线程池吧

iOS开发——多线程