我所知道并发编程之使用定时器、线程池方式演示多种线程创建(二)

上一篇文章介绍了Runable、匿名类、带返回值的线程创建方式

接下来我们这篇要介绍使用定时器、线程池、Lambda表达式等方式来创建

一、使用定时器创建线程


定时器其实也是相当于开辟一个线程来进行执行定时任务,就是我们所熟悉的Timer类

比如说我们想在某一个时间点上执行一件事,比如凌晨要跑数据,或者实现20分钟之后提示我们一些事等等,都可以通过定时器来执行

定时器关于定时任务,除了JDK所给我们提供的Timer类这个API以外,还有很多的第三方的关于定时的任务的框架。

比如Spring就对定时任务进行非常好的支持,还有一个非常强大的关于计划任务的框架叫quartz,quartz也是说的企业中我们做定时任务的专门的一个系统

下面我们看一下定时器的实现,看看怎么实现一个定时的任务

class Demo4{

    public static void main(String[] args) {

        Timer task = new Timer();

    }
}

我们可以通过schedule()这个方法来提交一个定时任务,定时任务就是TimerTask task,后面那个参数就是你可以通过指定延迟多长时间执行等等

class Demo4{


    public static void main(String[] args) {

        Timer timer = new Timer();
        
        /* time为Date类型,在指定时间执行一次*/
        timer.schedule(TimerTask task, Date time)

        /* firstTime为Date类型,period为long,
         在firstTime时刻第一次执行,之后每隔period毫秒执行一次*/
        timer.schedule(TimerTask task, Date firstTime, long period)

        /* delay为long类型,从当前开始delay毫秒后执行一次*/
        timer.schedule(TimerTask task, long delay)

        /* delay为long类型,period为long,
         从当前开始delay毫秒后执行一次,之后每隔period毫秒执行一次*/
        timer.schedule(TimerTask task, long delay, long period)
    }
}

我们想让它立刻执行然后每隔1秒执行一次,那么我们就可以使用这个构造方法

image.png

第一个参数线程任务TimerTask是一个抽象类,一起看看他的源码是怎么样的

public abstract class TimerTask implements Runnable {
    
    //省略其他关键性代码......
}

我们发现它也实现了Runnable接口,所以我们可以在run()方法里面就可以实现定时任务

class Demo4{

    public static void main(String[] args) {

        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("timeTask start.....");
            }
        },0,1000);
    }
}
//运行结果如下:
timeTask start.....
timeTask start.....
timeTask start.....
timeTask start.....
timeTask start.....
timeTask start.....

我们设置延迟0,设置每隔1s执行一次完成进行输出

这种写法我们发现它有一个非常大的问题就是不可控,控制起来非常麻烦。

而且当这个任务没有执行完毕或者我们想每次都提交不同的任务的话,那么我们没法对它进行持久化等操作

定时器我们就说到这里

二、使用线程池创建线程


在JDK中给我们提供线程池,方便我们使用线程池能够创建一个多线程

什么是线程池?就是它首先是一个池子,这个池子里面装的是线程也就是说一个池子里面装了很多的线程

当我们在去使用线程的时候,而不需要再去创建线程了而是直接从池子里面去获取线程

当我们用完线程的时候也不去释放它,而是还给所谓的线程池,这就跟数据库连接池的原理是一样的它主要是降低线程的创建和销毁的资源的浪费,就相当于拿空间换时间,就是一个典型的缓存

首先我们先介绍一下线程池最上层的接口:Executor

image.png

我们发现它下面有很多的实现,一般用的最多的其实就是ExecutorService

image.png

刚刚提到Executor就代表一个线程池,但我们发现Executor是一个接口,如何创建它呢?我们没法new,不过可以通过Executors类下的方法创建

image.png

我们可以通过这些方法来创建一个线程池,这些都是创建线程池的方法

image.png

同时我们发现这些方法的返回值要么是ExecutorService,要么是ScheduledExecutorService。

image.png

而我们刚刚也知道一般使用最多的是ExecutorService子接口实现

所以我们使用父接口去指向子类接口并没有什么问题

