(一)重试线程池
- schedule线程池用于进行定时检查重试任务列表(有界)
- ThreadPollTaskEecutor是用于执行的
(1)用于执行对应的重试线程操作RetryUtils
- 构造的时候创建定时任务线程池,定时检查重试队列
- doRetry 把重试任务放入队列中
(2)测试类
- 堆某个任务执行重试,重写ru方法与失败之后进行回滚
package com.example.demo.ThreadPool;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ThreadPoolExecutor;
public class RetryUtils {
//1、提供重试线程池
//2、遍历任务队列,有任务就丢尽线程池开始重试
//3、最大重试次数限制
//重试次数
private final static int DEFAULT_RETRY_TIME=6;
//重试时间间隔
private final static int[] DEFAULT_DELAY_SECONDS={3,30,180,600,1800,3600};
//工作请求队列
private static Queue<RetryRunnable> TASK_QUEUE=new ConcurrentLinkedQueue<>();
//线程池 scheduler?
public static ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
public static ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
//1 静态代码块配置线程池
static{
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
//有界队列
executor.setQueueCapacity(1000);
executor.setThreadNamePrefix("async_");
// CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
scheduler.setThreadNamePrefix("scheduler_");
scheduler.setPoolSize(5);
scheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
scheduler.initialize();
}
//构造方法:创建了这个对象实例之后,就开启定时检查任务队列是否有需要重试的任务,有的话就去执行
// (如果超过了线程池数量,就会放到ThreadPollExecutor的队列中,注意与我们自己定义的这个队列进行区分)
//正常线程池只有核心工作线程全部空闲了,addWorker(null, false)从队列中获取任务执行。
public RetryUtils(){
scheduler.scheduleAtFixedRate(()-> {
for(RetryRunnable task:TASK_QUEUE){
long nextRetryMills=task.nextRetryMills;
//到时间了
if(nextRetryMills!=-1&&nextRetryMills<=System.currentTimeMillis()){
task.nextRetryMills=-1;
executor.execute(task);
}
}
},1000);
}
public void doRetry(Task task) {
doRetry(DEFAULT_RETRY_TIME, DEFAULT_DELAY_SECONDS, task);
}
public void doRetry(int maxRetryTime, Task task) {
doRetry(maxRetryTime, DEFAULT_DELAY_SECONDS, task);
}
public void doRetry(int[] retryDelaySeconds, Task task) {
doRetry(retryDelaySeconds.length, retryDelaySeconds, task);
}
public void doRetry(final int maxRetryTime, final int[] retryDelaySeconds, final Task task) {
Runnable runnable = new RetryRunnable(maxRetryTime, retryDelaySeconds, task);
executor.execute(runnable);
}
//自定义内部类
private static class RetryRunnable implements Runnable{
//包装的任务
private final Task task;
//最大重试次数
private final int maxRetryTimes;
private final int[] retryDelaySeconds;
//当前重试词属
private int retryTimes;
//下次重试时间
private volatile long nextRetryMills;
//构造函数
public RetryRunnable(final int maxRetryTimes,final int[] retryDelaySeconds,final Task task){
this.task=task;
if (maxRetryTimes <= 0) {
this.maxRetryTimes = DEFAULT_RETRY_TIME;
} else {
this.maxRetryTimes = maxRetryTimes;
}
if (retryDelaySeconds == null || retryDelaySeconds.length == 0) {
this.retryDelaySeconds = DEFAULT_DELAY_SECONDS;
} else {
this.retryDelaySeconds = retryDelaySeconds;
}
}
//执行业务方法
@Override
public void run() {
try {
task.run();
} catch (Throwable e) {
//重试失败了
//判断是否大于重试次数,如果已经大于重试次数则最后抛出一个重试失败,并且移除出重试工作队列中
int sleepSeconds=retryTimes>=retryDelaySeconds.length?retryDelaySeconds[retryDelaySeconds.length-1]:retryDelaySeconds[retryTimes];
if(retryTimes<maxRetryTimes){
//1 如果第一次尝试失败,加入到队列中,否则就是失败了
//2 给出下次尝试的时间
if(retryTimes==0){
TASK_QUEUE.add(this);
}
nextRetryMills=System.currentTimeMillis()+sleepSeconds*1000;
}else{
//重试次数已经超过阈值,直接返回重试错误,并移除出列表
TASK_QUEUE.remove(this);
task.retryFailed(e);
}
retryTimes++;
}
}
}
}
package com.example.demo.ThreadPool;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
public class IRetryTest {
public static void main(String[] args) {
//新建之后,会有一个定时检查
RetryUtils retryUtils=new RetryUtils();
IRetryTest iRetryTest=new IRetryTest();
//重试机制的形:发生了错误的任务丢到重试线程池中
iRetryTest.executeBusiness(retryUtils, new IRetryTest().new UserInfo("Jack"));
}
/**
* 重试的业务方法
*/
private void executeBusiness(RetryUtils retryUtils, UserInfo user) {
retryUtils.doRetry(new int[]{6,12},new Task(){
//1、重新需要执行的业务逻辑在run里面(没有的话默认6次,否则使用传入的)
@Override
public void run() throws Exception {
user.setName("Henry");
// log.info("执行业务,给员工修改名称为:" + user.getName());
}
//2、失败之后需要进行回滚
@Override
public void retryFailed(Throwable e) {
user.setName("Jack");
}
});
}
@Data
@NoArgsConstructor
@AllArgsConstructor
private class UserInfo{
private String name;
}
}
package com.example.demo.ThreadPool;
/**
* 业务类接口:用重试线程池执行失败任务
*/
public interface Task {
void run() throws Exception;
default void retryFailed(Throwable e) {
}
}
(二)线程基础
(1)创建线程
创建多线程有3种方式,分别是继承线程类,实现Runnable接口,匿名类
建议采用接口的方式:(可实现多个接口)
两个new Threa可以理解为同时执行。
//继承的方式
hread t1=new KillThread(gareen,teemo);
t1.start();
Thread t2=new KillThread(bh,leesin);
t2.start();
//接口的方式
Battle b1=new Battle(gareen,teemo);
new Thread(b1).start();
//匿名类的方式
Thread t=new Thread(){
@Override
public void run() {
while(!teemo.isDead()){
gareen.attackHero(teemo);
}
}
};
t.start();
(2)线程方法
sleep当前线程暂停Thread.sleep(1000); 表示当前线程暂停1000毫秒 ,其他线程不受影响Thread.sleep(1000); 会抛出InterruptedException 中断异常,因为当前线程sleep的时候,有可能被停止,这时就会抛出 InterruptedExceptionjoin加入到当前线程中线程会等待该线程结束完毕, 才会往下运行。可以这么理解,如果在main方法中创建线程t1, t1.join 的意思就是加入主线程,(同一个线程内还是有先后顺序的),所以一定是先执行t1再执行后面的(同步了,非异步)setPriority线程优先级当线程处于竞争关系的时候,优先级高的线程会有更大的几率获得CPU资源yield临时暂停当前线程,临时暂停,使得其他线程可以有更多的机会占用CPU资源setDaemon守护线程
t1.start();
//代码执行到这里,一直是main线程在运行
try
{ //t1线程加入到main线程中来,只有t1线程运行结束,才会继续往下走
t1.join();
}
catch (InterruptedException e) {
e.printStackTrace();
}
xxxxx
//会观察到盖伦把提莫杀掉后,才运行t2线程
t2.start();
Thread t2=
new
Thread(){
public
void
run(){
while
(!leesin.isDead()){
//临时暂停,使得t1可以占用CPU资源
Thread.yield();
bh.attackHero(leesin);
}
}
};
英雄有可以放一个技能叫做: 波动拳-a du gen。
每隔一秒钟,可以发一次,但是只能连续发3次。
发完3次之后,需要充能5秒钟,充满,再继续发。
//sleep的使用,把它理解为暂停就可以了
public void adugen(){
//不暂停就一直输出
while (true){
for(int i=0;i<3;i++){
System.out.println("波动拳第"+i+"发");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("开始为时5秒的充能");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
设置守护线程
1、在LogThread this.setDemon(true)
2、可以看成同时执行
new PasswordThread(password,passwords).start();
new LogThread(passwords).start();
(3)同步(并发编程)
假设盖伦有10000滴血,并且在基地里,同时又被对方多个英雄攻击
就是有多个线程在减少盖伦的hp
同时又有多个线程在恢复盖伦的hp
假设线程的数量是一样的,并且每次改变的值都是1,那么所有线程结束后,盖伦应该还是10000滴血
为了避免增加线程和减少进程进行同时操作导致错误,加锁
增加线程和减少线程是互斥的,使用synchronuzed
(4)线程交互
wait,notify,notifyall
this.wait()表示 让占有this的线程等待,并临时释放占有
this.notify() 表示通知那些等待在this的线程,可以苏醒过来了。
生产者消费者问题是一个非常典型性的线程交互的问题。
(5)线程池
线程池的思路和生产者消费者模型是很接近的。
- 准备一个任务容器
- 一次性启动10个 消费者线程
- 刚开始任务容器是空的,所以线程都wait在上面。
- 直到一个外部线程往这个任务容器中扔了一个“任务”,就会有一个消费者线程被唤醒notify
- 这个消费者线程取出“任务”,并且执行这个任务,执行完毕后,继续等待下一次任务的到来。
- 如果短时间内,有较多的任务加入,那么就会有多个线程被唤醒,去执行这些任务。
在整个过程中,都不需要创建新的线程,而是循环使用这些已经存在的线程
//定义线程池
ThreadPoolExecutor threadPool= new ThreadPoolExecutor(10, 15, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
//定义任务,使用线程池的线程执行
for (int i=0;i<15;i++){
final int taskIndex=i;
Runnable task=new Runnable() {
@Override
public void run() {
//执行任务
System.out.println("任务"+taskIndex);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
threadPool.execute(task);
}
threadPool.shutdown();
多线程题目
(1)循环顺序打印
法一:线程打印+lock方法
Lock lock= new ReentrantLock();
Thread t1=new Thread(){
@Override
public void run() {
while(state<20){
lock.lock();
if(state%2==0){
System.out.println("Foo");
state++;
}
lock.unlock();
}
}
};
Thread t2=new Thread(){
@Override
public void run() {
while(state<20){
lock.lock();
if(state%2==1){
System.out.println("Bar");
state++;
}
lock.unlock();
}
}
};
t1.start();
t2.start();
法二:信号量法
public static void semophoreTest(){
int n=3;
while(n>0){
try {
foo.acquire();
System.out.println("Foo");
bar.release();
bar.acquire();
System.out.println("Bar");
foo.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
n--;
}
}
}