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

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

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

1、使用定时器建立线程


定时器其实也是至关于开辟一个线程来进行执行定时任务,就是咱们所熟悉的Timer类缓存

好比说咱们想在某一个时间点上执行一件事,好比凌晨要跑数据,或者实现20分钟以后提示咱们一些事等等,均可以经过定时器来执行多线程

定时器关于定时任务,除了JDK所给咱们提供的Timer类这个API之外,还有不少的第三方的关于定时的任务的框架。并发

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

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

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秒执行一次,那么咱们就可使用这个构造方法spa

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执行一次完成进行输出

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

并且当这个任务没有执行完毕或者咱们想每次都提交不一样的任务的话,那么咱们无法对它进行持久化等操做

定时器咱们就说到这里

2、使用线程池建立线程


在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,比较智能的一个线程池

参考资料


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

相关文章
相关标签/搜索