一、线程池
1.线程间通信
概念:
多个线程在处理同⼀个资源,但是处理的动作(线程的任务)却不相同。
为什么要处理线程间通信:
多个线程并发执⾏时
,
在默认情况下
CPU
是随机切换线程的,当我们需要多个线程来共同完成⼀件任务,并且我们希望他们有规律的执⾏,那么多线程之间需要⼀些协调通信,以此来帮我们达到多线程共同操作⼀份数据。
如何保证线程间通信有效利⽤资源:
多个线程在处理同⼀个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同⼀个变量 的使⽤或操作。就是多个线程在操作同⼀份数据时,避免对同⼀共享变量的争夺。也就是我们需 要通过⼀定的⼿段使各个线程能有效 的利⽤资源。⽽这种⼿段即 --
等待唤醒机制。
2.等待唤醒机制
什么是等待唤醒机制
这是多个线程间的⼀种
协作
机制。
谈到线程我们经常想到的是线程间的
竞争
( race )
,⽐如去争夺锁,但这并不是故事的全部,线程间也会有协作机制。就是在⼀个线程进⾏了规定操作后,就进⼊等待状态
(
wait()
)
,等待其他线程执⾏完他们的指定代码过后再将其唤醒(
notify()
)
;在有多个线程进⾏等待时,如果需要,可以使⽤
notifyAll()
来 唤醒所有的等待线程。 wait/notify 就是线程间的⼀种协作机制。
等待唤醒中的⽅法
等待唤醒机制就是⽤于解决线程间通信的问题的,使⽤到的
3
个⽅法的含义如下:
1. wait:线程不再活动,不再参与调度,进⼊
wait set
中,因此不会浪费
CPU
资源,也不会
去竞争锁了,这时的线程状态即是
WAITING
。它还要等着别的线程执⾏⼀个
特别的动作
,
也即是
“
通知
( notify )”
在这个对象上等待的线程从
wait set
中释放出来,重新进⼊到调度队
列
( ready queue )
中。
2. notify:则选取所通知对象的
wait set
中的⼀个线程释放;例如,餐馆有空位置后,等候就
餐最久的顾客最先⼊座。
3. notifyAll:则释放所通知对象的
wait set
上的全部线程。
调⽤wait和notify⽅法需要注意的细节
1. wait⽅法与
notify
⽅法必须要由同⼀个锁对象调⽤。因为:对应的锁对象可以通过
notify
唤醒
使⽤同⼀个锁对象调⽤的
wait
⽅法后的线程。
2. wait⽅法与
notify
⽅法是属于
Object
类的⽅法的。因为:锁对象可以是任意对象,⽽任意对
象的所属类都是继承了
Object
类的。
3. wait⽅法与
notify
⽅法必须要在同步代码块或者是同步函数中使⽤。因为:必须要通过锁对
象调⽤这
2
个⽅法。
生产者与消费者问题:等待唤醒机制其实就是经典的“⽣产者与消费者”的问题。
3.有关线程池
线程池:
其实就是⼀个容纳多个线程的容器,其中的线程可以反复使⽤,省去了频繁创建线程对象的操作,⽆需反复创建线程⽽消耗过多资源。
合理利⽤线程池能够带来三个好处:
1. 降低资源消耗。减少了创建和销毁线程的次数,每个⼯作线程都可以被重复利⽤,可执⾏多个任务。
2. 提⾼响应速度。当任务到达时,任务可以不需要的等到线程创建就能⽴即执⾏。
3. 提⾼线程的可管理性。可以根据系统的承受能⼒,调整线程池中⼯作线线程的数⽬,防⽌因为消耗过多的内存,⽽把服务器累趴下(每个线程需要⼤约1MB
内存,线程开的越多,消耗的内存也就越⼤,最后死机)。
1.线程池的使用
Java
⾥⾯线程池的顶级接⼝是
java.util.concurrent.Executor
,但是严格意义上讲
Executor并不是⼀个线程池,⽽只是⼀个执⾏线程的⼯具。
真正的线程池接⼝是java.util.concurrent.ExecutorService
。要配置⼀个线程池是⽐较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优 的,因此在 java.util.concurrent.Executors
线程⼯⼚类⾥⾯提供了⼀些静态⼯⼚,⽣成⼀些常⽤的线程池。官 ⽅建议使⽤ Executors
⼯程类来创建线程池对象。
Executors
类中有个创建线程池的⽅法如下:
public static ExecutorService newFixedThreadPool(int nThreads)
:返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最⼤数量)
获取到了⼀个线程池
ExecutorService
对象,那么怎么使⽤呢,在这⾥定义了⼀个使⽤线程池对
象的⽅法如下:
public Future<?> submit(Runnable task)
:获取线程池中的某⼀个线程对象,并执⾏
(
Future
接⼝:⽤来记录线程任务执⾏完毕后产⽣的结果。线程池创建与使⽤)
使⽤线程池中线程对象的步骤:
1. 创建线程池对象。
2. 创建Runnable
接⼝⼦类对象。
(task)
3. 提交Runnable
接⼝⼦类对象。
(take task)
4. 关闭线程池(⼀般不做)。