线程池(2)--------JavaWeb

一:线程池参数补充:

一:TimeUnit:空闲线程存活时间的基本单位

1)这个参数是配合参数Long keepAliveTime来进行使用的,表示当前线程存活时间,参数keepAliveTime是Long类型,TimeUnit有以下7个值:

2)TimeUnit:days:天

3)TimeUnit:Hours:小时

4)TimeUnit:Minutes:分钟

5)TimeUnit:seconds:秒

6)TimeUnit:milliseconds:毫秒

7)TimeUnit:microseconds:微秒

8)TimeUnit:nanoseconds:纳秒

二:BlockingQueue:线程池中存放任务的队列,用来进行存储线程池中所有待执行任务

1)ArrayBlockingQueue:一个有数组结构组成的有界阻塞队列

2)LinkedlockingQueue:一个由链表结构组成的有界阻塞队列

3)SynchronousQueue:一个不包含元素的阻塞队列,直接提交给线程不会保持他们

4)PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列

5)DelayQueue:一个有优先级实现的无界阻塞队列,只有延迟期满的时候才可以从中提取元素,指定延迟时间

6)LinkedTransferQueue:一个由链表结构组成的无界阻塞队列

7)inkedBlockingDeque:一个由链表结构组成的双向阻塞队列。

三:ThreadFactory:线程池调用线程的时候调用的工厂方法,通过这个方法可以进行设置线程的优先级,线程命名规则以及线程类型,是守护线程还是普通线程:

创建ThreadFactory实例,重写newThread方法:在里面创建线程:

package OperateNode;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ExectorOperator{
    public static void main(String[] args) throws InterruptedException {
        ThreadFactory factor=new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread=new Thread(r);
                thread.setDaemon(false);//设置为守护线程
                thread.setName("李佳伟的线程");
                thread.setPriority(Thread.MAX_PRIORITY);
                return thread;
            }
        };
        ThreadPoolExecutor executor=new ThreadPoolExecutor(10,10,0, TimeUnit.SECONDS,new LinkedBlockingQueue<>(10),factor);
        executor.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"正在运行");
            }
        });
        //Thread.sleep(100000);
    }

}

四:RejectedExecutionHandler,拒绝策略:当线程池的任务超过任务队列可以存储的最大值之后,执行的策略:

1)第一种拒绝策略是AbortPolicy(饿报它,泡了see),这种拒绝策略在拒绝任务时,会直接抛出异常 RejectedExecutionException (属于RuntimeException),让你感知到任务被拒绝了(这是线程池的默认拒绝策略)

2)第二种拒绝策略是DiscardPolicy:当新任务被提交后直接被丢弃掉,也不会给你任何的通知,相对而言存在一定的风险,因为我们提交的时候根本不知道这个任务会被丢弃,可能造成数据丢失。

3)第三种拒绝策略是DiscardOldestPolicy,如果线程池没被关闭且没有能力执行,则会丢弃任务队列中的头结点,通常是存活时间最长的任务

4)第四种拒绝策略是:CallerRunsPolicy(考乐runs泡了see):调用当前的mian线程执行这个任务,把任务交给添加此任务的线程来进行执行

线程池的执行流程:从线程池的执行方法execute方法说起

1)先进行判断当前线程数是否大于核心线程数,如果为false,就新建线程(正式工)并执行任务

2)如果为true,在进行判断当前任务队列是否满了,如果为false,就把任务添加到任务队列里面等待线程执行

3)如果返回结果是true,在进行判断当前线程数是否大于最大线程数,结果是false,那么就新建线程执行此任务,否则就执行线程池的拒绝策略

下面我们来进行演示一下线程池的默认拒绝策略:通过new 

1)DisCardPolicy:忽略此任务,忽略最新的任务:

   Runnable runnable=new Runnable() {
            @Override
            public void run() {
                System.out.println("当前线程被执行"+Thread.currentThread().getName());
            }
        };
 //线程池的默认策略:DiscardPolicy,忽略最新添加的任务,下面我们只创建只有一个线程的线程池
ThreadPoolExecutor executor=new ThreadPoolExecutor(1,1,100, TimeUnit.SECONDS,new LinkedBlockingQueue<>(1),new ThreadPoolExecutor.DiscardPolicy());
executor.submit(runnable);
executor.submit(runnable);
executor.submit(runnable);
executor.submit(runnable);

当前线程被执行pool-1-thread-1
当前线程被执行pool-1-thread-1

