Java Web 04_day

多线程

1.关于多线程的案例--定时器

        像是一个闹钟,进行定时,在一定时间之后,被唤醒并执行某个之前设定好的任务

        join(指定超时时间)和sleep(休眠指定时间)都是使用定时器实现的(系统的定时器)

        (1)标准库的定时器 

        java.util.Timer

        核心方法就一个,schedule(安排),参数有两个:任务是什么,多长时间后执行

public class demo7 {
    public static void main(String[] args) {
        Timer timer=new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello,timer");
            }
        },3000);
        System.out.println("main");
    }
}

        schedule有两个参数,第一个参数是一个任务,我们可以new一个TimerTask(),TimerTask这个类实现了Runnable接口,他表示一个任务,重写的run方法就是任务。第二个参数是暂停的时间。

        (2)手动实现定时器

        Timer内部需要什么?

        1.描述任务,创建一个专门的类来表示一个定时器中的任务(TimerTask)

        2.组织任务,把任务放到一起

        3.执行时间到了的任务

        先构造一个任务类,用来描述任务,需要两个属性:任务(Runnable)时间(long),提供构造方法,并在构造方法中将时间做成系统时间(System.currentTimeMills+delay)

class MyTimerTask implements Comparable<MyTimerTask>{
    //  任务
    private Runnable runnable;
    //任务执行的时间
    private long time;

    public MyTimerTask(Runnable runnable, long delay) {
        this.runnable = runnable;
        this.time = System.currentTimeMillis()+delay;
    }
    public void run(){
        runnable.run();
    }

    public long getTime() {
        return time;
    }

    @Override
    public int compareTo(MyTimerTask o) {
        //时间小的在前面,是this减o,时间大的在前,是o减this
        return (int) (this.time-o.time);
    }
}

        注意:这个类需要实现Comparable接口,因为我们的任务是用PriorityBlockingQueue存放的,这个队列是需要对任务进行排序的,我们需要表明我们的任务排序规则(时间),所以需要实现Comparable接口,重写这个comparaTo方法(小的在前就this-o,大的在前就o-this)。

        然后构造一个组织任务的类,因为我们要按顺序进行执行,所以此处选择的数据结构是PriorityBlockingQueue,我们需要设置一个线程,用来检查队列中是否有任务需要被执行,所以我们在构造方法中声明了一个线程,对队列不断进行检查。

