网站建设制作设计优化,深圳市宝安区中心医院是三甲吗,qq群营销工具,会展设计专业学什么目录
多线程的创建
方式一#xff1a;继承Thread类
方式二#xff1a;实现Runnable接口
方式三#xff1a;JDK 5.0新增#xff1a;实现Callable接口
Thread的常用方法
线程安全
线程安全问题是什么、发生的原因
线程安全问题案例模拟
线程同步
同步思想概述
方式…目录
多线程的创建
方式一继承Thread类
方式二实现Runnable接口
方式三JDK 5.0新增实现Callable接口
Thread的常用方法
线程安全
线程安全问题是什么、发生的原因
线程安全问题案例模拟
线程同步
同步思想概述
方式一同步代码块
方式二同步方法
方式三Lock锁
线程通信【了解】
线程池【重点】
线程池概述
线程池实现的API、参数说明
线程池处理Runnable任务
线程池处理Callable任务
Executors工具类实现线程池
补充知识定时器
补充知识并发、并行
补充知识线程的生命周期
多线程的创建
什么是线程
线程(thread)是一个程序内部的一条执行路径。
我们之前启动程序执行后main方法的执行其实就是一条单独的执行路径。程序中如果只有一条执行路径那么这个程序就是单线程的程序。
多线程是什么
多线程是指从软硬件上实现多条执行流程的技术。
多线程用在哪里有什么好处
再例如消息通信、淘宝、京东系统都离不开多线程技术。 方式一继承Thread类
Thread类
Java是通过java.lang.Thread 类来代表线程的。
按照面向对象的思想Thread类应该提供了实现多线程的方式。
多线程的实现方案一继承Thread类定义一个子类MyThread继承线程类java.lang.Thread重写run()方法创建MyThread类的对象调用线程对象的start()方法启动线程启动后还是执行run方法的
方式一优缺点优点编码简单缺点线程类已经继承Thread无法继承其他类不利于扩展。/*** 目标多线程的创建方式一继承Thread类实现。*/
public class Test {public static void main(String[] args) throws Exception {//3、new一个新线程对象Thread t new MyThread();//4、调用start方法启动线程执行的还是run方法t.start();for (int i 0; i 5; i) {System.out.println(主线程执行输出 i);}}
}/*** 1、定义一个线程类型继承Thread类*/class MyThread extends Thread {/*** 2、重写run方法里面是定义线程以后要干啥*/Overridepublic void run() {for (int i 0; i 5; i) {System.out.println(子线程执行输出 i);}}
}
1、为什么不直接调用了run方法而是调用start启动线程。直接调用run方法会当成普通方法执行此时相当于还是单线程执行。只有调用start方法才是启动一个新的线程执行。
2、把主线程任务放在子线程之前了。这样主线程一直是先跑完的相当于是一个单线程的效果了。
3、方式一是如何实现多线程的继承Thread类重写run方法创建线程对象调用start()方法启动。
4、优缺点是什么优点编码简单缺点存在单继承的局限性线程类继承Thread后不能继承其他类不便于扩展。
方式二实现Runnable接口
定义一个线程任务类MyRunnable实现Runnable接口重写run()方法
创建MyRunnable任务对象
把MyRunnable任务对象交给Thread处理。
调用线程对象的start()方法启动线程Thread的构造器 构造器 说明 public Thread(String name) 可以为当前线程指定名称 public Thread(Runnable target) 封装Runnable对象成为线程对象 public Thread(Runnable target String name ) 封装Runnable对象成为线程对象并指定线程名称
/*** 目标学会线程的创建方式二理解它的优缺点。*/
public class Test {public static void main(String[] args) throws Exception {//3、创建一个任务对象Runnable target new MyRunnable();//4、把任务对象交给Thread处理Thread t new Thread(target);//5、启动线程t.start();for (int i 0; i 5; i) {System.out.println(主线程执行输出 i);}}
}/*** 1、定义一个线程任务类 实现Runnable接口*/
class MyRunnable implements Runnable {/*** 2、重写run方法定义线程的执行任务的*/Overridepublic void run() {for (int i 0; i 5; i) {System.out.println(子线程执行输出 i);}}
}
1、第二种方式是如何创建线程的定义一个线程任务类MyRunnable实现Runnable接口重写run()方法创建MyRunnable对象把MyRunnable任务对象交给Thread线程对象处理。调用线程对象的start()方法启动线程
2、第二种方式的优点优点线程任务类只是实现了Runnale接口可以继续继承和实现。缺点如果线程有执行结果是不能直接返回的。多线程的实现方案二实现Runnable接口(匿名内部类形式)可以创建Runnable的匿名内部类对象。交给Thread处理。调用线程对象的start()启动线程。 public class Test {public static void main(String[] args) throws Exception {//创建一个任务对象
// Runnable target new Runnable() {
// Override
// public void run() {
// for (int i 0; i 5; i) {
// System.out.println(子线程执行输出 i);
// }
// }
// };Runnable target () - {for (int i 0; i 5; i) {System.out.println(子线1程执行输出 i);}};//把任务对象交给Thread处理Thread t new Thread(target);//启动线程t.start();new Thread(new Runnable() {Overridepublic void run() {for (int i 0; i 5; i) {System.out.println(子线2程执行输出 i);}}}).start();new Thread(() - {for (int i 0; i 5; i) {System.out.println(子线3程执行输出 i);}}).start();for (int i 0; i 5; i) {System.out.println(主线程执行输出 i);}}
}方式三JDK 5.0新增实现Callable接口
1、前2种线程创建方式都存在一个问题他们重写的run方法均不能直接返回结果。不适合需要返回线程执行结果的业务场景。2、怎么解决这个问题呢JDK 5.0提供了Callable和FutureTask来实现。这种方式的优点是可以得到线程执行的结果。多线程的实现方案三利用Callable、FutureTask接口实现。
1、得到任务对象定义类实现Callable接口重写call方法封装要做的事情。用FutureTask把Callable对象封装成线程任务对象。
2、把线程任务对象交给Thread处理。
3、调用Thread的start方法启动线程执行任务
4、线程执行完毕后、通过FutureTask的get方法去获取任务执行的结果。FutureTask的API 方法名称 说明 public FutureTask(Callable call) 把Callable对象封装成FutureTask对象。 public V get() throws Exception 获取线程执行call方法返回的结果。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;/*** 目标学会线程的创建方式三实现Callable接口结合FutureTask完成。*/
public class Test {public static void main(String[] args) {//3、创建Callable任务对象CallableString callable1 new MyCallable(100);//4、把Callable任务对象交给FutureTask对象// FutureTask对象的作用1 是Runnable的对象实现了Runnable接口可以交给Thread了// FutureTask对象的作用2 可以在线程执行完毕之后通过调用其get方法得到线程执行完成的结果FutureTaskString f1 new FutureTask(callable1);//5、交给线程处理Thread t1 new Thread(f1);//6、启动线程t1.start();CallableString callable2 new MyCallable(200);FutureTaskString f2 new FutureTask(callable2);Thread t2 new Thread(f2);t2.start();try {// 如果f1任务没有执行完毕这里的代码会等待直到线程1跑完才提取结果。String rs1 f1.get();System.out.println(第一个结果 rs1);} catch (Exception e) {e.printStackTrace();}try {// 如果f2任务没有执行完毕这里的代码会等待直到线程2跑完才提取结果。String rs2 f2.get();System.out.println(第二个结果 rs2);} catch (Exception e) {e.printStackTrace();}}
}/*** 1、定义一个任务类实现Callable接口 应该申明线程任务执行完毕后的结果的数据类型*/
class MyCallable implements CallableString {private int n;public MyCallable(int n) {this.n n;}/*** 2、重写方法*/Overridepublic String call() throws Exception {int sum 0;for (int i 0; i n; i) {sum i;}return 子线程执行的结果是 sum;}
}
方式三优缺点
优点线程任务类只是实现接口可以继续继承类和实现接口扩展性强。
可以在线程执行完毕后去获取线程执行的结果。
缺点编码复杂一点。3种方式对比 方式 优点 缺点 继承Thread类 编程比较简单可以直接使用Thread类中的方法 扩展性较差不能再继承其他的类不能返回线程执行的结果 实现Runnable接口 扩展性强实现该接口的同时还可以继承其他的类。 编程相对复杂不能返回线程执行的结果 实现Callable接口 扩展性强实现该接口的同时还可以继承其他的类。可以得到线程执行的结果 编程相对复杂
Thread的常用方法
Thread常用API说明Thread常用方法获取线程名称getName()、设置名称setName()、获取当前线程对象currentThread()。至于Thread类提供的诸如yield、join、interrupt、不推荐的方法 stop 、守护线程、线程优先级等线程的控制方法在开发中很少使用
1. 当有很多线程在执行的时候我们怎么去区分这些线程呢此时需要使用Thread的常用方法getName()、setName()、currentThread()等。Thread获取和设置线程名称 方法名称 说明 String getName() 获取当前线程的名称默认线程名称是Thread-索引 void setName(String name) 将此线程的名称更改为指定的名称通过构造器也可以设置线程名称 Thread类获得当前线程的对象 方法名称 说明 public static Thread currentThread() 返回对当前正在执行的线程对象的引用
注意
1、此方法是Thread类的静态方法可以直接使用Thread类调用。
2、这个方法是在哪个线程执行中调用的就会得到哪个线程对象。Thread的构造器 方法名称 说明 public Thread(String name) 可以为当前线程指定名称 public Thread(Runnable target) 封装Runnable对象成为线程对象 public Thread(Runnable target String name ) 封装Runnable对象成为线程对象并指定线程名称
/*** 目标线程的API*/
public class Test {//main方法是由主线程负责调度的public static void main(String[] args) {//Thread t1 new MyThread();Thread t1 new MyThread(1号);//调用有参构造器//t1.setName(1号);t1.start();//启动线程System.out.println(t1.getName());//Thread t2 new MyThread();Thread t2 new MyThread(2号);//t2.setName(2号);t2.start();//启动线程System.out.println(t2.getName());//哪个线程执行它它就得到哪个线程对象当前线程对象//主线程的名称就叫mainThread m Thread.currentThread();System.out.println(m.getName());for (int i 0; i 5; i) {System.out.println(主线程输出 i);}}
}class MyThread extends Thread {public MyThread() {}public MyThread(String name) {// 为当前线程对象设置名称送给父类的有参数构造器初始化名称super(name);}Overridepublic void run() {for (int i 0; i 5; i) {System.out.println(Thread.currentThread().getName() 输出 i);}}
}
Thread类的线程休眠方法 方法名称 说明 public static void sleep(long time) 让当前线程休眠指定的时间后再继续执行单位为毫秒。
/*** 目标线程的API*/
public class Test {//main方法是由主线程负责调度的public static void main(String[] args) throws Exception {for (int i 0; i 5; i) {System.out.println(输出 i);if (i 3) {// 让当前线程进入休眠状态// 段子项目经理让我加上这行代码如果用户愿意交钱我就注释掉。Thread.sleep(3000);//休眠3s}}}
}线程安全
线程安全问题是什么、发生的原因
线程安全问题多个线程同时操作同一个共享资源的时候可能会出现业务安全问题称为线程安全问题。
取钱模型演示需求小明和小红是一对夫妻他们有一个共同的账户余额是10万元。如果小明和小红同时来取钱而且2人都要取钱10万元可能出现什么问题呢线程安全问题出现的原因存在多线程并发同时访问共享资源存在修改共享资源
线程安全问题案例模拟
案例取钱业务
需求小明和小红是一对夫妻他们有一个共同的账户余额是10万元模拟2人同时去取钱10万。
分析①需要提供一个账户类创建一个账户对象代表2个人的共享账户。②需要定义一个线程类线程类可以处理账户对象。③创建2个线程对象传入同一个账户对象。④启动2个线程去同一个账户对象中取钱10万。/*** 取钱的线程类*/
public class DrawThread extends Thread {//接收处理的账户对象private Account acc;public DrawThread(Account acc, String name) {super(name);this.acc acc;}Overridepublic void run() {//小明 小红 取钱acc.drawMoney(100000);}
}
public class Account {private String cardId;//卡号private double money;//账户金额public Account() {}public Account(String cardId, double money) {this.cardId cardId;this.money money;}//小明 小红public void drawMoney(double money) {//1、先获取是谁来取钱线程的名字就是人名String name Thread.currentThread().getName();//1、判断账户是否够钱if (this.money money) {//3、取钱System.out.println(name 来取钱成功取出 money);//4、更新账户金额this.money - money;System.out.println(name来取钱后剩余this.money);}else {//5、金额不足System.out.println(name来取钱余额不足);}}public String getCardId() {return cardId;}public void setCardId(String cardId) {this.cardId cardId;}public double getMoney() {return money;}public void setMoney(double money) {this.money money;}
}public class Test {public static void main(String[] args) throws Exception {//1、定义线程类创建一个共享的账户对象Account acc new Account(ICBC-111, 100000);//2、创建两个线程对象代表小明和小红同时进来new DrawThread(acc, 小明).start();new DrawThread(acc, 小红).start();}
}线程安全问题发生的原因是什么多个线程同时访问同一个共享资源且存在修改该资源。
线程同步
同步思想概述
线程同步为了解决线程安全问题。
1、取钱案例出现问题的原因多个线程同时执行发现账户都是够钱的。
2、如何才能保证线程安全呢让多个线程实现先后依次访问共享资源这样就解决了安全问题
线程同步的核心思想加锁把共享资源进行上锁每次只能一个线程进入访问完毕以后解锁然后其他线程才能进来。 线程同步解决安全问题的思想是什么加锁让多个线程实现先后依次访问共享资源这样就解决了安全问题。
方式一同步代码块
同步代码块
作用把出现线程安全问题的核心代码给上锁。
原理每次只能一个线程进入执行完毕后自动解锁其他线程才可以进来执行。
synchronized(同步锁对象) {操作共享资源的代码(核心代码)
}
锁对象要求理论上锁对象只要对于当前同时执行的线程来说是同一个对象即可。/***同步代码块* 锁住操作共享资源的代码(核心代码)*/
synchronized (suo) {//1、判断账户是否够钱if (this.money money) {//3、取钱System.out.println(name 来取钱成功取出 money);//4、更新账户金额this.money - money;System.out.println(name来取钱后剩余this.money);}else {//5、金额不足System.out.println(name来取钱余额不足);}}
锁对象用任意唯一的对象好不好呢?不好会影响其他无关线程的执行。
锁对象的规范要求规范上建议使用共享资源作为锁对象。对于实例方法建议使用this作为锁对象。对于静态方法建议使用字节码类名.class对象作为锁对象。
1、同步代码块是如何实现线程安全的对出现问题的核心代码使用synchronized进行加锁每次只能一个线程占锁进入访问
2. 同步代码块的同步锁对象有什么要求 对于实例方法建议使用this作为锁对象。对于静态方法建议使用字节码类名.class对象作为锁对象。/*** 对于静态方法建议使用字节码类名.class对象作为锁对象*/
// public static void run(){
// synchronized (Account.class){
//
// }
// }//小明 小红public void drawMoney(double money) {//1、先获取是谁来取钱线程的名字就是人名String name Thread.currentThread().getName();/***同步代码块* 锁住操作共享资源的代码(核心代码)* this acc 共享账户*/synchronized (this) {//1、判断账户是否够钱if (this.money money) {//3、取钱System.out.println(name 来取钱成功取出 money);//4、更新账户金额this.money - money;System.out.println(name来取钱后剩余this.money);}else {//5、金额不足System.out.println(name来取钱余额不足);}}}
方式二同步方法
同步方法作用把出现线程安全问题的核心方法给上锁。原理每次只能一个线程进入执行完毕以后自动解锁其他线程才可以进来执行。
格式
修饰符 synchronized 返回值类型 方法名称(形参列表) {操作共享资源的代码
}
同步方法底层原理同步方法其实底层也是有隐式锁对象的只是锁的范围是整个方法代码。如果方法是实例方法同步方法默认用this作为的锁对象。但是代码要高度面向对象如果方法是静态方法同步方法默认用类名.class作为的锁对象。public synchronized void drawMoney(double money) {//1、先获取是谁来取钱线程的名字就是人名String name Thread.currentThread().getName();//1、判断账户是否够钱if (this.money money) {//3、取钱System.out.println(name 来取钱成功取出 money);//4、更新账户金额this.money - money;System.out.println(name来取钱后剩余this.money);}else {//5、金额不足System.out.println(name来取钱余额不足);}}
1、是同步代码块好还是同步方法好一点
同步代码块锁的范围更小同步方法锁的范围更大。总结
1、同步方法是如何保证线程安全的对出现问题的核心方法使用synchronized修饰每次只能一个线程占锁进入访问
2、同步方法的同步锁对象的原理 对于实例方法默认使用this作为锁对象。对于静态方法默认使用类名.class对象作为锁对象。
方式三Lock锁
Lock锁
为了更清晰的表达如何加锁和释放锁JDK5以后提供了一个新的锁对象Lock更加灵活、方便。
Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作。
Lock是接口不能直接实例化这里采用它的实现类ReentrantLock来构建Lock锁对象。 方法名称 说明 public ReentrantLock() 获得Lock锁的实现类对象 Lock的API 方法名称 说明 void lock() 获得锁 void unlock() 释放锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class Account {private String cardId;//卡号private double money;//账户金额//final修饰后锁对象是唯一不可替换的非常专业private final Lock lock new ReentrantLock();public Account() {}public Account(String cardId, double money) {this.cardId cardId;this.money money;}//小明 小红public void drawMoney(double money) {//1、先获取是谁来取钱线程的名字就是人名String name Thread.currentThread().getName();//2、判断账户是否够钱lock.lock();//上锁try {if (this.money money) {//3、取钱System.out.println(name 来取钱成功取出 money);//4、更新账户金额this.money - money;System.out.println(name 来取钱后剩余 this.money);//System.out.println(10/0);//解锁放进finally避免出现bug不能解锁} else {//5、金额不足System.out.println(name 来取钱余额不足);}} finally {lock.unlock();//解锁}}public String getCardId() {return cardId;}public void setCardId(String cardId) {this.cardId cardId;}public double getMoney() {return money;}public void setMoney(double money) {this.money money;}
}线程通信【了解】
什么是线程通信、如何实现所谓线程通信就是线程间相互发送数据线程间共享一个资源即可实现线程通信。
线程通信常见形式通过共享一个数据的方式实现。根据共享数据的情况决定自己该怎么做以及通知其他线程怎么做。
线程通信实际应用场景生产者与消费者模型生产者线程负责生产数据消费者线程负责消费生产者产生的数据。要求生产者线程生产完数据后唤醒消费者然后等待自己消费者消费完该数据后唤醒生产者然后等待自己。Object类的等待和唤醒方法 方法名称 说明 void wait() 让当前线程等待并释放所占锁直到另一个线程调用notify()方法或 notifyAll()方法 void notify() 唤醒正在等待的单个线程 void notifyAll() 唤醒正在等待的所有线程
注意
上述方法应该使用当前同步锁对象进行调用。线程池【重点】
线程池概述
什么是线程池线程池就是一个可以复用线程的技术。
不使用线程池的问题 如果用户每发起一个请求后台就创建一个新线程来处理下次新任务来了又要创建新线程而创建新线程的开销是很大的这样会严重影响系统的性能。
线程池的工作原理 线程池实现的API、参数说明
谁代表线程池
JDK 5.0起提供了代表线程池的接口ExecutorService
如何得到线程池对象
方式一使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象
方式二使用Executors线程池的工具类调用方法返回不同特点的线程池对象 ThreadPoolExecutor构造器的参数说明
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueueRunnable workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) 线程池常见面试题
临时线程什么时候创建啊新任务提交时发现核心线程都在忙任务队列也满了并且还可以创建临时线程此时才会创建临时线程。
什么时候会开始拒绝任务核心线程和临时线程都在忙任务队列也满了新的任务过来的时候才会开始任务拒绝。线程池处理Runnable任务
ThreadPoolExecutor创建线程池对象示例
ExecutorService pools new ThreadPoolExecutor(3, 5, 8 , TimeUnit.SECONDS, new ArrayBlockingQueue(6),Executors.defaultThreadFactory() , new ThreadPoolExecutor.AbortPolicy());ExecutorService的常用方法 方法名称 说明 void execute(Runnable command) 执行任务/命令没有返回值一般用来执行 Runnable 任务 FutureT submit(CallableT task) 执行任务返回未来任务对象获取线程结果一般拿来执行 Callable 任务 void shutdown() 等任务执行完毕后关闭线程池 ListRunnable shutdownNow() 立刻关闭停止正在执行的任务并返回队列中未执行的任务
新任务拒绝策略 策略 详解 ThreadPoolExecutor.AbortPolicy 丢弃任务并抛出RejectedExecutionException异常。是默认的策略 ThreadPoolExecutor.DiscardPolicy 丢弃任务但是不抛出异常 这是不推荐的做法 ThreadPoolExecutor.DiscardOldestPolicy 抛弃队列中等待最久的任务 然后把当前任务加入队列中 ThreadPoolExecutor.CallerRunsPolicy 由主线程负责调用任务的run()方法从而绕过线程池直接执行
public class MyRunnable implements Runnable{Overridepublic void run() {for (int i 0; i 5; i) {System.out.println(Thread.currentThread().getName() 输出了HelloWorld i);}try {System.out.println(Thread.currentThread().getName() 本任务与线程绑定了线程进入休眠了~~~);Thread.sleep(10000000);} catch (Exception e) {e.printStackTrace();}}
}import java.util.concurrent.*;/**目标自定义一个线程池对象并测试其特性。*/
public class ThreadPoolDemo1 {public static void main(String[] args) {// 1、创建线程池对象/**public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueueRunnable workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)*/ExecutorService pool new ThreadPoolExecutor(3, 5 ,6, TimeUnit.SECONDS, new ArrayBlockingQueue(5) , Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy() );// 2、给任务线程池处理。Runnable target new MyRunnable();pool.execute(target);pool.execute(target);pool.execute(target);pool.execute(target);pool.execute(target);pool.execute(target);pool.execute(target);pool.execute(target);// 创建临时线程pool.execute(target);pool.execute(target);
// // 不创建拒绝策略被触发
// pool.execute(target);// 关闭线程池开发中一般不会使用。// pool.shutdownNow(); // 立即关闭即使任务没有完成会丢失任务的pool.shutdown(); // 会等待全部任务执行完毕之后再关闭建议使用的}
}线程池处理Callable任务
ExecutorService的常用方法 方法名称 说明 void execute(Runnable command) 执行任务/命令没有返回值一般用来执行 Runnable 任务 FutureT submit(CallableT task) 执行Callable任务返回未来任务对象获取线程结果 void shutdown() 等任务执行完毕后关闭线程池 ListRunnable shutdownNow() 立刻关闭停止正在执行的任务并返回队列中未执行的任务
import java.util.concurrent.Callable;/**1、定义一个任务类 实现Callable接口 应该申明线程任务执行完毕后的结果的数据类型*/
public class MyCallable implements CallableString{private int n;public MyCallable(int n) {this.n n;}/**2、重写call方法任务方法*/Overridepublic String call() throws Exception {int sum 0;for (int i 1; i n ; i) {sum i;}return Thread.currentThread().getName() 执行 1- n 的和结果是 sum;}
}
import java.util.concurrent.*;/**目标自定义一个线程池对象并测试其特性。*/
public class ThreadPoolDemo2 {public static void main(String[] args) throws Exception {// 1、创建线程池对象/**public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueueRunnable workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)*/ExecutorService pool new ThreadPoolExecutor(3, 5 ,6, TimeUnit.SECONDS, new ArrayBlockingQueue(5) , Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy() );// 2、给任务线程池处理。FutureString f1 pool.submit(new MyCallable(100));FutureString f2 pool.submit(new MyCallable(200));FutureString f3 pool.submit(new MyCallable(300));FutureString f4 pool.submit(new MyCallable(400));FutureString f5 pool.submit(new MyCallable(500));// String rs f1.get();
// System.out.println(rs);System.out.println(f1.get());System.out.println(f2.get());System.out.println(f3.get());System.out.println(f4.get());System.out.println(f5.get());}
}
Executors工具类实现线程池
Executors得到线程池对象的常用方法
Executors线程池的工具类通过调用方法返回不同类型的线程池对象。 方法名称 说明 public static ExecutorService newCachedThreadPool() 线程数量随着任务增加而增加如果线程任务执行完毕且空闲了一段时间则会被回收掉。 public static ExecutorService newFixedThreadPool(int nThreads) 创建固定线程数量的线程池如果某个线程因为执行异常而结束那么线程池会补充一个新线程替代它。 public static ExecutorService newSingleThreadExecutor () 创建只有一个线程的线程池对象如果该线程出现异常而结束那么线程池会补充一个新线程。 public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 创建一个线程池可以实现在给定的延迟后运行任务或者定期执行任务。
import java.util.concurrent.*;/**目标使用Executors的工具方法直接得到一个线程池对象。*/
public class ThreadPoolDemo3 {public static void main(String[] args) throws Exception {// 1、创建固定线程数据的线程池ExecutorService pool Executors.newFixedThreadPool(3);pool.execute(new MyRunnable());pool.execute(new MyRunnable());pool.execute(new MyRunnable());pool.execute(new MyRunnable()); // 已经没有多余线程了}
}注意Executors的底层其实也是基于线程池的实现类ThreadPoolExecutor创建线程池对象的。Executors使用可能存在的陷阱
大型并发系统环境中使用Executors如果不注意可能会出现系统风险。 方法名称 存在问题 public static ExecutorService newFixedThreadPool(int nThreads) 允许请求的任务队列长度是Integer.MAX_VALUE可能出现OOM错误 java.lang.OutOfMemoryError public static ExecutorService newSingleThreadExecutor() public static ExecutorService newCachedThreadPool() 创建的线程数量最大上限是Integer.MAX_VALUE 线程数可能会随着任务1:1增长也可能出现OOM错误 java.lang.OutOfMemoryError public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
Executors使用可能存在的陷阱
大型并发系统环境中使用Executors如果不注意可能会出现系统风险。补充知识定时器
定时器
定时器是一种控制任务延时调用或者周期调用的技术。
作用闹钟、定时邮件发送。
定时器的实现方式
方式一Timer
方式二 ScheduledExecutorService构造器 说明 public Timer() 创建Timer定时器对象 方法 说明 public void schedule(TimerTask task, long delay, long period) 开启一个定时器按照计划处理TimerTask任务
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;/*** Timer定时器的使用和了解*/
public class Test {public static void main(String[] args) {//1、创建Timer定时器Timer timer new Timer();//定时器本身就是一个单线程//2、调用方法处理定时任务timer.schedule(new TimerTask() {Overridepublic void run() {System.out.println(Thread.currentThread().getName()执行AA~~new Date());
// try {
// Thread.sleep(5000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }//System.out.println(10/0);}},3000,2000);timer.schedule(new TimerTask() {Overridepublic void run() {System.out.println(Thread.currentThread().getName()执行BB~~new Date());}},3000,2000);}
}Timer定时器的特点和存在的问题
1、Timer是单线程处理多个任务按照顺序执行存在延时与设置定时器的时间有出入。
2、可能因为其中的某个任务的异常使Timer线程死掉从而影响后续任务执行。ScheduledExecutorService定时器ScheduledExecutorService是 jdk1.5中引入了并发包目的是为了弥补Timer的缺陷, ScheduledExecutorService内部为线程池。Executors的方法 说明 public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 得到线程池对象 ScheduledExecutorService的方法 说明 public ScheduledFuture? scheduleAtFixedRate(Runnable command, long initialDelay, long period,TimeUnit unit) 周期调度方法
import java.util.Date;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;/*** Timer定时器的使用和了解*/
public class Test {public static void main(String[] args) {// 1、创建ScheduledExecutorService线程池做定时器ScheduledExecutorService pool Executors.newScheduledThreadPool(3);//2、开启定时任务pool.scheduleAtFixedRate(new TimerTask() {Overridepublic void run() {System.out.println(Thread.currentThread().getName() 执行输出AAA new Date());try {Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}}}, 0, 2, TimeUnit.SECONDS);pool.scheduleAtFixedRate(new TimerTask() {Overridepublic void run() {System.out.println(Thread.currentThread().getName() 执行输出BBB new Date());System.out.println(10 / 0);}}, 0, 2, TimeUnit.SECONDS);pool.scheduleAtFixedRate(new TimerTask() {Overridepublic void run() {System.out.println(Thread.currentThread().getName() 执行输出CCC new Date());}}, 0, 2, TimeUnit.SECONDS);}
}
ScheduledExecutorService的优点
1、基于线程池某个任务的执行情况不会影响其他定时任务的执行。补充知识并发、并行
并发与并行
正在运行的程序软件就是一个独立的进程 线程是属于进程的多个线程其实是并发与并行同时进行的。
并发的理解
CPU同时处理线程的数量有限。
CPU会轮询为系统的每个线程服务由于CPU切换的速度很快感觉这些线程在同时执行这就是并发。并行的理解
在同一个时刻上同时有多个线程在被CPU处理并执行。简单说说并发和并行的含义
并发CPU分时轮询的执行线程。
并行同一个时刻同时在执行。
补充知识线程的生命周期 线程的状态
线程的状态也就是线程从生到死的过程以及中间经历的各种状态及状态转换。
理解线程的状态有利于提升并发编程的理解能力。
Java线程的状态
Java总共定义了6种状态
6种状态都定义在Thread类的内部枚举类中。线程的6种状态总结