1)从上述结果可以看出给线程池添加了四个任务,当添加第一个任务的时候,先判断当前线程数是否小于核心线程数,当前线程数是0,新创建线程执行任务,此时线程数为1

2)此时我们再添加一个任务,先判断当前线程数是否大于核心线程数,不小于;那么将这个任务添加到阻塞队列

3)此时我们添加第三个任务,当前线程数不小于核心线程数,况且任务队列已经满了,况且当前线程数等于最大线程数,那么执行拒绝策略,直接进行忽略;

2)AbortPolicy拒绝策略:JDK的默认拒绝策略并且终止策略,线程池会抛出异常并终止执行此任务

public class ExectorOperator {
    public static void main(String[] args) {
        Runnable runnable=new Runnable() {
            @Override
            public void run() {
                System.out.println("当前线程被执行"+Thread.currentThread().getName());
            }
        };
        //线程池的默认策略:AbortPolicy,任务队列满了直接拒绝
        ThreadPoolExecutor executor=new ThreadPoolExecutor(1,1,100, TimeUnit.SECONDS,new LinkedBlockingQueue<>(1),new ThreadPoolExecutor.AbortPolicy());
        executor.submit(runnable);
        executor.submit(runnable);
        executor.submit(runnable);
        executor.submit(runnable);
    }
}

从上面结果可以看出,给线程池添加了四个任务,而线程池添加了4个任务,而线程池只执行了两个任务就结束了,其他两个任务执行了终止策略,并抛出了拒绝执行的异常

3)自定义拒绝策略:

当然除了JDK提供的四种拒绝策略之外,我们还可以通过实现new RejectedExecutionHandler,并重写rejectExecution方法来实现自定义拒绝策略

package OperateNode;

import java.sql.Time;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ExectorOperator {
    public static void main(String[] args) {
        Runnable runnable=new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"正在执行");
            }
        };
 ThreadPoolExecutor executor=new ThreadPoolExecutor(1, 1, 100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1), new RejectedExecutionHandler() {
     @Override
     public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
         System.out.println("我是拒绝策略");
     }
 });
 executor.submit(runnable);
 executor.submit(runnable);
 executor.submit(runnable);
 executor.submit(runnable);

    }
}

为什么我们创建线程池一定要使用ThreadPoolExecutor? ----线程个数和任务个数不可控

1)在JAVA里面,我们一定要使用线程池来进行创建线程池,因为这种方式可以通过来进行创建线程池,因为这种方式可以通过参数来进行控制最大任务数和拒绝策略,让线程池的执行变得更加透明和可控,并且可以规避资源耗尽的风险

2)但是使用Exectors来进行创建线程会导致线程个数或者任务个数不可控,可能会导致内存溢出的风险

1)比如说我想要进行创建Exectors.newCachedThreadPool和创建线程的时候,并不会限制线程的数量,它允许创建的线程数是Integer.MAXVALUE,当任务数量特别多的时候,就会进行创建非常多的线程,咱们的OOM,每一个线程至少要消耗IM大小的内存,加上JDK的系统的类的加载也是需要消耗一部分的内存了,所以说就有可能会内存耗尽;

2)其他创建线程池的问题,比如说Executors自动创建线程池的方式,Exectors.newFixedThreadPool()和new SingleThreadPool(),默认情况下任务队列LinkedBlockingQueue的存储容量是Integer.MAX_VALUE,也是趋于无限大的,这样就会导致线程池中任务过多而导致内存溢出问题

小林coding

线程池的状态: 

1)running:运行状态,线程池创建好了之后就会进入到这个状态,如果说不手动调用关闭方法,那么这个线程池在整个程序运行期间都是这个状态

2)shutdown:关闭状态,不再接受新任务提交,但是会将已经保存在任务队列中的任务执行完成(执行完任务之后,线程数变成0)

3)stop:停止状态,不会再接受新任务提交,并且会中断正在执行的任务,放弃任务队列中已经有的任务(线程数直接变成0)

4)tidying:整理状态,当所有的任务都执行完毕之后,包括任务队列中的任务执行完毕之后,当前线程池的活动线程数降低到0的状态,到了这个状态之后,会调用线程池的terminated()方法

5)terminated:销毁状态,当执行完线程池的terminated()方法就会进入到这个状态

一:当我们进行调用shutDown()方法的时候,线程池的状态会从running再到shutDown状态,再到tidying状态,最后在到terminated状态

二:当我们调用shutDownNow()的方法的时候,线程池的状态会从running到stop,再到tidying,最后到teminated销毁状态 

