线程应用场景

(一)重试线程池

线程池实现重试机制

  • 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)线程池
线程池的思路和生产者消费者模型是很接近的。

  1. 准备一个任务容器
  2. 一次性启动10个 消费者线程
  3. 刚开始任务容器是空的,所以线程都wait在上面。
  4. 直到一个外部线程往这个任务容器中扔了一个“任务”,就会有一个消费者线程被唤醒notify
  5. 这个消费者线程取出“任务”,并且执行这个任务,执行完毕后,继续等待下一次任务的到来。
  6. 如果短时间内,有较多的任务加入,那么就会有多个线程被唤醒,去执行这些任务。
    在整个过程中,都不需要创建新的线程,而是循环使用这些已经存在的线程
//定义线程池
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--;
        }

    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值