等待唤醒机制
概念
线程间通信:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。 为什么要处理线程间通信: 多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行, 那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。 如何保证线程间通信有效利用资源: 多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。 就是多个线程在操作同一份数据时, 避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制。 什么是等待唤醒机制 这是多个线程间的一种协作机制。谈到线程我们经常想到的是线程间的竞争(race),比如去争夺锁,但这并不是故事的全部,线程间也会有协作机制。就好比在公司里你和你的同事们,你们可能存在在晋升时的竞争,但更多时候你们更多是一起合作以完成某些任务。就是在一个线程进行了规定操作后,就进入等待状态(wait()), 等待其他线程执行完他们的指定代码过后 再将其唤醒(notify());在有多个线程进行等待时, 如果需要,可以使用 notifyAll()来唤醒所有的等待线程。wait/notify 就是线程间的一种协作机制。
等待唤醒中的方法
wait:线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了,这时的线程状态即是 WAITING。它还要等着别的线程执行一个特别的动作,也即是“通知(notify)”在这个对象上等待的线程从wait set 中释放出来,重新进入到调度队列(ready queue)中 notify:则选取所通知对象的 wait set 中的一个线程释放;例如,餐馆有空位置后,等候就餐最久的顾客最先入座。 notifyAll:则释放所通知对象的 wait set 上的全部线程
调用wait和notify方法需要注意的细节
wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。 wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。 wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。
生产者与消费者问题
public class BaoZi {
private String name = "肉馅" ;
private boolean flag= true ;
public String getName ( ) {
return name;
}
public void setName ( String name) {
this . name = name;
}
public boolean isFlag ( ) {
return flag;
}
public void setFlag ( boolean flag) {
this . flag = flag;
}
}
public class Producer extends Thread {
public BaoZi baozi;
public Producer ( BaoZi baozi) {
this . baozi = baozi;
}
@Override
public void run ( ) {
while ( true ) {
synchronized ( baozi) {
if ( baozi. isFlag ( ) ) {
System. out. println ( baozi. getName ( ) + "包子真好吃" ) ;
baozi. notify ( ) ;
baozi. setFlag ( false ) ;
} else {
try {
baozi. wait ( ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
}
}
}
}
}
public class Custmer extends Thread {
public BaoZi baozi;
public Custmer ( BaoZi baozi) {
this . baozi = baozi;
}
@Override
public void run ( ) {
while ( true ) {
synchronized ( baozi) {
if ( ! baozi. isFlag ( ) ) {
System. out. println ( "我要开始做包子了" ) ;
baozi. setFlag ( true ) ;
baozi. notify ( ) ;
} else {
try {
baozi. wait ( ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
}
}
}
}
}
public class Test {
public static void main ( String[ ] args) {
BaoZi baozi = new BaoZi ( ) ;
new Producer ( baozi) . start ( ) ;
new Custmer ( baozi) . start ( ) ;
}
}
线程池
概念:
其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
三个好处:
降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
Executors类线程生产工厂
public static ExecutorService newFixedThreadPool(int nThreads) :返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量 public Future<?> submit(Runnable task) :获取线程池中的某一个线程对象,并执行
使用线程池中线程对象的步骤:
创建线程池对象。 创建Runnable接口子类对象。(task) 提交Runnable接口子类对象。(take task) 关闭线程池(一般不做)。
Lambda表达式
Lambda标准格式
一些参数 一个箭头 一段代码 (参数类型 参数名称) ‐> { 代码语句 } 小括号内的语法与传统方法参数列表一致:无参数则留空;多个参数则用逗号分隔。 -> 是新引入的语法格式,代表指向动作。 大括号内的语法与传统方法体要求基本一致
省略规则
小括号内参数的类型可以省略; 如果小括号内有且仅有一个参,则小括号可以省略; 如果大括号内有且仅有一个语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号
Lambda的使用前提
使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。无论是JDK内置的 Runnable 、 Comparator 接口还是自定义的接口,只有当接口中的抽象方法存在且唯一 时,才可以使用Lambda。 使用Lambda必须具有上下文推断。也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例
创建多线程的几种方式
newCachedThreadPool 创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能。调用 execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。 newFixedThreadPool public static ExecutorService newFixedThreadPool(int nThreads) 创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。 newScheduledThreadPool public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) corePoolSize - 池中所保存的线程数,即使线程是空闲的也包括在内。 newSingleThreadExecutor public static ExecutorService newSingleThreadExecutor() 创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。(注意,如果因为在关闭前的执行期间出现失败而终止了此单个线程,那么如果需要,一个新线程将代替它执行后续的任务)。可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。与其他等效的 newFixedThreadPool(1)不同,可保证无需重新配置此方法所返回的执行程序即可使用其他的线程。