线程池(2)--------JavaWeb_第1张图片

1)线程池的terminated()方法,也就是让线程池从tidying状态到terminated状态底层源码实现的方法是空方法

2)所以我们可以在创建线程池的时候重写这个方法

   ThreadPoolExecutor executor=new ThreadPoolExecutor(10,10,10,TimeUnit.SECONDS,new LinkedBlockingQueue<>(100)){
            @Override
            protected void terminated() {
                super.terminated();
                System.out.println("线程池被销毁");
            }
        };
        executor.shutdown();

    }

Java种终止线程的方式

1)自定义中断标识符,中断线程:自定义中断标识符的意思就是在程序中通过定义一个变量的方式来进行判断程序是否要继续向下执行

但是它有着一个很严重的问题,那就是说线程中断的会很不及时,因为尤其是线程在执行具体的代码的时候,是无法进行即使调用while(flag)来进行判断线程是否是终止状态,他只能在下一轮运行的时候判断是否要终止当前线程,所以他中断线程很不及时

2)Interrupt进行中断线程:调用Interrupted方法可以及时给执行任务的线程,发送一个中断线程的指令,他并不会直接进行中断线程,而是直接进入到catch语句,但是它相对于中断标识符更能及时地收到信号中断的指令

3)stop停止线程:他虽然可以进行停止线程,但是此方法已经是过时的,不再进行试用的过期方法,在JAVA最新的版本已经移除了,所以并不会直接建议进行使用

如何使用线程池执行定时任务?

1)在咱们的JAVA语言中,有两个线程池可以执行定时任务,那么是ScheduledThreadPool和new SingleThreadSecheduledPool,其中这里面的SingleThreadSchedulePool相当于是ScheduledThreadPool的单线程版本,他们的使用基本上是一致的

2)所以咱们重点介绍一下ScheduledThreadPool如何来进行执行定时任务

1)使用schedule方法执行定时任务,一次只执行一次定时任务

2)使用scheduleAtFixedRate执行定时任务,并且可以进行执行多次定时任务 

3)使用scheduleWithFixedDelay方法执行定时任务,并且执行多次定时任务

一:执行一次schedule方法只能执行一次定时任务,他需要进行进行传递第三个参数:

1.1)传递一个任务,Runnable或者Callable对象

1.2)第二个参数,添加定时任务之后过多久之后执行定时任务

1.3)第三个参数,时间单位,配合参数2一起进行使用

 ScheduledExecutorService executorService = Executors.newScheduledThreadPool(10);
        executorService.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("开始执行定时任务");

            }
        },10,TimeUnit.SECONDS);
    

第二种方式:scheduleAtFixedRate:

这个方法可以执行多次定时任务,此方法可以多次执行定时任务

此方法有四个参数:

2.1)传递一个任务是Runnable或者是Callable对象

2.2)传递一个时间,添加定时任务之后,在过多久之后执行定时任务

2.3)定时任务执行执行的时间间隔

2.4)执行的时间单位,配合参数2和参数3一起进行使用

   ScheduledExecutorService executorService = Executors.newScheduledThreadPool(10);
       executorService.scheduleAtFixedRate(new Runnable() {
           @Override
           public void run() {
               System.out.println("执行定时任务");
           }
       },3L,3L,TimeUnit.SECONDS);
    }

当任务添加成功之后,3s之后执行第一个定时任务,之后每隔两秒执行一次定时任务,它的执行间隔是固定的,不受定时任务执行时长影响;

第三种方式:scheduleWithFixedDelay和第三种方式用的基本差不多

 ScheduledExecutorService executorService = Executors.newScheduledThreadPool(10);
       executorService.scheduleWithFixedDelay(new Runnable() {
           @Override
           public void run() {
               System.out.println("执行定时任务");
           }
       },3L,3L,TimeUnit.SECONDS);
    }

从上述结果来看,定时任务在3s之后开始执行,以后每隔4S开始执行一次,这4S包含了定时任务花费的2s还有加上每隔2S执行一次的时间间隔,他是在定时任务完成之后,再格N秒完成任务,它的执行时间受定时任务执行时长的影响

 如何进行判断线程池已经完成所有任务了

在某种情况下,我们如果不对线程池是否任务完成做出判断,就会出现以下几种情况:

