Java中线程的基础介绍(2)
1-1.线程状态概述
线程的状态有共有六种状态:新建状态(NEW),运行状态(RUNNABLE),阻塞状态(BLOCKED),死亡状态(TERMINATED),休眠状态(TIMED-WAITING),无限(永久)等待状态(WAITING)。
其中阻塞状态(BLOCKED):具有cpu的执行资格,等待cpu空闲时执行。
休眠状态(TIMED-WAITING):放弃cpu的执行资格,cpu空闲时也不执行。
线程状态 | 导致状态发生条件 |
NEW(新建) | 线程刚被创建,但是并未启动。还没调用start方法。 |
Runnable(可运行) | 线程可以在java虚拟机中运行的状态,可能正在运行自己代码 ,也可能没有,这取决于操作系统处理器。 |
Blocked(锁阻塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持 有,则该线程进入Blocked状态;当该线程持有锁时,该线程将 变成Runnable状态。 |
Waiting(无限等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程 进入Waiting状态。进入这个状态后是不能自动唤醒的,必须 等待另一个线程调用notify或者notifyAll方法才能够唤醒。 |
TimedWaiting(计时等待) | 同waiting状态,有几个方法有超时参数,调用他们将进入 Timed Waiting状态。这一状态将一直保持到超时期满或者接 收到唤醒通知。带有超时参数的常用方法有Thread.sleep、 Object.wait。 |
Teminated(被终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止 了run方法而死亡。 |
1-2.等待唤醒机制
等待唤醒机制就是线程之间的通信方式。
就是在-一个线程进行了规定操作后,就进入等待状态( wait()),等待其他线程执行完他们的指定代码过后 再将
其唤醒( notify() ) ;在有多个线程进行等待时,如果需要,可以使用notifyAll()来唤醒所有的等待线程。
wait/notify就是线程间的一种协作机制。
等待唤醒机制就是用于解决线程间通信的问题的,使用到的3个方法的含义如下:
1). wait :线程不再活动,不再参与调度,进入waitset中,因此不会浪费CPU资源,也不会去竞争锁了,这时
的线程状态即是WAITING。它还要等着别的线程执行一一个特别的动作,也即是"通知( notify)”在这个对象上
等待的线程从wait set中释放出来,重新进入到调度队列( ready queue )中
2). notify :则选取所通知对象的wait set中的一一个线程释放;例如,餐馆有空位置后,等候就餐最久的顾客最先
入座。
3). notifyAll :则释放所通知对象的wait set上的全部线程。
注意:
哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而
此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调
用wait方法之后的地方恢复执行。
总结如下:
●如果能获取锁,线程就从WAITING状态变成RUNNABLE状态;
●否则,从waitset出来,又进入entryset ,线程就从WAITING状态又变成BLOCKED状态
调用wait和notify方法需要注意的细节
1). wait方法与notify方法必须要由同一一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同-一个锁对
象调用的wait方法后的线程。
2). wait方法与notify方法是属于0bject类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继
承了Object类的。
3). wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方
法。
1-3.object类中wait带参方法和notify
进入到wait等待有两种方式:
1).使用sleep(long m)方法,在毫秒值结束之后,线程睡醒后进入到Runnable/Blocked状态。
2).使用wait(long m)方法,wait方法如果在毫秒值结束后还没有被notify唤醒,就会自动醒来,线程进入到Runnable/Blocked状态。
唤醒方法:
void notify() 唤醒的的单个线程,有多个线程时就会随机唤醒一个线程。
void notifyALL() 唤醒所有线程。
1-4.线程之间的通信
概念:多个线程之间在处理同一个资源时,但是处理的动作(线程任务)却不相同时,就会有通信过程,以达到多个线程共同操作一份数据。
2-1.线程池的概念
●线程池:其实就是一一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
当程序第一次启动的时候创建多个线程,保存到一个集合中
当我们想要使用线程的时候就可以从集合中取出来线程使用
Threadt = list.remove(0);返回的是被移除的元素(线程只能被一个任务使用)
Thread t = linked.removeFist();
当我们使用完毕线程,需要把线程归还给线程池
合理利用线程池能够带来三个好处:
1).降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
2).提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
3).提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
2-2.线程池的代码实现
线程池:JDK1.5之后提供的
java. util. concurrent . Executors :线程池的工厂类,用来生成线程池
Executors类中的静态方法:
static ExecutorService newF ixedThreadPool(int nThreads) 创建一个可重用固定线程数的线程池
参数:
int nThreads :创建线程池中包含的线程数量
返回值:
ExecutorService接口,返回的是ExecutorService接口的实现类对象,我们可以使用ExecutorService接口接收(面向接口编程)
java. util. concurrent . ExecutorService:线程池接口
用来从线程池中获取线程,调用start方法,执行线程任务
submit(Runnable task) 提交一个Runnable 任务用于执行
关闭销毁线程池的方法
void shutdown( )
线程池的使用步骤:
1).使用线程池的工厂类Executors里边提供的静态方法newF ixedThreadPool生产- -个指定线程数量的线程池
2).创建一个类,实现Runnable接口,重写run方法,设置线程任务
3).调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法
4).调用ExecutorService中的方法shutdown销毁线程池(不建议执行)
2-3.函数式编程思想
面向对象的思想:做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情.
函数式编程思想:只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程
2-4.Lambda的标准格式
Lambda表达式的标准格式:
由三部分组成:
a.一些参数
b.一个箭头
c.一段代码
格式:
(参数列表) -> {一些重写方法的代码};
解释说明格式:
():接口中抽象方法的参数列表,没有参数,就空着;有参数就写出参数,多个参数使用逗号分隔
-> :传递的意思,把参数传递给方法体{}
{}:重写接口的抽象方法的方法体
2-5.Lambda的省略格式
Lambda是可推导的,也是可省略的。
凡是根据上下文推导出的内容,都可以省略书写。
在Lambda标准格式的基础上,使用省略写法的规则为:
1).小括号内参数的类型可以省略;
2).如果小括号内有且仅有一个参,则小括号可以省略;
3).如果大括号内有且仅有-个语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号。
Lambda表达式的使用前提:
1).使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。
无论是JDK内置的Runnable、Comparator 接口还是自定义的接口,只有当接口中的抽象方法存在且唯- -时 ,
才可以使用Lambda。
2).使用Lambda必须具有上下文推断。
也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。
备注:有且仅有一一个抽象方法的接口,称为“函数式接口"。