线程池
线程池思想
线程池概述
线程池:是一个存储线程的容器,当我们要使用线程的时候,就可以从线程池中获取一个线程,使用完毕在把线程归还到线程池
java.util.concurrent.Executors:是一个创建线程池的工厂类,专门用来生产线程池,里边的方法都是静态的
静态方法:
static ExecutorService newFixedThreadPool(int nThreads)
创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。
参数:
int nThreads:创建线程池,包含线程的数量
返回值:
ExecutorService:ExecutorService就是一个线程池,是一个接口,返回的是ExecutorService接口的实现类对象
可以使用ExecutorService接口来接收这个实现类对象(多态)
java.util.concurrent.ExecutorService:描述线程池的接口
常用的方法:
Future<?> submit(Runnable task) 提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。
Future<T> submit(Callable<T> task) 提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future。
参数:
Runnable task:传递Runnable接口的实现类对象(线程任务)==>重写run方法==>run方法没有返回值
Callable<T> task:传递Callable接口的实现类对象(线程任务)==>重写call方法==>有返回值
返回值:
Future<T>:用来接收线程任务的返回值,用来接收call方法的返回值
使用线程池执行Runnable接口的线程任务
/*
线程池:是一个存储线程的容器,当我们要使用线程的时候,就可以从线程池中获取一个线程,使用完毕在把线程归还到线程池
java.util.concurrent.Executors:是一个创建线程池的工厂类,专门用来生产线程池,里边的方法都是静态的
静态方法:
static ExecutorService newFixedThreadPool(int nThreads)
创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。
参数:
int nThreads:创建线程池,包含线程的数量
返回值:
ExecutorService:ExecutorService就是一个线程池,是一个接口,返回的是ExecutorService接口的实现类对象
可以使用ExecutorService接口来接收这个实现类对象(多态)
java.util.concurrent.ExecutorService:描述线程池的接口
常用的方法:
Future<?> submit(Runnable task) 提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。
Future<T> submit(Callable<T> task) 提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future。
参数:
Runnable task:传递Runnable接口的实现类对象(线程任务)==>重写run方法==>run方法没有返回值
Callable<T> task:传递Callable接口的实现类对象(线程任务)==>重写call方法==>有返回值
返回值:
Future<T>:用来接收线程任务的返回值,用来接收call方法的返回值
-------------------------------------------------------
线程池的实现步骤(重点):
1.使用Executors线程池工厂类中的静态方法newFixedThreadPool创建一个包含指定线程数量的线程池ExecutorService
2.创建Runnable接口|Callable接口的实现类对象,重写run方法|call方法,设置线程任务
3.使用线程池ExecutorService中的方法submit,传递线程任务(Runnable接口|Callable接口的实现类对象)
submit方法会在线程池中获取一个线程,执行线程任务
*/
public class Demo01ThreadPool {
public static void main(String[] args) {
//1.使用Executors线程池工厂类中的静态方法newFixedThreadPool创建一个包含指定线程数量的线程池ExecutorService
ExecutorService es = Executors.newFixedThreadPool(3);//线程池中包含了3个线程
//3.使用线程池ExecutorService中的方法submit,传递线程任务(Runnable接口|Callable接口的实现类对象)submit方法会在线程池中获取一个线程,执行线程任务
//new Thread(new RunnableImpl()).start();
es.submit(new RunnableImpl());//pool-1-thread-2线程正在执行线程任务!
es.submit(new RunnableImpl());//pool-1-thread-1线程正在执行线程任务!
es.submit(new RunnableImpl());//pool-1-thread-3线程正在执行线程任务!
es.submit(new RunnableImpl());//pool-1-thread-2线程正在执行线程任务!
//匿名内部类
es.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程正在执行线程任务!");
}
});
/*
线程池中的方法:(了解)
void shutdown() 用于销毁线程池的方法
注意:
线程池一旦被销毁,就不能在使用了,会抛出异常
*/
es.shutdown();
es.submit(new RunnableImpl());//RejectedExecutionException
}
}
//2.创建Runnable接口|Callable接口的实现类对象,重写run方法|call方法,设置线程任务
public class RunnableImpl implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程正在执行线程任务!");
}
}
使用Callable接口的线程任务
/*
使用线程池执行Callable接口的线程任务(重点)
java.util.concurrent.Callable<V>
返回结果并且可能抛出异常的任务。实现者定义了一个不带任何参数的叫做 call 的方法。
Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。
但是 Runnable 接口没有返回结果,Callable接口有返回的结果
Callable接口的作用:
可以使用Callable接口中的call方法,用于设置线程任务,而这个线程任务包含了一个返回值
Callable接口中的方法:
V call() 计算结果,如果无法计算结果,则抛出一个异常。
返回值:
V:返回一个指定泛型的值,接口使用什么泛型,就返回一个什么类型的值
*/
public class Demo02ThreadPoll {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//1.使用Executors线程池工厂类中的静态方法newFixedThreadPool创建一个包含指定线程数量的线程池ExecutorService
ExecutorService es = Executors.newFixedThreadPool(3);
//2.创建Runnable接口|Callable接口的实现类对象,重写run方法|call方法,设置线程任务
Callable<Integer> callable = new Callable<Integer>(){
@Override
public Integer call() throws Exception {
//返回一个随机的整数
return new Random().nextInt(10);//产生随机数的范围[0-9]
}
};
//3.使用线程池ExecutorService中的方法submit,传递线程任务(Runnable接口|Callable接口的实现类对象)submit方法会在线程池中获取一个线程,执行线程任务
Future<Integer> future = es.submit(callable);
System.out.println(future);//java.util.concurrent.FutureTask@14ae5a5
/*
java.util.concurrent.Future<V>接口
Future 表示异步计算的结果。用来接收call方法的返回值
Future<V>接口中的方法
V get() 如有必要,等待计算完成,然后获取其结果。用于获取对象中接收到的call方法的返回值
*/
Integer v = future.get();
System.out.println(v);
Future<Double> f2 = es.submit(new Callable<Double>() {
@Override
public Double call() throws Exception {
//返回一个随机小数[0.0--1.0]
return Math.random();
}
});
System.out.println(f2.get());
}
}
死锁
死锁原理
死锁代码实现
/*
死锁:两个线程你拿着我的锁,我拿着你的锁,导致两个线程都进不去同步继续执行
前提:
必须有同步代码块的嵌套
必须有两个锁对象
必须有连个线程在执行
*/
public class RunnableImpl implements Runnable{
//创建两个锁对象
private String lockA = "A锁";
private String lockB = "B锁";
//定义一个变量,用于两个线程交叉执行,一人执行一次
int a = 0;
@Override
public void run() {
//定义一个死循环,让线程重复执行
while (true){
//判断a是奇数还是偶数
if(a%2==0){//偶数,让一个线程执行
//同步代码块的嵌套
synchronized (lockA){
System.out.println(Thread.currentThread().getName()+"into if lockA...");
synchronized (lockB){
System.out.println(Thread.currentThread().getName()+"into if lockB...");
}
}
}else {//奇数,让另外一个线程执行
//同步代码块的嵌套
synchronized (lockB){
System.out.println(Thread.currentThread().getName()+"into else lockB...");
synchronized (lockA){
System.out.println(Thread.currentThread().getName()+"into else lockA...");
}
}
}
a++;
}
}
}
//测试类
public class Demo01DeadLock {
public static void main(String[] args) {
//创建两个线程,同时执行线程任务
RunnableImpl r = new RunnableImpl();
new Thread(r).start();
new Thread(r).start();
}
}
执行结果:
Thread-1into if lockA...
Thread-1into if lockB...
Thread-1into else lockB...
Thread-1into else lockA...
Thread-1into if lockA...
Thread-0into else lockB...
注意:平时写代码尽量避免死锁出现
线程状态
Object类中等待与唤醒方法
Object类中的方法:
void wait()
在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
void notify()
唤醒在此对象监视器(同步锁,对象锁)上等待的单个线程。
void notifyAll()
唤醒在此对象监视器上等待的所有线程。
注意:
wait()和notify()方法一般都是在同步代码块中使用
一般都是使用锁对象调用wait()和notify()方法(多个线程使用的是同一个锁对象)
锁对象-->wait()-->Thread-0线程-->等待
锁对象-->notify()-->唤醒在锁对象上等待的线程-->唤醒Thread-0
等待与唤醒案例(重点)
代码实现
/*
包子类:资源类
属性:皮,馅,状态(有,没有)
*/
public class BaoZi {
//皮
String pi;
//馅
String xian;
//包子的状态:初始值为false
boolean flag = false;
}
/*
包子铺类:是一个线程类
线程任务:做包子
对包子的状态进行判断
true:有包子
包子铺等待 wait();
false:没有包子
包子铺线程做包子
打印做x皮x馅的包子
花3秒钟做包子
3秒钟之后做好了包子
修改包子的状态为有
唤醒吃货线程吃包子
注意:
必须保证包子铺线程和吃货线程只能有一个在执行
可以使用同步代码块,需要使用一个锁对象,必须保证两个线程使用同一个锁对象
可以使用 包子对象作为锁对象
定义一个包子变量,使用能构造方法为变量赋值
*/
//包子铺类:是一个线程类
public class BaoZiPu extends Thread{
//定义一个包子变量,使用能构造方法为变量赋值
private BaoZi bz;
public BaoZiPu(BaoZi bz) {
this.bz = bz;
}
//线程任务:做包子
@Override
public void run() {
//增加一个死循环,让包子铺一直做包子
while (true){
//同步代码块
synchronized (bz){
//对包子的状态进行判断
if(bz.flag==true){
//true:有包子,包子铺等待 wait();
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//包子铺线程被唤醒之后,会继续执行wait之后的代码
//false:没有包子,包子铺线程做包子
//打印做x皮x馅的包子
bz.pi = "薄皮";
bz.xian = "牛肉大葱陷";
System.out.println("包子铺线程正在做"+bz.pi+bz.xian+"的包子!");
//花3秒钟做包子
System.out.println("包子铺做包子的需要3秒钟时间!");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//3秒钟之后做好了包子,修改包子的状态为有
bz.flag = true;
//唤醒吃货线程吃包子
bz.notify();//唤醒包子对象上等待的吃货线程
System.out.println("包子铺线程已经做好了"+bz.pi+bz.xian+"的包子,吃货线程赶紧来吃包子吧");
}
}
}
}
/*
吃货类:是一个线程类
线程任务:吃包子
对包子的状态进行判断
false:没有包子
吃货线程等待 包子对象.wait():
true:有包子
吃货线程吃包子
打印吃x皮x馅的包子
花1秒钟吃包子
1秒钟之后吃完包子
修改包子的状态为没有
唤醒包子铺线程做包子
注意:
必须保证包子铺线程和吃货线程只能有一个在执行
可以使用同步代码块,需要使用一个锁对象,必须保证两个线程使用同一个锁对象
可以使用 包子对象作为锁对象
定义一个包子变量,使用能构造方法为变量赋值
*/
/货类:是一个线程类
public class ChiHuo implements Runnable{
//定义一个包子变量,使用能构造方法为变量赋值
private BaoZi bz;
public ChiHuo(BaoZi bz) {
this.bz = bz;
}
//线程任务:吃包子
@Override
public void run() {
//让吃货线程一直吃包子,增加一个死循环
while (true){
//同步代码块
synchronized (bz){
//对包子的状态进行判断
if(bz.flag==false){
//false:没有包子,吃货线程等待 包子对象.wait():
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/货线程被唤醒之后,会继续执行wait之后的代码
//true:有包子,吃货线程吃包子
//打印吃x皮x馅的包子
System.out.println("吃货正在吃"+bz.pi+bz.xian+"的包子!");
//花1秒钟吃包子
System.out.println("吃货吃包子需要1秒钟!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//1秒钟之后吃完包子,修改包子的状态为没有
bz.flag= false;
//唤醒包子铺线程做包子
bz.notify();//唤醒包子对象上等待的包子铺线程
System.out.println("吃货线程已经吃完"+bz.pi+bz.xian+"的包子,包子铺线程感觉做包子把!");
System.out.println("------------------------------------------------------------------");
}
}
}
}
/*
测试类
创建包子对象,创建2个线程,一个线程做包子,一个线程吃包子
*/
public class Demo01WaitAndNotify {
public static void main(String[] args) {
//创建包子对象
BaoZi bz = new BaoZi();
//创建2个线程,一个线程做包子,一个线程吃包子
new BaoZiPu(bz).start();
new Thread(new ChiHuo(bz)).start();
}
}
定时器(重点)
定时器,可以设置线程在某个时间执行某件事情,或者某个时间开始,每间隔指定的时间反复的做某件事情
/*
java.util.Timer类:
一种工具,线程用其安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。
构造方法:
Timer() 创建一个新计时器。
成员方法:
void cancel() 终止此计时器,丢弃所有当前已安排的任务。
注意,在此计时器调用的计时器任务的 run 方法内调用此方法,就可以绝对确保正在执行的任务是此计时器所执行的最后一个任务。
void schedule(TimerTask task, long delay) 安排在指定延迟后执行指定的任务。只执行一次
参数:
TimerTask task:所要安排的任务。定时器要执行的任务
long delay:执行任务前的延迟时间,单位是毫秒。多少毫秒之后执行定时器的任务
void schedule(TimerTask task, long delay, long period) 安排指定的任务从指定的延迟后开始进行重复的固定延迟执行。
参数:
TimerTask task:所要安排的任务。定时器要执行的任务
long delay:执行任务前的延迟时间,单位是毫秒。多少毫秒之后执行定时器的任务
long period:执行各后续任务之间的时间间隔,单位是毫秒。 定时器开始执行之后,每隔多少毫秒重复执行
void schedule(TimerTask task, Date time) 安排在指定的时间执行指定的任务。只执行一次
参数:
TimerTask task:所要安排的任务。定时器要执行的任务
Date time:执行任务的时间。 什么日期开始执行任务 2020-02-12 14:46:45
void schedule(TimerTask task, Date firstTime, long period) 安排指定的任务在指定的时间开始进行重复的固定延迟执行。
参数:
TimerTask task:所要安排的任务。定时器要执行的任务
firstTime - 首次执行任务的时间。 什么日期和事件开始执行任务 2020-02-12 14:46:45
period - 执行各后续任务之间的时间间隔,单位是毫秒。定时器开始执行之后,每隔多少毫秒重复执行
java.util.TimerTask:由 Timer 安排为一次执行或重复执行的任务。
void run() 此计时器任务要执行的操作。重写run方法设置线程任务
*/
public class Demo01Timer {
public static void main(String[] args) throws ParseException {
show04();
}
private static void show04() throws ParseException {
//创建一个反复执行的定时器:2020-02-12 15:02:45开始执行,每隔1秒钟执行1次
Timer t = new Timer();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = sdf.parse("2020-02-12 15:02:50");
t.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("哈哈");
}
},date,1000);
}
private static void show03() throws ParseException {
//创建一个执行一次的定时器:2020-02-12 15:00:45
Timer t = new Timer();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = sdf.parse("2020-02-12 15:00:10");
t.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("哈哈");
t.cancel();
}
},date);
}
private static void show02() {
//创建一个反复执行的定时器,5秒钟之后开始执行,每隔1秒钟执行一次
Timer t = new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(new Date()));
}
},5000,1000);
}
private static void show01() {
//创建一个执行一次的定时器,5秒钟之后开始执行
Timer t = new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("c4爆炸了!");
t.cancel();
}
},5000);
}
}