上海做网站费用,一个商城网站开发要多少时间,用wordpress 扒站,中介房产管理软件推荐多线程相对于其他 Java 知识点来讲#xff0c;有一定的学习门槛#xff0c;并且了解起来比较费劲。在平时工作中如若使用不当会出现数据错乱、执行效率低(还不如单线程去运行)或者死锁程序挂掉等等问题#xff0c;所以掌握了解多线程至关重要。本文从基础概念开始到最后的并…多线程相对于其他 Java 知识点来讲有一定的学习门槛并且了解起来比较费劲。在平时工作中如若使用不当会出现数据错乱、执行效率低(还不如单线程去运行)或者死锁程序挂掉等等问题所以掌握了解多线程至关重要。本文从基础概念开始到最后的并发模型由浅入深讲解下线程方面的知识。概念梳理本节我将带大家了解多线程中几大基础概念。并发与并行并行表示两个线程同时做事情。并发表示一会做这个事情一会做另一个事情存在着调度。单核 CPU 不可能存在并行(微观上)。临界区临界区用来表示一种公共资源或者说是共享数据可以被多个线程使用。但是每一次只能有一个线程使用它一旦临界区资源被占用其他线程要想使用这个资源就必须等待。阻塞与非阻塞阻塞和非阻塞通常用来形容多线程间的相互影响。比如一个线程占用了临界区资源那么其它所有需要这个资源的线程就必须在这个临界区中进行等待等待会导致线程挂起。这种情况就是阻塞。此时如果占用资源的线程一直不愿意释放资源那么其它所有阻塞在这个临界区上的线程都不能工作。阻塞是指线程在操作系统层面被挂起。阻塞一般性能不好需大约8万个时钟周期来做调度。非阻塞则允许多个线程同时进入临界区。死锁死锁是进程死锁的简称是指多个进程循环等待他方占有的资源而无限的僵持下去的局面。活锁假设有两个线程1、2它们都需要资源 A/B假设1号线程占有了 A 资源2号线程占有了 B 资源由于两个线程都需要同时拥有这两个资源才可以工作为了避免死锁1号线程释放了 A 资源占有锁2号线程释放了 B 资源占有锁此时 AB 空闲两个线程又同时抢锁再次出现上述情况此时发生了活锁。简单类比电梯遇到人一个进的一个出的对面占路两个人同时往一个方向让路来回重复还是堵着路。如果线上应用遇到了活锁问题恭喜你中奖了这类问题比较难排查。饥饿饥饿是指某一个或者多个线程因为种种原因无法获得所需要的资源导致一直无法执行。线程的生命周期在线程的生命周期中它要经历创建、可运行、不可运行几种状态。创建状态当用 new 操作符创建一个新的线程对象时该线程处于创建状态。处于创建状态的线程只是一个空的线程对象系统不为它分配资源。可运行状态执行线程的 start() 方法将为线程分配必须的系统资源安排其运行并调用线程体——run()方法这样就使得该线程处于可运行状态(Runnable)。这一状态并不是运行中状态(Running)因为线程也许实际上并未真正运行。不可运行状态当发生下列事件时处于运行状态的线程会转入到不可运行状态调用了 sleep() 方法线程调用 wait() 方法等待特定条件的满足线程输入/输出阻塞返回可运行状态处于睡眠状态的线程在指定的时间过去后如果线程在等待某一条件另一个对象必须通过 notify() 或 notifyAll() 方法通知等待线程条件的改变如果线程是因为输入输出阻塞等待输入输出完成。线程的优先级线程优先级及设置线程的优先级是为了在多线程环境中便于系统对线程的调度优先级高的线程将优先执行。一个线程的优先级设置遵从以下原则线程创建时子继承父的优先级线程创建后可通过调用 setPriority() 方法改变优先级线程的优先级是1-10之间的正整数。线程的调度策略线程调度器选择优先级最高的线程运行。但是如果发生以下情况就会终止线程的运行线程体中调用了 yield() 方法让出了对 CPU 的占用权线程体中调用了 sleep() 方法使线程进入睡眠状态线程由于 I/O 操作而受阻塞另一个更高优先级的线程出现在支持时间片的系统中该线程的时间片用完。单线程创建方式单线程创建方式比较简单一般只有两种方式继承 Thread 类和实现 Runnable 接口这两种方式比较常用就不在 Demo 了但是对于新手需要注意的问题有不管是继承 Thread 类还是实现 Runable 接口业务逻辑是写在 run 方法里面线程启动的时候是执行 start() 方法开启新的线程不影响主线程的代码执行顺序也不会阻塞主线程的执行新的线程和主线程的代码执行顺序是不能够保证先后的对于多线程程序从微观上来讲某一时刻只有一个线程在工作多线程目的是让 CPU 忙起来通过查看 Thread 的源码可以看到Thread 类是实现了 Runnable 接口的所以这两种本质上来讲是一个PS平时在工作中也可以借鉴这种代码结构对上层调用来讲提供更多的选择作为服务提供方核心业务归一维护为什么要用线程池通过上面的介绍完全可以开发一个多线程的程序为什么还要引入线程池呢。主要是因为上述单线程方式存在以下几个问题线程的工作周期线程创建所需时间为 T1线程执行任务所需时间为 T2线程销毁所需时间为 T3往往是 T1T3 大于 T2所有如果频繁创建线程会损耗过多额外的时间如果有任务来了再去创建线程的话效率比较低如果从一个池子中可以直接获取可用的线程那效率会有所提高。所以线程池省去了任务过来要先创建线程再去执行的过程节省了时间提升了效率线程池可以管理和控制线程因为线程是稀缺资源如果无限制的创建不仅会消耗系统资源还会降低系统的稳定性使用线程池可以进行统一的分配调优和监控线程池提供队列存放缓冲等待执行的任务。大致总结了上述的几个原因所以可以得出一个结论就是在平时工作中如果要开发多线程程序尽量要使用线程池的方式来创建和管理线程。通过线程池创建线程从调用 API 角度来说分为两种一种是原生的线程池另外该一种是通过 Java 提供的并发包来创建后者比较简单后者其实是对原生的线程池创建方式做了一次简化包装让调用者使用起来更方便但道理都是一样的。所以搞明白原生线程池的原理是非常重要的。ThreadPoolExecutor通过 ThreadPoolExecutor 创建线程池。先来解释下其中的参数含义(如果看的比较模糊可以大致有个印象后面的图是关键)。corePoolSize核心池的大小。在创建了线程池后默认情况下线程池中并没有任何线程而是等待有任务到来才创建线程去执行任务除非调用了 prestartAllCoreThreads() 或者 prestartCoreThread() 方法从这两个方法的名字就可以看出是预创建线程的意思即在没有任务到来之前就创建 corePoolSize 个线程或者一个线程。默认情况下在创建了线程池后线程池中的线程数为0当有任务来之后就会创建一个线程去执行任务当线程池中的线程数目达到 corePoolSize 后就会把到达的任务放到缓存队列当中。maximumPoolSize线程池最大线程数这个参数也是一个非常重要的参数它表示在线程池中最多能创建多少个线程。keepAliveTime表示线程没有任务执行时最多保持多久时间会终止。默认情况下只有当线程池中的线程数大于 corePoolSize 时keepAliveTime 才会起作用直到线程池中的线程数不大于 corePoolSize即当线程池中的线程数大于 corePoolSize 时如果一个线程空闲的时间达到 keepAliveTime则会终止直到线程池中的线程数不超过 corePoolSize。但是如果调用了 allowCoreThreadTimeOut(boolean) 方法在线程池中的线程数不大于 corePoolSize 时keepAliveTime 参数也会起作用直到线程池中的线程数为0。unit参数 keepAliveTime 的时间单位。workQueue一个阻塞队列用来存储等待执行的任务这个参数的选择也很重要会对线程池的运行过程产生重大影响一般来说这里的阻塞队列有以下这几种选择ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue。threadFactory线程工厂主要用来创建线程。handler表示当拒绝处理任务时的策略有以下四种取值ThreadPoolExecutor.AbortPolicy丢弃任务并抛出 RejectedExecutionException 异常ThreadPoolExecutor.DiscardPolicy也是丢弃任务但是不抛出异常ThreadPoolExecutor.DiscardOldestPolicy丢弃队列最前面的任务然后重新尝试执行任务(重复此过程)ThreadPoolExecutor.CallerRunsPolicy由调用线程处理该任务。上面这些参数是如何配合工作的呢请看下图注意图上面的序号。简单总结下线程池之间的参数协作分为以下几步线程优先向 CorePool 中提交在 Corepool 满了之后线程被提交到任务队列等待线程池空闲在任务队列满了之后 corePool 还没有空闲那么任务将被提交到 maxPool 中如果 MaxPool 满了之后执行 task 拒绝策略。流程图如下以上就是原生线程池创建的核心原理。除了原生线程池之外并发包还提供了简单的创建方式上面也说了它们是对原生线程池的一种包装可以让开发者简单快捷的创建所需要的线程池。ExecutorsnewSingleThreadExecutor创建一个线程的线程池在这个线程池中始终只有一个线程存在。如果线程池中的线程因为异常问题退出那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。newFixedThreadPool创建固定大小的线程池。每次提交一个任务就创建一个线程直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变如果某个线程因为执行异常而结束那么线程池会补充一个新线程。newCachedThreadPool可根据实际情况调整线程数量的线程池线程池中的线程数量不确定如果有空闲线程会优先选择空闲线程如果没有空闲线程并且此时有任务提交会创建新的线程。在正常开发中并不推荐这个线程池因为在极端情况下会因为 newCachedThreadPool 创建过多线程而耗尽 CPU 和内存资源。newScheduledThreadPool此线程池可以指定固定数量的线程来周期性的去执行。比如通过 scheduleAtFixedRate 或者 scheduleWithFixedDelay 来指定周期时间。PS另外在写定时任务时(如果不用 Quartz 框架)最好采用这种线程池来做因为它可以保证里面始终是存在活的线程的。推荐使用 ThreadPoolExecutor 方式在阿里的 Java 开发手册时有一条是不推荐使用 Executors 去创建而是推荐去使用 ThreadPoolExecutor 来创建线程池。这样做的目的主要原因是使用 Executors 创建线程池不会传入核心参数而是采用的默认值这样的话我们往往会忽略掉里面参数的含义如果业务场景要求比较苛刻的话存在资源耗尽的风险另外采用 ThreadPoolExecutor 的方式可以让我们更加清楚地了解线程池的运行规则不管是面试还是对技术成长都有莫大的好处。改了变量其他线程可以立即知道。保证可见性的方法有以下几种volatile加入 volatile 关键字的变量在进行汇编时会多出一个 lock 前缀指令这个前缀指令相当于一个内存屏障内存屏障可以保证内存操作的顺序。当声明为 volatile 的变量进行写操作时那么这个变量需要将数据写到主内存中。由于处理器会实现缓存一致性协议所以写到主内存后会导致其他处理器的缓存无效也就是线程工作内存无效需要从主内存中重新刷新数据。