//创建一个可缓存线程池如果线程池长度超过处理需要
//可灵活回收空闲线程,若无可回收,则新建线程。
newCachedThreadPool

//创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newFixedThreadPool 

//创建一个定长线程池,支持定时及周期性任务执行。
newScheduledThreadPool 

//创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务
//保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
newSingleThreadExecutor 

我们使用FixedThreadPool来创建一个固定容量的线程池,关于其他的线程池后面来详细的学习

class Demo5{

    public static void main(String[] args) {

        //创建十个线程
        Executor executor = Executors.newFixedThreadPool(10);
        
    }
}

当我们用到线程的时候,就向它(本例中threadPool)申请,用完了之后在返回给它

class Demo5{

    public static void main(String[] args) {

        //创建十个线程
        Executor executor = Executors.newFixedThreadPool(10);
        executor.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"start....");
            }
        });
    }
}
//运行结果如下:
pool-1-thread-1start....

这就是我们的线程任务只执行了一次,每一次execute()提交的是一个线程任务。

我们说线程池中有多个线程,那么也就是说可以同时提交多个线程任务

class Demo5{

    public static void main(String[] args) {

        //创建十个线程
        Executor executor = Executors.newFixedThreadPool(10);

        for (int i = 0; i<10; i++){
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"start....");
                }
            });
        }
    }
}
//运行结果如下:
pool-1-thread-1start....
pool-1-thread-7start....
pool-1-thread-2start....
pool-1-thread-3start....
pool-1-thread-6start....
pool-1-thread-4start....
pool-1-thread-9start....
pool-1-thread-10start....
pool-1-thread-5start....
pool-1-thread-8start....

当你运行起来就会发现当执行完毕之后这个程序并没有停止,因为这是一个线程池,而你并没有告诉它让它停掉

class Demo5{

    public static void main(String[] args) {

        //创建十个线程
        ExecutorService executor = Executors.newFixedThreadPool(10);

        for (int i = 0; i<10; i++){
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"start....");
                }
            });
        }
        //停止线程池
        executor.shutdown();
    }
}

假如以目前的代码提交一百次线程任务会输出什么呢?

class Demo5{

    public static void main(String[] args) {

        //创建十个线程
        ExecutorService executor = Executors.newFixedThreadPool(10);

        for (int i = 0; i<100; i++){
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"start....");
                }
            });
        }
        //停止线程池
        executor.shutdown();
    }
}
//运行结果如下:
pool-1-thread-1start....
pool-1-thread-2start....
pool-1-thread-1start....
pool-1-thread-1start....
pool-1-thread-1start....
pool-1-thread-1start....
pool-1-thread-1start....
pool-1-thread-7start....

这时我们发现当我们提交一百个定时任务,发现并不是由100个线程所执行

也就是说现在相当于什么呢?第一个线程任务提交进来了,被第一个线程执行了可能,然后接着第二个线程任务被提交了,那么可能第二个线程就接着来干活。

哪个线程抢到执行权就那个线程去干活,这时有可能上面的还没有干完,而第二个线程干活比较利落,它干完了之后返回给线程池(本例中的线程池是threadPool)

接着我们又要提交一个线程任务,于是从线程池中拿到第二个线程接着干活于是就是这么一个情况,也就是说你即使提交了100个线程任务,它依然是由这10个线程来进行干活

接下来我们看看CachedThreadPool是一个什么样的,也是先来提交10个线程任务

image.png
image.png

我们发现它差不多也是给我们创建了10个线程,那么,我们再来提交100个线程任务

image.png

image.png

我们发现它的最大值达到了50了,其实这个是没有规律的

也就是说它这个线程池的大小,它是怎么来决定的呢?

其实它就是由你线程任务,你不断的提交线程任务,那么它就不断的创建

它认为不够用了它就去创建,它认为够用了它就回收

这是关于CachedThreadPool,比较智能的一个线程池

参考资料


龙果学院:并发编程原理与实战(叶子猿老师)

你可能感兴趣的:(我所知道并发编程之使用定时器、线程池方式演示多种线程创建(二))