目录
一 、?进程 &?线程
二、多线程的实现
2.1、继承Thread
2.2、实现Runnable接口
2.3、实现Callable接口
三、Therad类
四、线程安全问题
4.1、同步代码块——synchronized
4.2、同步方法——synchronized
4.3、显示锁——ReentrantLock()
五、线程死锁
六、wait() && notifyAll()
七、线程池——ExecutorService
7.1、缓存线程池——newCachedThreadPool()
7.2、定长线程池——newFixedThreadPool(2)
7.3、单线程线程池——newSingleThreadScheduledExecutor()
7.4、周期任务定长线程池——newScheduledThreadPool(2)
一 、?进程 &?线程
? ? ? ? ? ? ? 在学习多线程之前,我们需要先了解一下?进程?与 线程 之间的关系。
? ? ? ?例子:一个能播放音频 + MV的音乐播放器。
- ? ? ? ? 这个播放器,就是一个进程;
- ? ? ? ??播放音频、播放MV就是这个进程中的两个线程?。
? ? ? ?
? ? ? ? ? ?进程 与 线程 的占用空间概述
- ?进程:?是指一个内存中运行的应用程序 ;各个进程都是有独立的内存空间。
- ?线程:是进程中的一个执行路径,一个进程可以有多个线程。同一个进程里面的线程,? ? ? ? ? ? ? 可以随意切换、执行;共用一个堆内存,有各自的栈空间。
? ? ? ? ?一个进程里面,至少有一个线程;没有的话,该进程就结束运行。
? ? ? ? 线程的调度问题
? ? ? 假设电脑有8个CPU?,也就是可以同时做8件事;
? ? ? 问题来了:? 打开任务管理器,可以发现电脑上有一千多个线程!!! 明明就只能同时做8件事怎会有这么多线程呢??
? ? ?解决方法:? 为了让电脑的8个CPU“同时”处理一千多个线程(注意双引号)。这就需要合理使用电脑CPU。即调度? (调度分为两种) ? ? ? ①分时调度:
? ? ? ? ??把8个CPU的使用时间,均分给电脑全部的线程,(平均分配每个线程占用CPU的时间)? 所以,在一个极短的时间段里,8个CPU,只能执行8个线程
? ?②抢占式调度:
? ? ? ? Java使用的就是?抢占式调度,当CPU空闲时,各个线程就去争抢该CPU,抢到即开始执行线程。? 不过,Java给各个线程设置了优先权,优先级高的,就会先一步获得CPU的使用权。同一等级的,就如上面所言,去争抢CPU。(具有一定的随机性) ?
? ? ? ?在了解完?线程的调度,可能大家还不能明白上面的问题是怎么解决的。下面在详细说一下:? ? ?
? ? ? ? ? ? ?①前提:8个CPU,因此只能同时执行8个线程。
? ? ? ? ? ? ?②抢占式调度,在实际过程中,线程A只执行了?一个“极小”的时间段,然后该CPU又被其他线程抢去,也是执行了一个“极小”的时间段,又被另一个线程抢去......,好不容易线程A再次抢到该CPU,又只是执行了?一个“极小”的时间段,又被其他线程抢去.....,如此循环,直到线程A执行完毕。
? ? ? ? ? ?③所以说,①的前提,要更改成? ?“极小的时间段”里,8个CPU,只能同时执行8个线程。
? ? ? ? ? ?④我们对于这个“极小的时间段”感知不到,只是看上去这8个CPU,“同时”执行了一千多个线程而已。
二、多线程的实现
? ? ? 在大致了解了线程的执行之后,我们下一步就开始学习在Java中如何去编写多线程。目前,有3种方法,下面来逐一介绍。
? ? ? 且main方法,就相当于我Java程序里面的主线程。
2.1、继承Thread
? ?①? 继承Thread以实现多线程的流程:
- ? ? ? ?编写一个类,继承Thread,然后实现Thread类里面的run()方法。
- ? ? ? ?之后创建该类的对象时,一个对象?相当于?一个子线程
- ? ? ? 创建对象之后,不是调用里面的run()方法,而是调用start()方法,来启动?子线程。
? ? ? ?
? ? ? ?该方法弊端:?这个线程?与?任务(执行的代码快)绑定在一起。就是创建n个线程,他们都是执行同一个任务。
package com.qjx;
public class Therad_Test {
public static void main(String[] args) {
MyThread m = new MyThread();
m.start();
for (int i = 0 ; i<5;i++){
System.out.print("主线程"+i+"\t");
}
System.out.println();
}
}
class MyThread extends Thread {
/**
* run方法,就是 线程要执行的任务方法
* 这里的代码 就是一条新的 执行路径
* 该执行路径的 触发方式 : 不是调用run()方法,而是通过thread对象的start()来启动任务
*
*/
@Override
public void run() {
for (int i = 0 ; i<5;i++){
System.out.print("子线程"+i+"\t");
}
System.out.println();
}
}
/**
* 这里 mian 和 m 就是两个线程 ;
* 互相抢占CPU, 只有两个线程 都完成时,程序才会结束
* 每个线程都有 自己的 栈空间; 但同一份 堆内存
*/
2.2、实现Runnable接口
② 实现Runnable接口以实现多线程的流程:
- ? ? ?编写一个类,实现Runnable,然后实现run()方法。
- ? ? 然后创建该类的对象 (创建任务对象)
- ? ? 创建线程,并把任务对象作为参数传递
- ? ?调用start()方法,执行线程
实现Runnable接口 ?与 继承Thread ?相比,有如下优势:
- ? ?Runnable 通过创建任务, ?然后给线程分配任务 以实现多线程; 更适合多个线程同? ? ?时执行相同任务的情况
- ? ?可以避免 单继承 所带来的局限性(java单继承;多实现接口)
- ? ? 任务与线程 ? 两者是分离的,提高了程序的健壮性
- ? ? 后续学习的 线程池技术,接收Runnable类型的任务;不接受Thread类型的线程
?
package 常用类库.多线程;
public class 多线程02 {
public static void main(String[] args) {
/**
* 1. 创建一个任务对象
* 2. 创建一个线程,并且施以 任务对象
* 3. 执行该线程
*/
MyRunnable r2 = new MyRunnable();
Thread t = new Thread(r2);
t.start();
for (int i = 0 ; i<4;i++){
System.out.println("第一线程"+i);
}
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0 ; i<4;i++){
System.out.println("第二线程"+i);
}
}
}
class My implements Runnable{
@Override
public void run() {
for (int i = 0 ; i<4;i++){
System.out.println("第三线程"+i);
}
}
}
2.3、实现Callable接口
③ 实现Callable接口,以实现多线程的流程:
- ? ? ?编写一个类,实现Callable接口,实现Callable类里面的call()方法。并创建该类对象A
- ? ? ?通过FutureTask类,创建任务对象B,并把A作为参数传递
- ? ? ?创建线程对象C,并把B作为参数传递,调用start()方法启动线程? ??
? ? ? ? ? ? ?实现Callable接口、创建对象A时,是需要传入一个泛型值的。
? 与前两种方法的区别:
- ? ? ? ? ? 前两种 :?子线程 &?主线程,是两个执行路径,“同时”执行的。
- ? ? ? ? ? 第三种 :?执行子线程时,主线程会暂时停止,等子线程执行完毕,返回一个值,主线程才重新启动。 (也可以像?前两种一样?与主线程同时执行)。
? ? ? 当然,这个特性,是需要调用get()方法,才会这样。调用了,主线程才会等子线程执行完毕,再执行。(前提:主线程的代码 必须在.get()方法的后面。 在前面,则是主线程先执行完毕,再去执行子线程)?get()方法会返回 Object类型的数据
? ? 没有调用,子线程与主线程 “同时”执行。
package com.qjx;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Callable_Test {
public static void main(String[] args) throws InterruptedException {
Callable<String> m = new My();
FutureTask f = new FutureTask(m); //创建任务
Thread t = new Thread(f); //创建线程
t.start(); //启动线程
try {
Object s = f.get(); //调用get()方法
System.out.println(s);
} catch (ExecutionException e) {
e.printStackTrace();
}
for (int i =0; i<4; i++){
Thread.sleep(1000);
System.out.println("主线程:\t"+i);
}
}
}
class My implements Callable<String> {
@Override
public String call() throws Exception {
for (int i=0; i<4;i++){
Thread.sleep(100);
System.out.println("子线程:\t"+i);
}
return "该子线程 已经完成 结束";
}
}
三、Therad类
? ? ? ? 通过上面?三种实现多线程的方法中,我们可以看到三者都涉及了 Thread类;下面我就来介绍Thread类。
? ? ? ?Thread常见的构造方法:
- ? ? ? Thread()? ? //该线程?还没有任务
- ? ? ? Thread(Runnable? target)? ? //给该线程,赋予一个任务
- ? ? ? Thread(String? name)? ? ? ? ?//给该线程?一个名字
- ? ? ? Thread(Runnable target? ,? String? name)? ? //给线程任务? +?名字
? ? 获取 \ 设置线程名字? ?&&获取线程标识符
- setName()? ?// 设置线程名字
- getName() // 返回线程 名字?
- getId() //返回Thread标识符
- Thread.currentThread() // 获取 当前线程对象
public class Thread_Test {
public static void main(String[] args) {
MyRunnable m1 = new MyRunnable();
Thread t1 = new Thread(m1,"旧名字");
System.out.println(t1.getName());
t1.setName("新名字");
t1.start();
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
System.out.println(Thread.currentThread().getId());
System.out.println("hello");
}
}
?线程休眠 ——sleep
sleep(long millis) //让线程暂时休眠 millis 毫秒
sleep(long millis, int nanos) mills毫秒 + nanos纳秒
/**
* 线程 休眠 sleep
* 需要 抛出异常 or 处理异常
*/
public class Thread_Test {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i<5; i++){
System.out.println(i);
Thread.sleep(1000); //休眠1s ,程序才会继续执行下去
}
}
}
线程阻塞
? ? ? 线程阻塞,不单只是说?线程休眠。
? ? ??而是所有?耗时的操作,都是线程阻塞。
? ? ? 比如说,线程在执行IO操作时,需要读取一个文件里面所有的数据;读取完数据,该线程才会往下执行,那这个读取时间,也就是阻塞时间。
线程中断——interrupt
? ? ? ?一个线程是一个独立的执行路径,它是否结束,应该由其自身决定。
? ? ? ?一个线程可能有许多资源,外部直接“杀死"它,可能导致这些资源―不能释放。占用空间,产生垃圾(可能是还不能回收的垃圾)
? ? ???因此主要需要?以线程中断方式,去结束线程。
- ? “线程名.interrupt();"??//给该线程添加? 中断标记
- ?可以理解为,线程本来就有中断标记,这一句代码,就是给该标记赋值为 true
- ?当子线程进行某些操作时(sleep、wait),就会去检查这个中断标记;当中断标记? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 为?true?就抛出异常InterruptedException。
- ? 然后,异常处理?catch里面? 可以根据需要,去“杀”或者“不杀”该线程。? ?"?return ;"? ?就表示 “杀死”线程。
如果子线程没有某些操作时,即使标记为true,也不会中断。
public class Thread_Test {
public static void main(String[] args) throws InterruptedException {
MyRunnable m1 = new MyRunnable(); //创建任务
Thread t1 = new Thread(m1); //创建 线程
t1.start();
for (int i = 0; i<5; i++){
System.out.println("主 ---"+i);
Thread.sleep(1000);
}
// 给子线程 添加中断标记 .
t1.interrupt();
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i<10; i++){
System.out.println("子 ---"+i);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println("主线程已经执行完毕,检测到中断标记,是否中断子线程,请决定操作: 1--中断 2--继续");
int num = new Scanner(System.in).nextInt();
if (num == 1){
return;
}
}
}
}
}
守护线程——setDaemon(true)
前面说过,进程所有线程结束时,该进程就over 了
现在细说:? ?线程分为-----用户线程-----守护线程
- 用户线程:? 我们前面所看到的―主线程、子线程(直接创建的线程都是用户线程)
- 守护线程:? 我们利用"用户线程名.setDaemon(true)",该用户线程就变成了守护线程。
接着细说:? ?
- ? ? ? ? ? ? ? ? ? 进程? 所有 用户线程结束时,该进程结束
- ? ? ? ? ? ? ? ? ? 最后一个用户线程结束时,守护线程自动死去
public class Thread_Test {
public static void main(String[] args) throws InterruptedException {
MyRunnable m1 = new MyRunnable(); //创建任务
Thread t1 = new Thread(m1); //创建用户线程
t1.setDaemon(true); /// 设置为 守护线程
t1.start();
for (int i = 0; i < 6; i++){
System.out.println(i);
Thread.sleep(1000);
}
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
while (true){ //守护线程 一直执行。直到用户线程(该程序中,指主线程main)结束时,死亡
System.out.println("我是--守护线程---随时等待死亡");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
四、线程安全问题
? ? ? ? Java多线程虽然可以提高工作效率,但是也容易出错。
? ? ? ? ?多个线程,执行同一个任务时,就容易出错!!!!
可以看到执行结果,出现余票为负数的情况:
原因就是:
- ? ??线程A判断的时候,count符合条件﹐
- ? ? 但是下一个“极小”的时间段里,另一个线程B抢到了CPU执行"count--”,以致count小于0 ;
- ? ? 然后线程A就输出了不满足条件的count。
?
public class ThreadTest {
public static void main(String[] args) {
MyRunnable m = new MyRunnable();
new Thread(m).start(); //第 一 个线程 执行下面的任务
new Thread(m).start(); //第 二 个线程 执行下面的任务
new Thread(m).start(); //第 三 个线程 执行下面的任务
}
}
class MyRunnable implements Runnable{
private int count = 10;
@Override
public void run() {
while (count > 0){
System.out.println("正在卖票:");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("出票成功,余票:"+count);
}
}
}
? ?为了解决上面的问题,Java给出了3种方法来解决问题。
4.1、同步代码块——synchronized
格式:
? ? ? ?synchronized(锁对象){? ? ? ? // Java中所有对象,都可以作为锁对象
? ? ? ? ? ? ? 代码块
? ? ? }
理解:
- ? ?? ? ? 当线程A,执行到synchronized部分的代码时,就会去关注,锁对象是否已经上锁(上锁,就表示有一个线程,正在执行这一部分代码)。
- ? ? ? ? 如果已经上锁,线程A就只能等待,等待锁对象?解锁(解锁,就表示这一部分代码,没有线程在执行)。
- ? ? ? ? 没锁、解锁的情况下,线程A又必须与其他线程去争抢这个锁。
class MyRunnable implements Runnable{
private int count = 10;
private Object o = new Object(); //锁对象
@Override
public void run() {
while (true){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o){
if (count > 0){
System.out.println("正在卖票:");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"\t出票成功,余票:"+count);
}else {
break;
}
}
}
}
}
4.2、同步方法——synchronized
? 同步方法,在编写一个方法时,加上“synchronized”。效果如同步代码块一样。
class MyRunnable implements Runnable{
private int count = 10;
@Override
public void run() {
while (true){
boolean f = sale();
if (!f){
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized boolean sale(){
// 该方法也是有 锁对象的,谁调用该方法 ,就是ta
if (count > 0){
System.out.println("正在卖票:");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"出票成功,余票:"+count);
return true;
}
return false;
}
}
4.3、显示锁——ReentrantLock()
同步代码块、同步方法都是隐式锁。??下面我们来了解一下隐式锁。
格式:?private Lock l = new ReentrantLock();? ?//创建锁对象? ,传入参数true,变公平锁
? ? ?l.lock()? //表示上锁,其他线程不能进入,得等待?解锁
? ? ?l.unlock()? //表示解锁,线程之间可以开始争抢锁
class MyRunnable implements Runnable{
private int count = 10;
// 显示锁
private Lock l = new ReentrantLock(); //
@Override
public void run() {
while (true){
l .lock(); //
if (count > 0){
System.out.println("正在卖票:");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"出票成5功,余票:"+count);
}else {
break;
}
l.unlock(); //
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
五、线程死锁
public class ThreadTest {
public static void main(String[] args) {
Culprit c = new Culprit();
Police p = new Police();
new MThread(c,p).start();
c.say(p);
}
}
class MThread extends Thread{
private Culprit c ;
private Police p ;
public MThread(Culprit c , Police p ){
this.c = c;
this.p = p;
}
@Override
public void run() {
p.say(c);
}
}
/**
* 罪犯类
*/
class Culprit{
public synchronized void say(Police p){ //罪犯 说话
System.out.println("罪犯:给我五百万+飞机;我就把人质放了!");
p.fun();
}
public synchronized void fun(){ //罪犯 回答 警察
System.out.println("罪犯的回应:那就准备1百万");
}
}
/**
* 警察类
*/
class Police{
public synchronized void say(Culprit c){ //警察说话
System.out.println("警察:不可能,你赶紧放了人质");
c.fun();
}
public synchronized void fun(){ //警察 回答 罪犯
System.out.println("警察的回应:okokok!一百万");
}
}
解析:? 执行完主线程①时,但还没执行②时 ,子线程的①已经执行。? ??
? ? ? ? ?(因为①到②这一段过程,是“极小的时间段”,你可以暂时理解为? 主、子线程的①是“同时”进行的。方便理解)
主线程:c.say (p )
? ? ? ? ? ? ? ?①?打印了语句;此时c是锁住的? ??
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? (极小时间段)
? ? ? ? ? ? ? ? ? ? ? ? ? ?②执行p.fun ()语句,但是此时p已经锁住了,进不去,也就调用不了fun方法
子线程: p.say(c)
? ? ? ? ? ? ? ?①打印了语句;此时p是锁住的
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? (极小时间段)
? ? ? ? ? ? ? ? ? ? ? ? ?②执行c.fun ()语句,但是此时c已经锁住了,进不去,也就调用不了fun方法
?结果 :? 就锁死了;因为C\P都锁住了,C\P里面的代码就执行不下去,C\P都一直在等锁开。
?当然,也有可能不出现死锁的情况,那就是主线程①、②执行完时,子线程才开始执行①。
避免线程死锁的方法:
? ? ? ?在任何有可能导致锁产生的方法里(产生锁的方法)? ??
? ? ? ?不在调用另一个可能产生锁的方法
? ? (比如:上面代码中,say()方法? 调用了?另一个类的fun()方法)
六、wait() && notifyAll()
AP文档中,Object类里面有两个方法,wait()? && notifyAll()。
- ? wait() :? ?让当前线程?睡眠,直到被唤醒。?
? ? ? ? ? ? ? ? ? ? ? ?参入参数,也是等待被唤醒,或者超过时间参数,自己唤醒。
- notifyAll() :?唤醒等待此对象监视器的所有线程
? 例子:?线程A ------ 厨师? ? ? ? ? ? ? ? ? ? ? 线程B-----?服务员
? ? ? ? ? ? ? ? 任务? ------ 做菜? ? ? ? ? ? ? ? ? ? ? ??任务------ 上菜
? ?原则上,是厨师做完一道菜 ,服务员就把菜端走;厨师再做一道菜。
? ?问题就是:?代码实现时,这两个线程,会抢着执行。所以就会出现下面两个问题
- ? ? ? ? ? ? ? ? ?服务员还没把菜端走,厨师就又做了一道菜。?
- ? ? ? ? ? ? ? ? ?或者厨师还没做下一道菜,服务员这边就又要上菜了。
? ?因此,就需要利用上面两个方法,让厨师做完一道菜时,进入睡眠状态,直到被唤醒;服务员上菜之后,进入睡眠状态,直达被唤醒。
public class ThreadTest {
public static void main(String[] args) {
Food f = new Food(); //创建food对象 f
new Cook(f).start(); //启动 厨师线程
new Waiter(f).start(); //启动 服务员线程
}
//厨师
static class Cook extends Thread{
private Food f;
public Cook(Food f) {
this.f = f;
}
@Override
public void run() {
for(int i=0;i<100;i++){ //厨师的任务,包括setNameAndSaste()
if(i%2==0){
f.setNameAndSaste("老干妈小米粥","香辣味");
}else{
f.setNameAndSaste("煎饼果子","甜辣味");
}
}
}
}
//服务生
static class Waiter extends Thread{
private Food f;
public Waiter(Food f) {
this.f = f;
}
@Override
public void run() {
for(int i=0;i<100;i++){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
f.get(); // 服务员的任务 包括get()方法
}
}
}
/**
* 食物,上面厨师的任务setNameAndSaste , 服务员的任务get。 都针对于食物
* 因此写成一个food类
*
*/
static class Food{
private String name;
private String taste;
//true 表示可以生产
private boolean flag = true;
public synchronized void setNameAndSaste(String name,String taste){
if(flag) {
this.name = name;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.taste = taste;
flag = false;
this.notifyAll(); //唤醒 睡眠的线程,这里就是唤醒服务员线程
try {
this.wait(); // 厨师线程 睡眠
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void get(){
if(!flag) {
System.out.println("服务员端走的菜的名称是:" + name + ",味道:" + taste);
flag = true;
this.notifyAll(); //唤醒 睡眠的线程,这里就是唤醒 厨师线程
try {
this.wait(); //服务员线程 睡眠
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
七、线程池——ExecutorService
? ? ? 后续编程中,我们可能会创建大量线程,但这些线程只执行一次,就不再使用。
? ? ? ?而且,创建线程? &&? 执行任务,两段占用程序的时间里,执行任务可能只占据总时间的5%,大量时间浪费于线程的创建。这样就会降低系统的效率。
线程池:就是线程的容器。
? ? ? 因此,Java引入了线程池的概念,线程池里面可以容纳多个线程,且线程可以重复使用。节省了频繁创建线程所需的时间、资源。
线程池的优点:? ?
- ? ? ? 降低资源消耗。
- ? ? ? 提高响应速度。
- ? ? ? 提高线程的可管理性。
? ? ? ? ?
7.1、缓存线程池——newCachedThreadPool()
长度无限。
执行流程:
- ? ? ? ? ? ? 判断线程池是否存在空闲线程 ,
- ? ? ? ? ? ? 存在则使用空闲线程,
- ? ? ? ? ? ? 不存在,则创建新线程 并放入线程池, 然后使用,新创建的线程也会重复使用
使用格式:
? ExecutorService service = Executors.newCachedThreadPool();? //创建线程池
?service.execute(Runnable?traget)? //执行任务,线程池就会去调用/创建线程,执行该任务
7.2、定长线程池——newFixedThreadPool(2)
长度指定好。
执行流程:
- ? ? ? ? ? ? 判断线程池是否存在空闲线程
- ? ? ? ? ? ? 存在则使用
- ? ? ? ? ? ?不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
- ? ? ? ? ? ?不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
使用格式:
ExecutorService service = Executors.newFixedThreadPool(2);? //创建线程池,指定长度
service.execute( Runnable? target )? //执行任务
? ? ? ?可以看见,定长线程池? &&?缓存线程池,创建方式基本一致。?定长线程池,只是指定了线程池里面可以存放多少个线程。
7.3、单线程线程池——newSingleThreadScheduledExecutor()
?单线程线程池的效果? ?===? ?定长线程池指定长度为1时。
?执行流程:
- ? ? ? ?判断线程池 的那个线程 是否空闲
- ? ? ? ?空闲则使用
- ? ? ? ?不空闲,则等待 池中的单个线程空闲后 使用
使用格式:
ExecutorService service = Executors.newSingleThreadScheduledExecutor();?
service.execute(Runnable? target)
7.4、周期任务定长线程池——newScheduledThreadPool(2)
周期性任务执行时:
? ? ? ? ? ?定时执行, 当某个时机触发时, 自动执行某任务 。
执行流程,与定长线程池一样。
使用格式:
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
service.schedule()
?① 定时执行一次 :? ??
? ? ? 参数1 : 定时执行的 任务?
? ? ? 参数2 : 时长数字? ?,多长时间后执行任务。
? ? ? 参数3 : 时长数字的 时间单位,TimeUnit的常量指定?
? ? ? service.schedule(?target , 5 ,TimeUnit.SECONDS)? ?5秒后,执行target任务
② 周期执行:
? ? ? 参数1 : 定时执行的 任务
? ? ? 参数2 : 延迟时长数字 (第一次执行 在什么时间以后)
? ? ? 参数3 : 周期时长数字 (多久执行一次)
? ? ? 参数4 : 时长数字的 时间单位,TimeUnit的常量指定
service.schedule(?target , 5 ,1,TimeUnit.SECONDS)? ?5秒后,开始执行target任务,之后每隔1秒,执行一次target任务。
?感谢您的观看!
|