乐趣区

关于java:我所知道并发编程之使用定时器匿名内部类带返回值的方式演示多种线程创建二

上一篇文章介绍了 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 秒执行一次,那么咱们就能够应用这个构造方法

第一个参数线程工作 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

咱们发现它上面有很多的实现,个别用的最多的其实就是 ExecutorService

刚刚提到 Executor 就代表一个线程池,但咱们发现 Executor 是一个接口,如何创立它呢?咱们没法 new,不过能够通过 Executors 类下的办法创立

咱们能够通过这些办法来创立一个线程池,这些都是创立线程池的办法

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

而咱们刚刚也晓得个别应用最多的是 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 个线程工作


咱们发现它差不多也是给咱们创立了 10 个线程,那么,咱们再来提交 100 个线程工作

咱们发现它的最大值达到了 50 了,其实这个是没有法则的

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

其实它就是由你线程工作,你一直的提交线程工作,那么它就一直的创立

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

这是对于 CachedThreadPool,比拟智能的一个线程池

参考资料


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

退出移动版