共计 10920 个字符,预计需要花费 28 分钟才能阅读完成。
你背不下的书,总有人能背下来。你做不出的题,总有人能做进去。你违心拖到今天的事,总有人明天就能做完。
1、多线程有什么用?
一个可能在很多人看来很扯淡的一个问题:我会用多线程就好了,还管它有什么用?在我看来,这个答复更扯淡。所谓 ” 知其然知其所以然 ”,” 会用 ” 只是 ” 知其然 ”,” 为什么用 ” 才是 ” 知其所以然 ”,只有达到 ” 知其然知其所以然 ” 的水平才能够说是把一个知识点运用自如。OK,上面说说我对这个问题的认识:
(1)施展多核 CPU 的劣势
随着工业的提高,当初的笔记本、台式机乃至商用的应用服务器至多也都是双核的,4 核、8 核甚至 16 核的也都不少见,如果是单线程的程序,那么在双核 CPU 上就节约了 50%,在 4 核 CPU 上就节约了 75%。单核 CPU 上所谓的 ” 多线程 ” 那是假的多线程,同一时间处理器只会解决一段逻辑,只不过线程之间切换得比拟快,看着像多个线程 ” 同时 ” 运行罢了。多核 CPU 上的多线程才是真正的多线程,它能让你的多段逻辑同时工作,多线程,能够真正施展出多核 CPU 的劣势来,达到充分利用 CPU 的目标。
(2)避免阻塞
从程序运行效率的角度来看,单核 CPU 岂但不会施展出多线程的劣势,反而会因为在单核 CPU 上运行多线程导致线程上下文的切换,而升高程序整体的效率。然而单核 CPU 咱们还是要利用多线程,就是为了避免阻塞。试想,如果单核 CPU 应用单线程,那么只有这个线程阻塞了,比方说近程读取某个数据吧,对端迟迟未返回又没有设置超时工夫,那么你的整个程序在数据返回回来之前就进行运行了。多线程能够避免这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,也不会影响其它工作的执行。
(3)便于建模
这是另外一个没有这么显著的长处了。假如有一个大的工作 A,单线程编程,那么就要思考很多,建设整个程序模型比拟麻烦。然而如果把这个大的工作 A 分解成几个小工作,工作 B、工作 C、工作 D,别离建设程序模型,并通过多线程别离运行这几个工作,那就简略很多了。
2、创立线程的形式
比拟常见的一个问题了,个别就是两种:
(1)继承 Thread 类
(2)实现 Runnable 接口
至于哪个好,不用说必定是后者好,因为实现接口的形式比继承类的形式更灵便,也能缩小程序之间的耦合度,面向接口编程 也是设计模式 6 大准则的外围。
3、start()办法和 run()办法的区别
只有调用了 start()办法,才会体现出多线程的个性,不同线程的 run()办法外面的代码交替执行。如果只是调用 run()办法,那么代码还是同步执行的,必须期待一个线程的 run()办法外面的代码全副执行结束之后,另外一个线程才能够执行其 run()办法外面的代码。
4、Runnable 接口和 Callable 接口的区别
有点深的问题了,也看出一个 Java 程序员学习常识的广度。
Runnable 接口中的 run()办法的返回值是 void,它做的事件只是纯正地去执行 run()办法中的代码而已;Callable 接口中的 call()办法是有返回值的,是一个泛型,和 Future、FutureTask 配合能够用来获取异步执行的后果。
这其实是很有用的一个个性,因为 多线程相比单线程更难、更简单的一个重要起因就是因为多线程充斥着未知性,某条线程是否执行了?某条线程执行了多久?某条线程执行的时候咱们冀望的数据是否曾经赋值结束?无奈得悉,咱们能做的只是期待这条多线程的工作执行结束而已。而 Callable+Future/FutureTask 却能够获取多线程运行的后果,能够在等待时间太长没获取到须要的数据的状况下勾销该线程的工作,真的是十分有用。
5、CyclicBarrier 和 CountDownLatch 的区别
两个看上去有点像的类,都在 java.util.concurrent 下,都能够用来示意代码运行到某个点上,二者的区别在于:
(1)CyclicBarrier 的某个线程运行到某个点上之后,该线程即进行运行,直到所有的线程都达到了这个点,所有线程才从新运行;CountDownLatch 则不是,某线程运行到某个点上之后,只是给某个数值 - 1 而已,该线程持续运行
(2)CyclicBarrier 只能唤起一个工作,CountDownLatch 能够唤起多个工作
(3)CyclicBarrier 可重用,CountDownLatch 不可重用,计数值为 0 该 CountDownLatch 就不可再用了
6、volatile 关键字的作用
一个十分重要的问题,是每个学习、利用多线程的 Java 程序员都必须把握的。了解 volatile 关键字的作用的前提是要了解 Java 内存模型,这里就不讲 Java 内存模型了,能够参见第 31 点,volatile 关键字的作用次要有两个:
(1)多线程次要围绕可见性和原子性两个个性而开展,应用 volatile 关键字润饰的变量,保障了其在多线程之间的可见性,即每次读取到 volatile 变量,肯定是最新的数据
(2)代码底层执行不像咱们看到的高级语言 —-Java 程序这么简略,它的执行是Java 代码 –> 字节码 –> 依据字节码执行对应的 C /C++ 代码 –>C/C++ 代码被编译成汇编语言 –> 和硬件电路交互,事实中,为了获取更好的性能 JVM 可能会对指令进行重排序,多线程下可能会呈现一些意想不到的问题。应用 volatile 则会对禁止语义重排序,当然这也肯定水平上升高了代码执行效率
从实际角度而言,volatile 的一个重要作用就是和 CAS 联合,保障了原子性,具体的能够参见 java.util.concurrent.atomic 包下的类,比方 AtomicInteger。
7、什么是线程平安
又是一个实践的问题,各式各样的答案有很多,我给出一个集体认为解释地最好的:如果你的代码在多线程下执行和在单线程下执行永远都能取得一样的后果,那么你的代码就是线程平安的。
这个问题有值得一提的中央,就是线程平安也是有几个级别的:
(1)不可变
像 String、Integer、Long 这些,都是 final 类型的类,任何一个线程都扭转不了它们的值,要扭转除非新创建一个,因而这些不可变对象不须要任何同步伎俩就能够间接在多线程环境下应用
(2)相对线程平安
不论运行时环境如何,调用者都不须要额定的同步措施。要做到这一点通常须要付出许多额定的代价,Java 中标注本人是线程平安的类,实际上绝大多数都不是线程平安的,不过相对线程平安的类,Java 中也有,比方说 CopyOnWriteArrayList、CopyOnWriteArraySet
(3)绝对线程平安
绝对线程平安也就是咱们通常意义上所说的线程平安,像 Vector 这种,add、remove 办法都是原子操作,不会被打断,但也仅限于此,如果有个线程在遍历某个 Vector、有个线程同时在 add 这个 Vector,99% 的状况下都会呈现 ConcurrentModificationException,也就是fail-fast 机制。
(4)线程非平安
这个就没什么好说的了,ArrayList、LinkedList、HashMap 等都是线程非平安的类
8、Java 中如何获取到线程 dump 文件
死循环、死锁、阻塞、页面关上慢等问题,打线程 dump 是最好的解决问题的路径。所谓线程 dump 也就是线程堆栈,获取到线程堆栈有两步:
(1)获取到线程的 pid,能够通过应用 jps 命令,在 Linux 环境下还能够应用 ps -ef | grep java
(2)打印线程堆栈,能够通过应用 jstack pid 命令,在 Linux 环境下还能够应用 kill -3 pid
另外提一点,Thread 类提供了一个 getStackTrace()办法也能够用于获取线程堆栈。这是一个实例办法,因而此办法是和具体线程实例绑定的,每次获取获取到的是具体某个线程以后运行的堆栈,
9、一个线程如果呈现了运行时异样会怎么样
如果这个异样没有被捕捉的话,这个线程就进行执行了。另外重要的一点是:如果这个线程持有某个某个对象的监视器,那么这个对象监视器会被立刻开释
10、如何在两个线程之间共享数据
通过在线程之间共享对象就能够了,而后通过 wait/notify/notifyAll、await/signal/signalAll 进行唤起和期待,比方说阻塞队列 BlockingQueue 就是为线程之间共享数据而设计的
11、sleep 办法和 wait 办法有什么区别
这个问题常问,sleep 办法和 wait 办法都能够用来放弃 CPU 肯定的工夫,不同点在于如果线程持有某个对象的监视器,sleep 办法不会放弃这个对象的监视器,wait 办法会放弃这个对象的监视器
12、生产者消费者模型的作用是什么
这个问题很实践,然而很重要:
(1)通过均衡生产者的生产能力和消费者的生产能力来晋升整个零碎的运行效率,这是生产者消费者模型最重要的作用
(2)解耦,这是生产者消费者模型附带的作用,解耦意味着生产者和消费者之间的分割少,分割越少越能够单独倒退而不须要收到互相的制约
13、ThreadLocal 有什么用
简略说 ThreadLocal 就是一种以 空间换工夫 的做法,在每个 Thread 外面保护了一个以开地址法实现的 ThreadLocal.ThreadLocalMap,把数据进行隔离,数据不共享,天然就没有线程平安方面的问题了
14、为什么 wait()办法和 notify()/notifyAll()办法要在同步块中被调用
这是 JDK 强制的,wait()办法和 notify()/notifyAll()办法在调用前都必须先取得对象的锁
15、wait()办法和 notify()/notifyAll()办法在放弃对象监视器时有什么区别
wait()办法和 notify()/notifyAll()办法在放弃对象监视器的时候的区别在于:wait()办法立刻开释对象监视器,notify()/notifyAll()办法则会期待线程残余代码执行结束才会放弃对象监视器。
16、为什么要应用线程池
防止频繁地创立和销毁线程,达到线程对象的重用。另外,应用线程池还能够依据我的项目灵便地管制并发的数目。
17、怎么检测一个线程是否持有对象监视器
我也是在网上看到一道多线程面试题才晓得有办法能够判断某个线程是否持有对象监视器:Thread 类提供了一个 holdsLock(Object obj)办法,当且仅当对象 obj 的监视器被某条线程持有的时候才会返回 true,留神这是一个 static 办法,这意味着 ” 某条线程 ” 指的是以后线程。
18、synchronized 和 ReentrantLock 的区别
synchronized 是和 if、else、for、while 一样的关键字,ReentrantLock 是类,这是二者的本质区别。既然 ReentrantLock 是类,那么它就提供了比 synchronized 更多更灵便的个性,能够被继承、能够有办法、能够有各种各样的类变量,ReentrantLock 比 synchronized 的扩展性体现在几点上:
(1)ReentrantLock 能够对获取锁的等待时间进行设置,这样就防止了死锁
(2)ReentrantLock 能够获取各种锁的信息
(3)ReentrantLock 能够灵便地实现多路告诉
另外,二者的锁机制其实也是不一样的。ReentrantLock 底层调用的是 Unsafe 的 park 办法加锁,synchronized 操作的应该是对象头中 mark word,这点我不能确定。
19、ConcurrentHashMap 的并发度是什么
ConcurrentHashMap 的并发度就是 segment 的大小,默认为 16,这意味着最多同时能够有 16 条线程操作 ConcurrentHashMap,这也是 ConcurrentHashMap 对 Hashtable 的最大劣势,任何状况下,Hashtable 能同时有两条线程获取 Hashtable 中的数据吗?
20、ReadWriteLock 是什么
首先明确一下,不是说 ReentrantLock 不好,只是 ReentrantLock 某些时候有局限。如果应用 ReentrantLock,可能自身是为了避免线程 A 在写数据、线程 B 在读数据造成的数据不统一,但这样,如果线程 C 在读数据、线程 D 也在读数据,读数据是不会扭转数据的,没有必要加锁,然而还是加锁了,升高了程序的性能。
因为这个,才诞生了读写锁 ReadWriteLock。ReadWriteLock 是一个读写锁接口,ReentrantReadWriteLock 是 ReadWriteLock 接口的一个具体实现,实现了读写的拆散,读锁是共享的,写锁是独占的,读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,晋升了读写的性能。
21、FutureTask 是什么
这个其实后面有提到过,FutureTask 示意一个异步运算的工作。FutureTask 外面能够传入一个 Callable 的具体实现类,能够对这个异步运算的工作的后果进行期待获取、判断是否曾经实现、勾销工作等操作。当然,因为 FutureTask 也是 Runnable 接口的实现类,所以 FutureTask 也能够放入线程池中。
22、Linux 环境下如何查找哪个线程应用 CPU 最长
这是一个比拟偏实际的问题,这种问题我感觉挺有意义的。能够这么做:
(1)获取我的项目的 pid,jps 或者 ps -ef | grep java,这个后面有讲过
(2)top -H -p pid,程序不能扭转
这样就能够打印出以后的我的项目,每条线程占用 CPU 工夫的百分比。留神这里打出的是 LWP,也就是操作系统原生线程的线程号,我笔记本山没有部署 Linux 环境下的 Java 工程,因而没有方法截图演示,网友敌人们如果公司是应用 Linux 环境部署我的项目的话,能够尝试一下。
应用 ”top -H -p pid”+”jps pid” 能够很容易地找到某条占用 CPU 高的线程的线程堆栈,从而定位占用 CPU 高的起因,个别是因为不当的代码操作导致了死循环。
最初提一点,”top -H -p pid” 打进去的 LWP 是十进制的,”jps pid” 打进去的本地线程号是十六进制的,转换一下,就能定位到占用 CPU 高的线程的以后线程堆栈了。
23、Java 编程写一个会导致死锁的程序
第一次看到这个题目,感觉这是一个十分好的问题。很多人都晓得死锁是怎么一回事儿:线程 A 和线程 B 互相期待对方持有的锁导致程序有限死循环上来。当然也仅限于此了,问一下怎么写一个死锁的程序就不晓得了,这种状况说白了就是不懂什么是死锁,懂一个实践就完事儿了,实际中碰到死锁的问题基本上是看不出来的。
真正了解什么是死锁,这个问题其实不难,几个步骤:
(1)两个线程外面别离持有两个 Object 对象:lock1 和 lock2。这两个 lock 作为同步代码块的锁;
(2)线程 1 的 run()办法中同步代码块先获取 lock1 的对象锁,Thread.sleep(xxx),工夫不须要太多,50 毫秒差不多了,而后接着获取 lock2 的对象锁。这么做次要是为了避免线程 1 启动一下子就间断取得了 lock1 和 lock2 两个对象的对象锁
(3)线程 2 的 run)(办法中同步代码块先获取 lock2 的对象锁,接着获取 lock1 的对象锁,当然这时 lock1 的对象锁曾经被线程 1 锁持有,线程 2 必定是要期待线程 1 开释 lock1 的对象锁的
这样,线程 1 ″ 睡觉 ” 睡完,线程 2 曾经获取了 lock2 的对象锁了,线程 1 此时尝试获取 lock2 的对象锁,便被阻塞,此时一个死锁就造成了。代码就不写了,占的篇幅有点多,Java 多线程 7:死锁这篇文章外面有,就是下面步骤的代码实现。
24、怎么唤醒一个阻塞的线程
如果线程是因为调用了 wait()、sleep()或者 join()办法而导致的阻塞,能够中断线程,并且通过抛出 InterruptedException 来唤醒它;如果线程遇到了 IO 阻塞,无能为力,因为 IO 是操作系统实现的,Java 代码并没有方法间接接触到操作系统。
25、不可变对象对多线程有什么帮忙
后面有提到过的一个问题,不可变对象保障了对象的内存可见性,对不可变对象的读取不须要进行额定的同步伎俩,晋升了代码执行效率。
26、什么是多线程的上下文切换
多线程的上下文切换是指 CPU 控制权由一个曾经正在运行的线程切换到另外一个就绪并期待获取 CPU 执行权的线程的过程。
27、如果你提交工作时,线程池队列已满,这时会产生什么
这里辨别一下:
- 如果应用的是无界队列 LinkedBlockingQueue,也就是无界队列的话,没关系,持续增加工作到阻塞队列中期待执行,因为 LinkedBlockingQueue 能够近乎认为是一个无穷大的队列,能够有限寄存工作
- 如果应用的是有界队列比方 ArrayBlockingQueue,工作首先会被增加到 ArrayBlockingQueue 中,ArrayBlockingQueue 满了,会依据 maximumPoolSize 的值减少线程数量,如果减少了线程数量还是解决不过去,ArrayBlockingQueue 持续满,那么则会应用回绝策略 RejectedExecutionHandler 解决满了的工作,默认是 AbortPolicy
28、Java 中用到的线程调度算法是什么
抢占式。一个线程用完 CPU 之后,操作系统会依据线程优先级、线程饥饿状况等数据算出一个总的优先级并调配下一个工夫片给某个线程执行。
29、Thread.sleep(0)的作用是什么
这个问题和下面那个问题是相干的,我就连在一起了。因为 Java 采纳抢占式的线程调度算法,因而可能会呈现某条线程经常获取到 CPU 控制权的状况,为了让某些优先级比拟低的线程也能获取到 CPU 控制权,能够应用 Thread.sleep(0)手动触发一次操作系统调配工夫片的操作,这也是均衡 CPU 控制权的一种操作。
30、什么是自旋
很多 synchronized 外面的代码只是一些很简略的代码,执行工夫十分快,此时期待的线程都加锁可能是一种不太值得的操作,因为线程阻塞波及到用户态和内核态切换的问题。既然 synchronized 外面的代码执行得十分快,无妨让期待锁的线程不要被阻塞,而是在 synchronized 的边界做忙循环,这就是自旋。如果做了屡次忙循环发现还没有取得锁,再阻塞,这样可能是一种更好的策略。
31、什么是 Java 内存模型
Java 内存模型定义了一种多线程拜访 Java 内存的标准。Java 内存模型要残缺讲不是这里几句话能说分明的,我简略总结一下 Java 内存模型的几局部内容:
(1)Java 内存模型将内存分为了 主内存和工作内存。类的状态,也就是类之间共享的变量,是存储在主内存中的,每次 Java 线程用到这些主内存中的变量的时候,会读一次主内存中的变量,并让这些内存在本人的工作内存中有一份拷贝,运行本人线程代码的时候,用到这些变量,操作的都是本人工作内存中的那一份。在线程代码执行结束之后,会将最新的值更新到主内存中去
(2)定义了几个原子操作,用于操作主内存和工作内存中的变量
(3)定义了 volatile 变量的应用规定
(4)happens-before,即后行产生准则,定义了操作 A 必然后行产生于操作 B 的一些规定,比方在同一个线程内控制流后面的代码肯定后行产生于控制流前面的代码、一个开释锁 unlock 的动作肯定后行产生于前面对于同一个锁进行锁定 lock 的动作等等,只有合乎这些规定,则不须要额定做同步措施,如果某段代码不合乎所有的 happens-before 规定,则这段代码肯定是线程非平安的
32、什么是 CAS
CAS,全称为 Compare and Swap,即比拟 - 替换。假如有三个操作数:内存值 V、旧的预期值 A、要批改的值 B,当且仅当预期值 A 和内存值 V 雷同时,才会将内存值批改为 B 并返回 true,否则什么都不做并返回 false。当然 CAS 肯定要 volatile 变量配合,这样能力保障每次拿到的变量是主内存中最新的那个值,否则旧的预期值 A 对某条线程来说,永远是一个不会变的值 A,只有某次 CAS 操作失败,永远都不可能胜利。
33、什么是乐观锁和乐观锁
(1)乐观锁:就像它的名字一样,对于并发间操作产生的线程平安问题持乐观状态,乐观锁认为竞争不总是会产生,因而它不须要持有锁,将 比拟 - 替换 这两个动作作为一个原子操作尝试去批改内存中的变量,如果失败则示意发生冲突,那么就应该有相应的重试逻辑。
(2)乐观锁:还是像它的名字一样,对于并发间操作产生的线程平安问题持乐观状态,乐观锁认为竞争总是会产生,因而每次对某资源进行操作时,都会持有一个独占的锁,就像 synchronized,不管三七二十一,间接上了锁就操作资源了。
34、什么是 AQS
简略说一下 AQS,AQS 全称为 AbstractQueuedSychronizer,翻译过去应该是形象队列同步器。
如果说 java.util.concurrent 的根底是 CAS 的话,那么 AQS 就是整个 Java 并发包的外围了,ReentrantLock、CountDownLatch、Semaphore 等等都用到了它。AQS 实际上以双向队列的模式连贯所有的 Entry,比方说 ReentrantLock,所有期待的线程都被放在一个 Entry 中并连成双向队列,后面一个线程应用 ReentrantLock 好了,则双向队列实际上的第一个 Entry 开始运行。
AQS 定义了对双向队列所有的操作,而只凋谢了 tryLock 和 tryRelease 办法给开发者应用,开发者能够依据本人的实现重写 tryLock 和 tryRelease 办法,以实现本人的并发性能。
35、单例模式的线程安全性
陈词滥调的问题了,首先要说的是单例模式的线程平安意味着:某个类的实例在多线程环境下只会被创立一次进去。单例模式有很多种的写法,我总结一下:
(1)饿汉式单例模式的写法:线程平安
(2)懒汉式单例模式的写法:非线程平安
(3)双检锁单例模式的写法:线程平安
36、Semaphore 有什么作用
Semaphore 就是一个信号量,它的作用是 限度某段代码块的并发数。Semaphore 有一个构造函数,能够传入一个 int 型整数 n,示意某段代码最多只有 n 个线程能够拜访,如果超出了 n,那么请期待,等到某个线程执行结束这段代码块,下一个线程再进入。由此能够看出如果 Semaphore 构造函数中传入的 int 型整数 n =1,相当于变成了一个 synchronized 了。
37、Hashtable 的 size()办法中明明只有一条语句 ”return count”,为什么还要做同步?
这是我之前的一个困惑,不晓得大家有没有想过这个问题。某个办法中如果有多条语句,并且都在操作同一个类变量,那么在多线程环境下不加锁,势必会引发线程平安问题,这很好了解,然而 size()办法明明只有一条语句,为什么还要加锁?
对于这个问题,在缓缓地工作、学习中,有了了解,次要起因有两点:
(1)同一时间只能有一条线程执行固定类的同步办法,然而对于类的非同步办法,能够多条线程同时拜访 。所以,这样就有问题了,可能线程 A 在执行 Hashtable 的 put 办法增加数据,线程 B 则能够失常调用 size() 办法读取 Hashtable 中以后元素的个数,那读取到的值可能不是最新的,可能线程 A 增加了完了数据,然而没有对 size++,线程 B 就曾经读取 size 了,那么对于线程 B 来说读取到的 size 肯定是不精确的。而给 size()办法加了同步之后,意味着线程 B 调用 size()办法只有在线程 A 调用 put 办法结束之后才能够调用,这样就保障了线程安全性
(2)CPU 执行代码,执行的不是 Java 代码,这点很要害,肯定得记住 。Java 代码最终是被翻译成机器码执行的,机器码才是真正能够和硬件电路交互的代码。 即便你看到 Java 代码只有一行,甚至你看到 Java 代码编译之后生成的字节码也只有一行,也不意味着对于底层来说这句语句的操作只有一个。一句 ”return count” 假如被翻译成了三句汇编语句执行,一句汇编语句和其机器码做对应,齐全可能执行完第一句,线程就切换了。
38、线程类的构造方法、动态块是被哪个线程调用的
这是一个十分刁钻和刁滑的问题。请记住:线程类的构造方法、动态块是被 new 这个线程类所在的线程所调用的,而 run 办法外面的代码才是被线程本身所调用的。
如果说下面的说法让你感到困惑,那么我举个例子,假如 Thread2 中 new 了 Thread1,main 函数中 new 了 Thread2,那么:
(1)Thread2 的构造方法、动态块是 main 线程调用的,Thread2 的 run()办法是 Thread2 本人调用的
(2)Thread1 的构造方法、动态块是 Thread2 调用的,Thread1 的 run()办法是 Thread1 本人调用的
39、同步办法和同步块,哪个是更好的抉择
同步块,这意味着同步块之外的代码是异步执行的,这比同步整个办法更晋升代码的效率。请晓得一条准则:同步的范畴越小越好。
借着这一条,我额定提一点,虽说同步的范畴越少越好,然而在 Java 虚拟机中还是存在着一种叫做 锁粗化 的优化办法,这种办法就是把同步范畴变大。这是有用的,比方说 StringBuffer,它是一个线程平安的类,天然最罕用的 append()办法是一个同步办法,咱们写代码的时候会重复 append 字符串,这意味着要进行重复的加锁 -> 解锁,这对性能不利,因为这意味着 Java 虚拟机在这条线程上要重复地在内核态和用户态之间进行切换,因而 Java 虚构机会将屡次 append 办法调用的代码进行一个锁粗化的操作,将屡次的 append 的操作扩大到 append 办法的头尾,变成一个大的同步块,这样就缩小了加锁 –> 解锁的次数,无效地晋升了代码执行的效率。
40、高并发、工作执行工夫短的业务怎么应用线程池?并发不高、工作执行工夫长的业务怎么应用线程池?并发高、业务执行工夫长的业务怎么应用线程池?
这是我在并发编程网上看到的一个问题,把这个问题放在最初一个,心愿每个人都能看到并且思考一下,因为这个问题十分好、十分理论、十分业余。对于这个问题,集体认识是:
(1)高并发、工作执行工夫短的业务,线程池线程数能够设置为 CPU 核数 +1,缩小线程上下文的切换
(2)并发不高、工作执行工夫长的业务要辨别开看:
a)如果是业务工夫长集中在 IO 操作上,也就是 IO 密集型的工作,因为 IO 操作并不占用 CPU,所以不要让所有的 CPU 闲下来,能够加大线程池中的线程数目,让 CPU 解决更多的业务
b)如果是业务工夫长集中在计算操作上,也就是计算密集型工作,这个就没方法了,和(1)一样吧,线程池中的线程数设置得少一些,缩小线程上下文的切换
(3)并发高、业务执行工夫长,解决这种类型工作的要害不在于线程池而在于整体架构的设计,看看这些业务外面某些数据是否能做缓存是第一步,减少服务器是第二步,至于线程池的设置,设置参考(2)。最初,业务执行工夫长的问题,也可能须要剖析一下,看看能不能应用中间件对工作进行拆分和解耦。