public static void main(String[] args) {
       ThreadPoolExecutor threadPool=new ThreadPoolExecutor(10, 10, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10), new RejectedExecutionHandler() {
           @Override
           public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
               System.out.println("执行线程池的拒绝策略");
           }
       });
       addTask(threadPool);
       System.out.println("线程池已经全部完成任务");
    }
    private static void addTask(ThreadPoolExecutor threadPool) {
        final int taskCount=9;
        for(int i=0;i

上面线程的打印顺序是先打印了线程池执行任务完成,再进行打印完成线程池的任务,主要原因是主线程main线程和线程池是并发执行的,线程池还没有执行完,main线程的打印代码就已经完成了

一:使用isTerminated()方法来进行判断线程池的任务是否已经执行完成:

 public static void main(String[] args) {
//1.创建线程池
        ThreadPoolExecutor executor=new ThreadPoolExecutor(10,10,3,TimeUnit.SECONDS,new LinkedBlockingQueue<>(10),new ThreadPoolExecutor.AbortPolicy());
//2.向线程池里面添加任务
        AddTask(executor);
//3.等待线程池执行完任务
        IsOK(executor);
//4.执行完成了进行打印操作
        System.out.println("线程池里面的所有任务已经完成");
    }

    private static void IsOK(ThreadPoolExecutor executor) {
        //改变线程池的所有状态
        executor.shutdown();
        //判断线程池中的终止状态,如果他不是终止状态,那么会一直执行
        while(!executor.isTerminated()){};
    }

    private static void AddTask(ThreadPoolExecutor executor) {
        for(int i=0;i<5;i++){
            final int a=i;
            executor.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程"+a+"正在执行");
                }
            });
        }
    }

shutDown()方法时启动线程池有序关闭的方法,他在方法执行完成之前会执行完当前已经进行提交的所有任务,并且不会再进行接受新的任务,当线程池的所有任务执行完成之后,线程池就进入到了终止状态,那么调用isTerminated()方法返回的结果就是true了,缺点就是需要关闭线程池

三:使用getCompletedTaskCount

1)getTaskCount(),返回计划执行的任务总数,因为任务和执行计划的状态可能在计算过程中动态变化,因此返回的就是一个近似值

2)getCompltedTaskCount(),返回完成执行任务的总数因为任务的状态和线程的状态可能在计算过程中动态的改变,所以说返回的值只是一个近似值,但是在连续的调用中并不会减少

   //判断线程池中的终止状态,如果他不是终止状态,那么会一直执行
  while(!(executor.getTaskCount()== executor.getCompletedTaskCount())){};

四:使用CountDownLatch:

CountDownLatch可理解成程序计数器,咱们可以进行创建N个任务的计数器,每一个任务执行完计数,那么计数器就-1,直到计数器减为0的时候,说明所有的任务都已经执行完成了,那么就可以直接执行下一段任务的代码了

  public static void main(String[] args) throws InterruptedException {
//1.创建线程池
ThreadPoolExecutor executor=new ThreadPoolExecutor(10,20,0,TimeUnit.SECONDS,new LinkedBlockingQueue<>(1024),new ThreadPoolExecutor.DiscardPolicy());
//2向线程池里面添加任务
        int TaskCount=5;
        CountDownLatch countDownLatch=new CountDownLatch(TaskCount);
        for(int i=0;i

他的写法虽然不用关闭线程池,但和它的缺点是只能使用一次,CountDownLatch只能被使用一次,创建之后不能被重复使用,也就是说CountDown可以被理解成只能被使用一次的计数器

五:使用CyclicBarrier:,它允许一组线程互相等待,直到到达某个公共屏障点;

1)构造方法可以进行传递两个参数,参数一是计数器的数量,参数2是当计数器为0的时候,所有任务都执行完成之后可以执行的方法或者事件

2)await()方法,在CyclicBarrier进行阻塞等待,调用这个方法的时候,CyclicBarrier内部用户的程序计数器会进行-1操作

//1.创建线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 20, 0, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1024), new ThreadPoolExecutor.DiscardPolicy());
        int TaskCount = 5;
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5, new Runnable() {
            @Override
            public void run() {
                System.out.println("线程池的所有任务已经执行完成");
            }
        });
        for(int i=0;i

除非直到遇到下面场景:

1)在CyclicBarrier上面等待的线程数量超过parties的时候,那么也就是计数器的声明数量的时候,那么所有线程释放,继续执行

2)当前线程被中断,那么直接抛出InterruptedException异常,并且停止等待,继续执行

3)其他等待的线程被中断或者其他等待的线程超时,那么当前线程抛出BrokenBarrierException异常,停止等待,继续执行

4)Boolean isBroken查看当前屏障是否处于损毁状态

你可能感兴趣的:(java,开发语言)