//2.组织任务任务
class MyTimer{
    //安排任务的时候,任务的顺序是无序的,但是我们需要按顺序执行
    //选择堆进行保存任务PriorityQueue
    //此处的队列考虑到线程安全问题->
    // 由于我们可能在多个线程中执行注册任务,还有线程来执行任务,此处的队列就需要注意线程安全问题
    private PriorityBlockingQueue<MyTimerTask> Queue=new PriorityBlockingQueue<>();
    public void  schedule(Runnable runnable,long delay){
        MyTimerTask myTimerTask=new MyTimerTask(runnable,delay);
        Queue.put(myTimerTask);
        //再插入任务之后,重新进行检查
        synchronized (locker){
            locker.notify();
        }
    }
    //3.执行时间到了的任务
    //需要有一个线程来不停的检查当前优先队列的队首元素,检查时间是否到
    private Object locker =new Object();
    public MyTimer(){
        Thread t=new Thread(()->{
            while(true){
                try {
                    //忙等->既没有实质性的产出,又没闲着->浪费CPU
                    //解决办法:利用wait指定等待时间(不需要notify)
                    MyTimerTask task=Queue.take();
                    long curTime=System.currentTimeMillis();
                    if(curTime< task.getTime()){
                        //如果任务没到,就把任务加回去
                        Queue.put(task);
                        //指定一个等待时间
                        synchronized (locker){
                            locker.wait(task.getTime()-curTime);
                        }
                    }else {
                    task.run();
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();
    }
}

        注意:此处这个线程一直在检查队列的任务,但是没有必要,这是一种忙等现象,既没有产出又没有闲着。因此会设置一个锁对象locker,在线程检查任务事件后,我们将locker.wait(执行的时间-现在的时间)让线程不用一直检查,等待一会。但是这样有可能会有更靠前更新的任务进队列,所以我们需要在每一次任务入队列成功后,notify这个锁对象,让线程进行检查。

2.关于多线程的案例--线程池

        线程,虽然比进程轻了,但是如果创建销毁的频率进一步增加,仍然会发现开销还是挺大的,解决办法:协程or线程池

        把线程提前创建好了,放到池子里,后面需要线程直接从池子中取,不必向系统申请了

                                                                        系统整体架构图

        我们一般认为用户态的操作比内核态的操作要效率高。(并不是说内核态操作慢,而是因为用户态的操作是可控的)

        (1)标准库的线程池--ThreadPoolExecute

        ThreadPoolExecute的构造方法:

        int corePoolSize-> 核心线程数

        int maxmumPoolSize->最大线程数(核心线程和非核心线程)

        long keepAliveTime->允许非核心线程“摸鱼”的时间

        TimeUnit unit->时间的单位(s,ms,us......)

        BlockingQueue<Runnable> workQueue->任务队列,线程池会提供一个submit方法,程序员会把任务注册到线程池中,加到这个任务队列中

        ThreadFactory threadFactory->线程工厂,线程创建

        RejectedExecutionHandler handler->拒绝策略,当任务满了,怎么做?

        虽然线程池的参数很多,但是最重要的参数还是第一组参数,线程数量->如何确定数量?

        正确做法:要通过性能测试的方法,找到合适的值,当线程多了,整体速度会变快,但是CPU占用率会增高,当线程数少了,整体速度会变慢,但是CPU占用率变低,所以我们要通过性能测试,找到一个平衡点。

        (2)标准库中简化版本的线程池--Executors

        本质上是针对ThreadPoolExecute进行的简化版本,提供了一些默认值

public class demo9 {
    public static void main(String[] args) {
        //创建一个固定数目的线程池
        ExecutorService pool=Executors.newFixedThreadPool(10);
        //创建一个自动扩容的线程池
        Executors.newCachedThreadPool();
        //创建一个一个线程的线程池
        Executors.newSingleThreadExecutor();
        //创建一个有定时器功能的线程池,类似于Timer
        Executors.newScheduledThreadPool();
        for (int i = 0; i < 100; i++) {
            pool.submit(new Runnable() {
                @Override
                public void run() {                       
                    System.out.println(Thread.currentThread().getName());
                }
            });
        }
    }
}

        打印结果:十个线程对这些任务分别进行执行。

        (3)手动实现线程池

        线程池需要什么:

        1.描述任务->直接用Runnable就好

        2.需要组织任务->BlockingQueue

        3.描述工作线程

        4.组织工作线程

        5.需要实现往线程池中添加任务

class MyThreadPool{
    //1.描述任务
    private  Runnable runnable;
    //2.组织任务
    private BlockingDeque<Runnable> Queue=new LinkedBlockingDeque<>();
    //3.描述线程
    static class Worker extends Thread{
        private BlockingDeque<Runnable> Queue=new LinkedBlockingDeque<>();
        public  Worker(BlockingDeque<Runnable> Queue){
            this.Queue=Queue;
        }
        @Override
        public void run() {
            while(true){
                try {
                    //如果队列为空就阻塞
                    Runnable runnable=Queue.take();
                    runnable.run();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
    //4.使用数据结构组织线程
    private List<Thread> Workers=new ArrayList<>();
    public MyThreadPool(int n){
        for (int i = 0; i < n; i++) {
            Worker worker=new Worker(Queue);
            worker.start();
            Workers.add(worker);
        }
    }
    //5.创建一个方法,能够允许程序员放任务到线程池中
    public void submit(Runnable runnable){
        try {
            Queue.put(runnable);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值