线程
- 实现方式3种
- 乐观锁&悲观锁
- 线程池
- 线程池总结
进程:是正在运行的程序
线程:是进程中的单个顺序控制流,是一条执行路径
实现方式3种
1.Thread
//步骤一:定义一个继承Thread的类
//步骤二:再定义的类中重写run()方法
//步骤三:创建定义类对象
//步骤四:启动线程
class MyThread extends Thread{
@Override
public void run(){
System.out.println("线程执行了");
}
}
public static void main(String[] args){
MyThread my1= new MyThread();
my1.start();
}
2.Runnable
//步骤一:定义一个实现Runnable接口
//步骤二:再定义的类中重写run()方法
//步骤三:创建定义类对象
//步骤四:创建Thread类对象,把定义的对象作为构造方法的参数
//步骤五:启动线程
class MyRunnable implements Runnable{
@Override
public void run(){
System.out.println("线程执行了");
}
}
public static void main(String[] args){
MyRunnable my1= new MyRunnable();
//Thread thread= new Thread(my1);
Thread thread= new Thread(my1,"第一个线程");
thread.start();
}
3.Callable
public class TwoThread implements Callable<String> {
@Override
public String call() throws Exception {
return "卢本伟";
}
}
@Test
void test1() throws ExecutionException, InterruptedException {
TwoThread twoThread = new TwoThread();
FutureTask<String> stringFutureTask = new FutureTask<>(twoThread);
Thread thread2 = new Thread(stringFutureTask);
thread2.start();
System.out.println(stringFutureTask.get())
}
设置以及获取线程名称
void setName(String name):将线程的名字更改为等于参数name
String getName():获取线程的名称
currentThread():返回对当前正在执行的线程对象的引用
Thread.currentThread().getName()
线程调度
模型 | 解释说明 |
---|---|
分时调度 | 所有的线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片 |
抢占式调度 | 有限让优先级高的线程使用CPU,如果线程的优先级相同,会进行随机选择,优先级高的线程获取的CPU的时间会相对的多一些 |
设置/获取优先级
public final int getPriority():返回此线程的优先级
public final void setPriority(int newPriority):更改此线程的优先级(1-10)
线程控制
static void sleep(long millis): 使用当前正在执行的线程停留指定的毫秒数
void join : 等待这个线程死亡
void setDaemon(boolean on) : 将此线程标记为守护线程,当运行的线程为守护贤臣改的视乎,java虚拟机将会退出
线程生命周期
多线程数据安全问题
1.同步代码块
synchronized(任意对象){
多条语句操作共享数据的代码
}
2.同步方法
同步方法锁默认使用的是this
修飾符 synchronized 返回值類型 方法名(方法参数){}
静态方法锁对象是SellTicket.class对象
lock锁
void lock : 获得锁
void unlock() : 释放锁
Lock是一个接口不能直接进行实例化,所以需要他的实现类ReentrantLoock进行实例化
创建一个new ReentrantLoock()实例然后再去使用方法
等待&唤醒
void wait():导致当前线程等待,直到另外一个线程调用该对象的notify()方法或者notifyAll()方法
void notify():唤醒正在等待对象监视器的单个线程
void notifyAll():唤醒正在等待对象监视器的所有线程
乐观锁&悲观锁
乐观锁:
指的是在操作数据的时候非常乐观,认为别人不会同时修改数据,因此乐观锁是不会上锁的,只是在执行更新的时候判断一下再次期间别人是否修改了数据,如果修改了数据规则,则放弃本次唱操作,否则继续执行操作
悲观锁:
指的是在操作数据的时候非常悲观,认为别人会同时修改数据,因此再操作数据的时候就会直接将数据锁住,知道操作完成才会释放锁,上锁期间其他人不能修改数据
其中synchronized和lock的实现都是悲观锁
乐观锁的实现方式:
1.CAS机制和版本号控制
需要读写的内存位置(V)
进行比较的预期值(A)
拟写入的新值(B)
CAS操作逻辑如下:如果内存位置V的值等于预期的A值,则将该位置更新为新值B,否则不进行任何操作。许多CAS的操作是自旋的:如果操作不成功,会一直重试,直到操作成功为止。
版本控制:
版本号机制的基本思路是在数据中增加一个字段version,表示该数据的版本号,每当数据被修改,版本号加1。当某个线程查询数据时,将该数据的版本号一起查出来;当该线程更新数据时,判断当前版本号与之前读取的版本号是否一致,如果一致才进行操作。需要注意的是,这里使用了版本号作为判断数据变化的标记,实际上可以根据实际情况选用其他能够标记数据版本的字段,如时间戳等
线程池
线程池其实就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。这里的线程就是我们前面学过的线程,这里的任务就是我们前面学过的实现了Runnable或Callable接口的实例对象;
为什么使用线程池:
使用线程池最大的原因就是可以根据系统的需求和硬件环境灵活的控制线程的数量,且可以对所有线程进行统一的管理和控制,从而提高系统的运行效率,降低系统运行运行压力;当然了,使用线程池的原因不仅仅只有这些,我们可以从线程池自身的优点上来进一步了解线程池的好处;
使用线程池有哪些优势
1.线程和任务分离,提升线程重用性;
2.控制线程并发数量,降低服务器压力,统一管理所有线程;
3.提升系统响应速度,假如创建线程用的时间为T1,执行任务用的时间为T2,销毁线程用的时间为T3,那么使用线程池就免去了T1和T3的时间;
应用场景介绍:
1.网购商品秒杀
2.云盘文件上传和下载
3.12306网上购票系统等
只要有并发的地方、任务数量大或小、每个任务执行时间长或短的都可以使用线程池;
只不过在使用线程池的时候,注意一下设置合理的线程池大小即可;
ThreadPoolExecutor部分源码
//构造方法:
public ThreadPoolExecutor(int corePoolSize, //核心线程数量
int maximumPoolSize,// 最大线程数
long keepAliveTime, // 最大空闲时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 任务队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 饱和处理机制
)
{ ... }
ThreadPoolExecutor参数详解
我们可以通过下面的场景理解ThreadPoolExecutor中的各个参数;
a客户(任务)去银行(线程池)办理业务,但银行刚开始营业,窗口服务员还未就位(相当于线程池中初始线程数量为0),
于是经理(线程池管理者)就安排1号工作人员(创建1号线程执行任务)接待a客户(创建线程);
在a客户业务还没办完时,b客户(任务)又来了,于是经理(线程池管理者)就安排2号工作人员(创建2号线程执行任务)接待b客户(又创建了一个新的线程);假设该银行总共就2个窗口(核心线程数量是2);
紧接着在a,b客户都没有结束的情况下c客户来了,于是经理(线程池管理者)就安排c客户先坐到银行大厅的座位上(空位相当于是任务队列)等候,
并告知他: 如果1、2号工作人员空出,c就可以前去办理业务;
此时d客户又到了银行,(工作人员都在忙,大厅座位也满了)于是经理赶紧安排临时工(新创建的线程)在大堂站着,手持pad设备给d客户办理业务;
假如前面的业务都没有结束的时候e客户又来了,此时正式工作人员都上了,临时工也上了,座位也满了(临时工加正式员工的总数量就是最大线程数),
于是经理只能按《超出银行最大接待能力处理办法》(饱和处理机制)拒接接待e客户;
最后,进来办业务的人少了,大厅的临时工空闲时间也超过了1个小时(最大空闲时间),经理就会让这部分空闲的员工人下班.(销毁线程)
但是为了保证银行银行正常工作(有一个allowCoreThreadTimeout变量控制是否允许销毁核心线程,默认false),即使正式工闲着,也不得提前下班,所以1、2号工作人员继续待着(池内保持核心线程数量);
4个参数的设计:
1.核心线程数(corePoolSize)
核心线程数的设计需要依据任务的处理时间和每秒产生的任务数量来确定,例如:执行一个任务需要0.1秒,系统百分之80的时间每秒都会产生100个任务,那么要想在1秒内处理完这100个任务,就需要10个线程,此时我们就可以设计核心线程数为10;当然实际情况不可能这么平均,所以我们一般按照8020原则设计即可,既按照百分之80的情况设计核心线程数,剩下的百分之20可以利用最大线程数处理;
2.任务队列长度(workQueue)
任务队列长度一般设计为:核心线程数/单个任务执行时间*2即可;例如上面的场景中,核心线程数设计为10,单个任务执行时间为0.1秒,则队列长度可以设计为200;
3.最大线程数(maximumPoolSize)
最大线程数的设计除了需要参照核心线程数的条件外,还需要参照系统每秒产生的最大任务数决定:例如:上述环境中,如果系统每秒最大产生的任务是1000个,那么,最大线程数=(最大任务数-任务队列长度)*单个任务执行时间;既: 最大线程数=(1000-200)*0.1=80个;
4.最大空闲时间(keepAliveTime)
这个参数的设计完全参考系统运行环境和硬件压力设定,没有固定的参考值,用户可以根据经验和系统产生任务的时间间隔合理设置一个值即可;
自定义线程池-实现步骤
1.编写任务类(MyTask),实现Runnable接口;
2.编写线程类(MyWorker),用于执行任务,需要持有所有任务;
3.编写线程池类(MyThreadPool),包含提交任务,执行任务的能力;
4.编写测试类(MyTest),创建线程池对象,提交多个任务测试;
MyTask
/*
需求:
自定义线程池练习,这是任务类,需要实现Runnable;
包含任务编号,每一个任务执行时间设计为0.2秒
*/
public class MyTask implements Runnable{
private int id;
//由于run方法是重写接口中的方法,因此id这个属性初始化可以利用构造方法完成
public MyTask(int id) {
this.id = id;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println("线程:"+name+" 即将执行任务:"+id);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:"+name+" 完成了任务:"+id);
}
@Override
public String toString() {
return "MyTask{" +
"id=" + id +
'}';
}
}
MyWorker
import java.util.List;
/*
需求:
编写一个线程类,需要继承Thread类,设计一个属性,用于保存线程的名字;
设计一个集合,用于保存所有的任务;
*/
public class MyWorker extends Thread{
private String name;//保存线程的名字
private List<Runnable> tasks;
//利用构造方法,给成员变量赋值
public MyWorker(String name, List<Runnable> tasks) {
super(name);
this.tasks = tasks;
}
@Override
public void run() {
//判断集合中是否有任务,只要有,就一直执行任务
while (tasks.size()>0){
Runnable r = tasks.remove(0);
r.run();
}
}
}
MyWorker
import java.util.List;
/*
需求:
编写一个线程类,需要继承Thread类,设计一个属性,用于保存线程的名字;
设计一个集合,用于保存所有的任务;
*/
public class MyWorker extends Thread{
private String name;//保存线程的名字
private List<Runnable> tasks;
//利用构造方法,给成员变量赋值
public MyWorker(String name, List<Runnable> tasks) {
super(name);
this.tasks = tasks;
}
@Override
public void run() {
//判断集合中是否有任务,只要有,就一直执行任务
while (tasks.size()>0){
Runnable r = tasks.remove(0);
r.run();
}
}
}
MyThreadPool
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
/*
这是自定义的线程池类;
成员变量:
1:任务队列 集合 需要控制线程安全问题
2:当前线程数量
3:核心线程数量
4:最大线程数量
5:任务队列的长度
成员方法
1:提交任务;
将任务添加到集合中,需要判断是否超出了任务总长度
2:执行任务;
判断当前线程的数量,决定创建核心线程还是非核心线程
*/
public class MyThreadPool {
// 1:任务队列 集合 需要控制线程安全问题
private List<Runnable> tasks = Collections.synchronizedList(new LinkedList<>());
//2:当前线程数量
private int num;
//3:核心线程数量
private int corePoolSize;
//4:最大线程数量
private int maxSize;
//5:任务队列的长度
private int workSize;
public MyThreadPool(int corePoolSize, int maxSize, int workSize) {
this.corePoolSize = corePoolSize;
this.maxSize = maxSize;
this.workSize = workSize;
}
//1:提交任务;
public void submit(Runnable r){
//判断当前集合中任务的数量,是否超出了最大任务数量
if(tasks.size()>=workSize){
System.out.println("任务:"+r+"被丢弃了...");
}else {
tasks.add(r);
//执行任务
execTask(r);
}
}
//2:执行任务;
private void execTask(Runnable r) {
//判断当前线程池中的线程总数量,是否超出了核心数,
if(num < corePoolSize){
new MyWorker("核心线程:"+num,tasks).start();
num++;
}else if(num < maxSize){
new MyWorker("非核心线程:"+num,tasks).start();
num++;
}else {
System.out.println("任务:"+r+" 被缓存了...");
}
}
}
MyTest
/*
测试类:
1: 创建线程池类对象;
2: 提交多个任务
*/
public class MyTest {
public static void main(String[] args) {
//1:创建线程池类对象;
MyThreadPool pool = new MyThreadPool(2,4,20);
//2: 提交多个任务
for (int i = 0; i <30 ; i++) {
//3:创建任务对象,并提交给线程池
MyTask my = new MyTask(i);
pool.submit(my);
}
}
}
Java内置线程池-ExecutorService介绍:
ExecutorService接口是java内置的线程池接口,通过学习接口中的方法,可以快速的掌握java内置线程池的基本使用
常用方法:
void shutdown() 启动一次顺序关闭,执行以前提交的任务,但不接受新任务。
List shutdownNow() 停止所有正在执行的任务,暂停处理正在等待的任务,并返回等待执行的任务列表。
Future submit(Callable task) 执行带返回值的任务,返回一个Future对象。
Future<?> submit(Runnable task) 执行 Runnable 任务,并返回一个表示该任务的 Future。
Future submit(Runnable task, T result) 执行 Runnable 任务,并返回一个表示该任务的 Future。
Java内置线程池-ExecutorService获取
获取ExecutorService可以利用JDK中的Executors 类中的静态方法,常用获取方式如下:
static ExecutorService newCachedThreadPool() 创建一个默认的线程池对象,里面的线程可重用,且在第一次使用时才创建
static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)
线程池中的所有线程都使用ThreadFactory来创建,这样的线程无需手动启动,自动执行;
static ExecutorService newFixedThreadPool(int nThreads) 创建一个可重用固定线程数的线程池
static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)
创建一个可重用固定线程数的线程池且线程池中的所有线程都使用ThreadFactory来创建。
static ExecutorService newSingleThreadExecutor()
创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。
static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)
创建一个使用单个 worker 线程的 Executor,且线程池中的所有线程都使用ThreadFactory来创建。
newCachedThreadPool
package com.test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
/*
练习Executors获取ExecutorService,然后调用方法,提交任务;
*/
public class MyTest01 {
public static void main(String[] args) {
// test1();
test2();
}
//练习newCachedThreadPool方法
private static void test1() {
//1:使用工厂类获取线程池对象
ExecutorService es = Executors.newCachedThreadPool();
//2:提交任务;
for (int i = 1; i <=10 ; i++) {
es.submit(new MyRunnable(i));
}
}
private static void test2() {
//1:使用工厂类获取线程池对象
ExecutorService es = Executors.newCachedThreadPool(new ThreadFactory() {
int n=1;
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"自定义的线程名称"+n++);
}
});
//2:提交任务;
for (int i = 1; i <=10 ; i++) {
es.submit(new MyRunnable(i));
}
}
}
/*
任务类,包含一个任务编号,在任务中,打印出是哪一个线程正在执行任务
*/
class MyRunnable implements Runnable{
private int id;
public MyRunnable(int id) {
this.id = id;
}
@Override
public void run() {
//获取线程的名称,打印一句话
String name = Thread.currentThread().getName();
System.out.println(name+"执行了任务..."+id);
}
}
newFixedThreadPool
package com.test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
/*
练习Executors获取ExecutorService,然后调用方法,提交任务;
*/
public class MyTest02 {
public static void main(String[] args) {
//test1();
test2();
}
//练习方法newFixedThreadPool
private static void test1() {
//1:使用工厂类获取线程池对象
ExecutorService es = Executors.newFixedThreadPool(3);
//2:提交任务;
for (int i = 1; i <=10 ; i++) {
es.submit(new MyRunnable2(i));
}
}
private static void test2() {
//1:使用工厂类获取线程池对象
ExecutorService es = Executors.newFixedThreadPool(3,new ThreadFactory() {
int n=1;
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"自定义的线程名称"+n++);
}
});
//2:提交任务;
for (int i = 1; i <=10 ; i++) {
es.submit(new MyRunnable2(i));
}
}
}
/*
任务类,包含一个任务编号,在任务中,打印出是哪一个线程正在执行任务
*/
class MyRunnable2 implements Runnable{
private int id;
public MyRunnable2(int id) {
this.id = id;
}
@Override
public void run() {
//获取线程的名称,打印一句话
String name = Thread.currentThread().getName();
System.out.println(name+"执行了任务..."+id);
}
}
newSingleThreadExecutor
package com.test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
/*
练习Executors获取ExecutorService,然后调用方法,提交任务;
*/
public class MyTest03 {
public static void main(String[] args) {
//test1();
test2();
}
//练习方法newFixedThreadPool
private static void test1() {
//1:使用工厂类获取线程池对象
ExecutorService es = Executors.newSingleThreadExecutor();
//2:提交任务;
for (int i = 1; i <=10 ; i++) {
es.submit(new MyRunnable3(i));
}
}
private static void test2() {
//1:使用工厂类获取线程池对象
ExecutorService es = Executors.newSingleThreadExecutor(new ThreadFactory() {
int n=1;
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"自定义的线程名称"+n++);
}
});
//2:提交任务;
for (int i = 1; i <=10 ; i++) {
es.submit(new MyRunnable3(i));
}
}
}
/*
任务类,包含一个任务编号,在任务中,打印出是哪一个线程正在执行任务
*/
class MyRunnable3 implements Runnable{
private int id;
public MyRunnable3(int id) {
this.id = id;
}
@Override
public void run() {
//获取线程的名称,打印一句话
String name = Thread.currentThread().getName();
System.out.println(name+"执行了任务..."+id);
}
}
练习Executors获取ExecutorService,测试关闭线程池的方法;
package com.test;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
/*
练习Executors获取ExecutorService,测试关闭线程池的方法;
*/
public class MyTest04 {
public static void main(String[] args) {
test1();
// test2();
}
//练习方法newFixedThreadPool
private static void test1() {
//1:使用工厂类获取线程池对象
ExecutorService es = Executors.newSingleThreadExecutor();
//2:提交任务;
for (int i = 1; i <=10 ; i++) {
es.submit(new MyRunnable4(i));
}
//3:关闭线程池,仅仅是不再接受新的任务,以前的任务还会继续执行
es.shutdown();
//es.submit(new MyRunnable4(888));//不能再提交新的任务了
}
private static void test2() {
//1:使用工厂类获取线程池对象
ExecutorService es = Executors.newSingleThreadExecutor(new ThreadFactory() {
int n=1;
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"自定义的线程名称"+n++);
}
});
//2:提交任务;
for (int i = 1; i <=10 ; i++) {
es.submit(new MyRunnable4(i));
}
//3:立刻关闭线程池,如果线程池中还有缓存的任务,没有执行,则取消执行,并返回这些任务
List<Runnable> list = es.shutdownNow();
System.out.println(list);
}
}
/*
任务类,包含一个任务编号,在任务中,打印出是哪一个线程正在执行任务
*/
class MyRunnable4 implements Runnable{
private int id;
public MyRunnable4(int id) {
this.id = id;
}
@Override
public void run() {
//获取线程的名称,打印一句话
String name = Thread.currentThread().getName();
System.out.println(name+"执行了任务..."+id);
}
@Override
public String toString() {
return "MyRunnable4{" +
"id=" + id +
'}';
}
}
Java内置线程池-ScheduledExecutorService
ScheduledExecutorService是ExecutorService的子接口,具备了延迟运行或定期执行任务的能力,
常用获取方式如下:
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
创建一个可重用固定线程数的线程池且允许延迟运行或定期执行任务;
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)
创建一个可重用固定线程数的线程池且线程池中的所有线程都使用ThreadFactory来创建,且允许延迟运行或定期执行任务;
static ScheduledExecutorService newSingleThreadScheduledExecutor()
创建一个单线程执行程序,它允许在给定延迟后运行命令或者定期地执行。
static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory)
创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。
ScheduledExecutorService常用方法如下:
<V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit)
延迟时间单位是unit,数量是delay的时间后执行callable。
ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
延迟时间单位是unit,数量是delay的时间后执行command。
ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
延迟时间单位是unit,数量是initialDelay的时间后,每间隔period时间重复执行一次command。
ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟。
newScheduledThreadPool的schedule
package com.test.demo3;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/*
测试ScheduleExecutorService接口中延迟执行任务和重复执行任务的功能
*/
public class ScheduleExecutorServiceDemo01 {
public static void main(String[] args) {
//1:获取一个具备延迟执行任务的线程池对象
ScheduledExecutorService es = Executors.newScheduledThreadPool(3);
//2:创建多个任务对象,提交任务,每个任务延迟2秒执行
for (int i=1;i<=10;i++){
es.schedule(new MyRunnable(i),2, TimeUnit.SECONDS);
}
System.out.println("over");
}
}
class MyRunnable implements Runnable{
private int id;
public MyRunnable(int id) {
this.id = id;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name+"执行了任务:"+id);
}
}
scheduleAtFixedRate方法
package com.test.demo3;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
/*
测试ScheduleExecutorService接口中延迟执行任务和重复执行任务的功能
*/
public class ScheduleExecutorServiceDemo02 {
public static void main(String[] args) {
//1:获取一个具备延迟执行任务的线程池对象
ScheduledExecutorService es = Executors.newScheduledThreadPool(3, new ThreadFactory() {
int n = 1;
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"自定义线程名:"+n++);
}
});
//2:创建多个任务对象,提交任务,每个任务延迟2秒执行
es.scheduleAtFixedRate(new MyRunnable2(1),1,2,TimeUnit.SECONDS);
System.out.println("over");
}
}
class MyRunnable2 implements Runnable{
private int id;
public MyRunnable2(int id) {
this.id = id;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"执行了任务:"+id);
}
}
Java内置线程池-异步计算结果(Future)
我们刚刚在学习java内置线程池使用时,没有考虑线程计算的结果,但开发中,我们有时需要利用线程进行一些计算,然后获取这些计算的结果,而java中的Future接口就是专门用于描述异步计算结果的,我们可以通过Future 对象获取线程计算的结果;
Future 的常用方法如下:
boolean cancel(boolean mayInterruptIfRunning)
试图取消对此任务的执行。
V get()
如有必要,等待计算完成,然后获取其结果。
V get(long timeout, TimeUnit unit)
如有必要,最多等待为使计算完成所给定的时间之后,获取其结果(如果结果可用)。
boolean isCancelled()
如果在任务正常完成前将其取消,则返回 true。
boolean isDone()
如果任务已完成,则返回 true。
package com.test.demo04;
import java.util.concurrent.*;
/*
练习异步计算结果
*/
public class FutureDemo {
public static void main(String[] args) throws Exception {
//1:获取线程池对象
ExecutorService es = Executors.newCachedThreadPool();
//2:创建Callable类型的任务对象
Future<Integer> f = es.submit(new MyCall(1, 1));
//3:判断任务是否已经完成
//test1(f);
boolean b = f.cancel(true);
//System.out.println("取消任务执行的结果:"+b);
//Integer v = f.get(1, TimeUnit.SECONDS);//由于等待时间过短,任务来不及执行完成,会报异常
//System.out.println("任务执行的结果是:"+v);
}
//正常测试流程
private static void test1(Future<Integer> f) throws InterruptedException, ExecutionException {
boolean done = f.isDone();
System.out.println("第一次判断任务是否完成:"+done);
boolean cancelled = f.isCancelled();
System.out.println("第一次判断任务是否取消:"+cancelled);
Integer v = f.get();//一直等待任务的执行,直到完成为止
System.out.println("任务执行的结果是:"+v);
boolean done2 = f.isDone();
System.out.println("第二次判断任务是否完成:"+done2);
boolean cancelled2 = f.isCancelled();
System.out.println("第二次判断任务是否取消:"+cancelled2);
}
}
class MyCall implements Callable<Integer>{
private int a;
private int b;
//通过构造方法传递两个参数
public MyCall(int a, int b) {
this.a = a;
this.b = b;
}
@Override
public Integer call() throws Exception {
String name = Thread.currentThread().getName();
System.out.println(name+"准备开始计算...");
Thread.sleep(2000);
System.out.println(name+"计算完成...");
return a+b;
}
}
场景
秒杀商品
内容
假如某网上商城推出活动,新上架10部新手机免费送客户体验,要求所有参与活动的人员在规定的时间同时参与秒杀挣抢,假如有20人同时参与了该活动,请使用线程池模拟这个场景,保证前10人秒杀成功,后10人秒杀失败;
要求:
1:使用线程池创建线程
2:解决线程安全问题
思路提示:
1:既然商品总数量是10个,那么我们可以在创建线程池的时候初始化线程数是10个及以下,设计线程池最大数量为10个;
2:当某个线程执行完任务之后,可以让其他秒杀的人继续使用该线程参与秒杀;
3:使用synchronized控制线程安全,防止出现错误数据;
代码步骤:
1:编写任务类,主要是送出手机给秒杀成功的客户;
2:编写主程序类,创建20个任务(模拟20个客户);
3:创建线程池对象并接收20个任务,开始执行任务;
package com.test.demo05;
/*
任务类:
包含了商品数量,客户名称,送手机的行为;
*/
public class MyTask implements Runnable {
//设计一个变量,用于表示商品的数量
private static int id = 10;
//表示客户名称的变量
private String userName;
public MyTask(String userName) {
this.userName = userName;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(userName+"正在使用"+name+"参与秒杀任务...");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (MyTask.class){
if(id>0){
System.out.println(userName+"使用"+name+"秒杀:"+id-- +"号商品成功啦!");
}else {
System.out.println(userName+"使用"+name+"秒杀失败啦!");
}
}
}
}
package com.test.demo05;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/*
主程序类,测试任务类
*/
public class MyTest {
public static void main(String[] args) {
//1:创建一个线程池对象
ThreadPoolExecutor pool = new ThreadPoolExecutor(3,5,1, TimeUnit.MINUTES,new LinkedBlockingQueue<>(15));
//2:循环创建任务对象
for (int i = 1; i <=20 ; i++) {
MyTask myTask = new MyTask("客户"+i);
pool.submit(myTask);
}
//3:关闭线程池
pool.shutdown();
}
}
内容:
设计一个程序,使用两个线程模拟在两个地点同时从一个账号中取钱,假如卡中一共有1000元,每个线程取800元,要求演示结果一个线程取款成功,剩余200元,另一个线程取款失败,余额不足;
要求:
1:使用线程池创建线程
2:解决线程安全问题
思路提示:
1:线程池可以利用Executors工厂类的静态方法,创建线程池对象;
2:解决线程安全问题可以使用synchronized方法控制取钱的操作
3:在取款前,先判断余额是否足够,且保证余额判断和取钱行为的原子性;
package com.test.demo06;
public class MyTask implements Runnable {
//用户姓名
private String userName;
//取款金额
private double money;
//总金额
private static double total = 1000;
public MyTask(String userName, double money) {
this.userName = userName;
this.money = money;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(userName+"正在准备使用"+name+"取款:"+money+"元");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (MyTask.class){
if(total-money>0){
System.out.println(userName+"使用"+name+"取款:"+money+"元成功,余额:"+(total-money));
total-=money;
}else {
System.out.println(userName+"使用"+name+"取款:"+money+"元失败,余额:"+total);
}
}
}
}
package com.test.demo06;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
public class MyTest {
public static void main(String[] args) {
//1:创建线程池对象
ExecutorService pool = Executors.newFixedThreadPool(2, new ThreadFactory() {
int id = 1;
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "ATM" + id++);
}
});
//2:创建两个任务并提交
for (int i = 1; i <=2 ; i++) {
MyTask myTask = new MyTask("客户" + i, 800);
pool.submit(myTask);
}
//3:关闭线程池
pool.shutdown();
}
}
线程池总结
1.利用Executors工厂类的静态方法,创建线程池对象;
2.编写Runnable或Callable实现类的实例对象;
3.利用ExecutorService的submit方法或ScheduledExecutorService的schedule方 法提交并执行线程任务
4.如果有执行结果,则处理异步执行结果(Future)
5.调用shutdown()方法,关闭线程池
线程池中阻塞队列的作用,为什么不采用其他的队列
1.一般的对列只能保证作为一个有限长度的缓冲区,如果超出了缓冲长度,就无法保留当前当前的任务了,阻塞队列可以通过阻塞保留当前想要继续入队的任务
2.阻塞队列中可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程获取wait状态,释放CPU资源。
3.阻塞队列自带阻塞和唤醒的功能,不需要额外处理,无任务执行时,线程池利用阻塞队列take方法挂起,从而保持核心线程的存活,不至于一直占用CPU资源
**********************************************************************************************
为什么是添加队列而不是先创建最大线程
在创建新线程的时候,是要获取全局锁的,这个时候其他的就得阻塞,影响了整体效率。
**********************************************************************************************
线程工厂的作用
创建线程的工厂,可以设定线程名、线程编号等。
**********************************************************************************************
jdk中4种拒绝策略
AbortPolicy(默认):直接抛出RejectedExecutionException异常阻止系统正常进行
CallerRunsPolicy:“调用者运行”一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量
DiscardPolicy:改策略默默丢弃无法处理的任务,不予任何受理也不抛出异常。如果允许任务丢弃,这是最好的一种策略。
DiscardOldestPolicy:抛弃队列中等待最久的任务,然后将当前任务加入队列,然后再次提交任务
参数 | 作用 |
---|---|
corePoolSize | 线程池中常驻核心线程数 |
maximumPoolSize | 线程池能够容纳同时执行的最大线程数,此值必须大于1 |
keepAliveTime | 多余空闲线程的存活时间。当前线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime时,多余空闲线程会被销毁直到剩下corePoolSize为止。 |
unit | keepAliveTime的单位 |
workQueue | 里面放了被提交但是尚未执行的任务 |
threadFactory | 表示线程池中工作线程的线程工厂,用于创建线程 |
handler | 拒绝策略,当队列满了并且工作线程大于等于线程池的最大线程数(maximumPoolSize)时,对任务的拒绝方式。 |