精华内容
下载资源
问答
  • JAVA多线程和线程池

    千次阅读 2019-08-25 11:06:12
    目录 1、线程状态 (1) 新建状态 (2) 就绪状态 (3) 运行状态 (4) 阻塞状态 (5) 死亡状态 ...2、线程优先级 ...4、创建线程 ...(3) 通过 Callable Future 创建线程 5、三种创建方式的区别 6、线程池 (...

    目录

    1、线程状态

    (1) 新建状态

    (2) 就绪状态

    (3) 运行状态

    (4) 阻塞状态

    (5) 死亡状态

    2、线程优先级

    3、同步工具synchronized、wait、notify

    4、创建线程

    (1) 实现 Runnable 接口

    (2) 继承 Thread 类

    (3) 通过 Callable 和 Future 创建线程

    5、三种创建方式的区别

    6、线程池

    (1) 什么是线程池

    (2) 为什么要有线程池

    (3) 线程池可以干什么

    (4) 线程池的创建

    7、线程池的实现类(ThreadPoolExecutor)

    (1) 直接提交队列

    (2) 有界的任务队列

    (3) 无界的任务队列

    (4) 优先任务队列

    (5) 几种常见的包装线程池类

    (6) 拒绝策略

    8、ThreadLocal 类

    9、线程安全

    10、synchronized 和 lock 的区别


    1、线程状态

    线程状态转换

    (1) 新建状态

            使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。

    (2) 就绪状态

            当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。

    (3) 运行状态

            如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。

    (4) 阻塞状态

            如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:

    • 等待阻塞,运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。wait()释放锁

    • 同步阻塞,线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。

    • 其他阻塞,通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。sleep()不释放锁

    (5) 死亡状态

            一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

    2、线程优先级

            每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。Java 线程的优先级是一个整数,其取值范围是1(Thread.MIN_PRIORITY)到10(Thread.MAX_PRIORITY)。默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。

    3、同步工具synchronized、wait、notify

            他们是应用于同步问题的人工线程调度工具。讲其本质,首先就要明确monitor的概念,Java中的每个对象都有一个监视器,来监测并发代码的重入。在非多线程编码时该监视器不发挥作用,反之如果在synchronized 范围内,监视器发挥作用。

            wait/notify必须存在于synchronized块中。并且,这三个关键字针对的是同一个监视器(某对象的监视器)。这意味着wait之后,其他线程可以进入同步块执行。

            当某代码并不持有监视器的使用权时去wait或notify,会抛出java.lang.IllegalMonitorStateException。也包括在synchronized块中去调用另一个对象的wait/notify,因为不同对象的监视器不同,同样会抛出此异常。

            synchronized单独使用:

    • 代码块:如下,在多线程环境下,synchronized块中的方法获取了lock实例的monitor,如果实例相同,那么只有一个线程能执行该块内容

    public class Thread1 implements Runnable { 
        Object lock; 
        public void run() { 
            synchronized(lock){ 
                //TODO 
            }
        }
    }
    • 直接用于方法: 相当于上面代码中用lock来锁定的效果,实际获取的是Thread1类的monitor。更进一步,如果修饰的是static方法,则锁定该类所有实例。

    public class Thread1 implements Runnable { 
        public synchronized void run() {
            //TODO 
        }
    }
    
    

            多线程的内存模型:main memory(主存)、working memory(线程栈),在处理数据时,线程会把值从主存load到本地栈,完成操作后再save回去(volatile关键词的作用:每次针对该变量的操作都激发一次load and save)。

            针对多线程使用的变量如果不是volatile或者final修饰的,很有可能产生不可预知的结果(另一个线程修改了这个值,但是之后在某线程看到的是修改之前的值)。其实道理上讲同一实例的同一属性本身只有一个副本。但是多线程是会缓存值的,本质上,volatile就是不去缓存,直接取值。在线程安全的情况下加volatile会牺牲性能。

    4、创建线程

            Java 提供了三种创建线程的方法:实现 Runnable 接口、继承 Thread 类、通过 Callable 和 Future 创建线程。

    线程创建方法

    (1) 实现 Runnable 接口

    public class Test {
        public static void main(String[] args) { 
            MyRunnable runnable = new MyRunnable(); 
            Thread thread = new Thread(runnable); 
            thread.start();
        } 
    } 
    class MyRunnable implements Runnable{ 
        public MyRunnable() {
            //TODO
        } 
        @Override public void run() { 
            //TODO
        } 
    }

    (2) 继承 Thread 类

    public class Test { 
        public static void main(String[] args) { 
            MyThread thread = new MyThread(); 
            thread.start(); 
        } 
    } 
    class MyThread extends Thread{ 
        public MyThread(){ 
            //TODO
        } 
        @Override public void run() { 
            //TODO
        } 
    }

            Thread类相关方法:

    //当前线程客转让CPU控制权,让别的就绪状态线程运行(切换)
    public static Thread.yield()
    
    //暂停一段时间
    public static Thread.slepp()
    
    //在一个项城中调用other.join(),将等待other线程执行完后才继续本线程
    public join()
    
    //后两个函数皆可以被打断
    public interrupte()

            关于中断:

            它并不像stop方法那样会中断一个正在运行的线程。线程会不时地检测中断标识位,以判断线程是否应该被中断(中断标识值是否为true)。终端只会影响到wait状态、sleep状态和join状态。被打断的线程会抛出InterruptedException。Thread.interrupted()检查当前线程是否发生中断,返回boolean。synchronized在获锁的过程中是不能被中断的。

            中断是一个状态!interrupt()方法只是将这个状态置为true而已。所以说正常运行的程序不去检测状态,就不会终止,而wait等阻塞方法会去检查并抛出异常。

            interrupt():设置当前中断标志位为true;

            interrupted():检查当前线程是否发生中断(即中断标志位是否为true)

            设置中断标志位后,只能通过wait()、sleep()、join()判断标志位,若标志位为true,会抛出InterruptedException异常,捕获异常后,手动中断线程或进行其他操作。

            不能使用try/catch来捕获异常!需使用自定义异常处理器捕获异常,步骤如下:

    1.定义异常处理器。实现 Thread.UncaughtExceptionHandler的uncaughtException方法
    
    //第一步:定义符合线程异常处理器规范的"异常处理器",实现Thread.UncaughtExceptionHandler规范
    
    class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler{
        //Thread.UncaughtExceptionHandler.uncaughtException()会在线程因未捕获的异常而临近死亡时被调用
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("caught:"+e);
        }
    }
    2.定义使用该异常处理器的线程工厂
    
    //第二步:定义线程工厂。线程工厂用来将任务附着给线程,并给该线程绑定一个异常处理器
    
    class HanlderThreadFactory implements ThreadFactory{
        @Override
        public Thread newThread(Runnable r) {
            System.out.println(this+"creating new Thread");
            Thread t = new Thread(r);
            System.out.println("created "+t);
            t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler()); //设定线程工厂的异常处理器
            System.out.println("eh="+t.getUncaughtExceptionHandler());
            return t;
        }
    }

            三、四步为测试:

    3.定义一个任务,让其抛出一个异常
    
    //第三步:我们的任务可能会抛出异常。显示的抛出一个exception
    
    class ExceptionThread implements Runnable{
        @Override
        public void run() {
            Thread t = Thread.currentThread();
            System.out.println("run() by "+t);
            System.out.println("eh = "+t.getUncaughtExceptionHandler());
            throw new RuntimeException();
        }
    }
    4.调用实验
    
    //第四步:使用线程工厂创建线程池,并调用其execute方法
    
    public class ThreadExceptionUncaughtExceptionHandler{
        public static void main(String[] args){
            ExecutorService exec = Executors.newCachedThreadPool(new HanlderThreadFactory());
            exec.execute(new ExceptionThread());
        }
    }

    (3) 通过 Callable 和 Future 创建线程

            上述两种创建线程的方法,在执行完任务之后无法获取执行结果。如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果。而Callable和Future可以在任务执行完毕之后得到任务执行结果。通过以下四种方法创建线程:

    • 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。

    • 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。

    • 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。

    • 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。

    5、三种创建方式的区别

            (1) 继承Thread类创建的线程可以拥有自己独立的类成员变量,但是实现Runnable接口创建线程共享实现接口类的成员变量。两中方式创建线程都要重写run方法,run方法是线程的执行体。(Thread和runnable均可以实现单独资源和共享资源)

            Eg.

            a、Thread类:

            启动两个线程,每个线程拥有单独的成员变量

    class MyThread extends Thread{
        //TODO 
    } 
    new MyThread().start(); 
    new MyThread().start();

            启动两个线程,两个线程共享成员变量

    MyThread m = new MyThread(); 
    new Thread(m).start(); 
    new Thread(m).start();

            b、Runnable接口:

            启动两个线程,共同享有成员变量

    class MyThread implements Runnable { 
        //TODO
    }
    
    MyThread m = new MyThread(); 
    new Thread(m).start(); 
    new Thread(m).start();

            启动两个线程,每个线程拥有单独的成员变量

    MyThread myThread = new MyThread(); 
    MyThread myThread2 = new MyThread(); 
    new Thread(myThread).start(); 
    new Thread(myThread2).start();

            (2) 在继承Thread类创建进程中可以通过使用this获得当前进程的对象,但是在实现Runnable接口创建线程的途径中可以使用Thread.currentThread()方式来获得当前进程。

            (3) 第三种方式是较为复杂的一种。Callable接口是一个与Runnable接口十分相似的接口。在Runnable中run方法为线程的执行体,但是在Callable接口中call方法是线程的执行体。下面是两个接口实现执行体的不同:

    • call方法有返回值,但是run方法没有

    • call方法可以生命抛出异常

            所以可以说Callable接口是Runnable接口的增强版本。

            (4)  FutureTask类实现了Runnable和Future接口。和Callable一样都是泛型。

            (5)  Future接口是对Callable任务的执行结果进行取消,查询是否完成,获取结果的。下面是这个接口的几个重要方法:

    • boolean cancel(boolean myInterruptRunning),试图取消Future与Callable关联的任务

    •  V get(), 返回Callable任务中call方法的返回值,调用该方法会导致程序阻塞,必须等到子线程结束才会有返回值。这里V表示泛型

    • V get(long timeout, TimeUnit  unit), 返回Callable中call方法的返回值,该方法让程序最多阻塞timeout毫秒的时间,或者直到unit时间点。如果在指定的时间Callable的任务没有完成就会抛出异常TimeoutEexception

    • boolean  isCancelled(), 如果Callable中的任务被取消,则返回true,否则返回false

    • boolean isDone(),如果Callable中的任务被完成,则返回true,否则返回false

    6、线程池

    (1) 什么是线程池

            线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源

    (2) 为什么要有线程池

            在java中,如果每个请求到达就创建一个新线程,开销是相当大的。在实际使用中,创建和销毁线程花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在一个jvm里创建太多的线程,可能会使系统由于过度消耗内存或"切换过度"而导致系统资源不足。为了防止资源不足,需要采取一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利用已有对象来进行服务。

    (3) 线程池可以干什么

            线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使用应用程序响应更快;另外,通过适当的调整线程中的线程数目可以防止出现资源不足的情况。

    (4) 线程池的创建

            线程池都是通过线程池工厂创建,再调用线程池中的方法获取线程,再通过线程去执行任务方法。

    • Executors:线程池创建工厂类

    • public static ExecutorService newFixedThreadPool(int nThreads):返回线程池对象

    • ExecutorService:线程池类

    • Future<?> submit(Runnable task):获取线程池中的某一个线程对象,并执行

    • Future 接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用

            a、使用Runnable接口创建线程池

    • 创建线程池对象

    • 关闭线程池

    • 提交 Runnable 接口子类对象

    • 创建 Runnable 接口子类对象

    public static void main(String[] args) {
        //创建线程池对象  参数5,代表有5个线程的线程池
        ExecutorService service = Executors.newFixedThreadPool(5);
    
        //创建Runnable线程任务对象
        TaskRunnable task = new TaskRunnable();
            
        //从线程池中获取线程对象
        service.submit(task);
        System.out.println("----------------------");
            
        //再获取一个线程对象
        service.submit(task);
            
        //关闭线程池
        service.shutdown();
    }

            b、使用Callable接口创建线程池

            ExecutorService:线程池类

            <T> Future<T> submit(Callable<T> task):获取线程池中的某一个线程对象,并执行线程中的 call() 方法

            Future 接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用

    • 创建线程池对象

    • 创建 Callable 接口子类对象

    • 提交 Callable接口子类对象

    • 关闭线程池

    public static void main(String[] args) {
           
        ExecutorService service = Executors.newFixedThreadPool(3);
        TaskCallable c = new TaskCallable();
            
        //线程池中获取线程对象,调用run方法
        service.submit(c);
            
        //再获取一个
        service.submit(c);
            
        //关闭线程池
        service.shutdown();
    }

    7、线程池的实现类(ThreadPoolExecutor)

            ThreadPoolExecutor 类继承了 AbstractExecutorService 类,而 AbstractExecutorService 类实现了 ExecutorService 接口。所以上述线程池创建的方法可以将创建的线程池(例如newFixedThreadPool)赋给 ExecutorService。

            ThreadPoolExecutor 构造函数如下:

    ThreadPoolExecutor ( int corePoolSize, // 线程池中的线程数量
    
                        int maximumPoolSize, // 线程池中的最大线程数量
    
                        long keepAliveTime, // 当线程池线程数量超过corePoolsize时,多余的空闲线程的存活时间,即超过corePoolSize的空闲线程,在keepAliveTime时间内会被销毁
                        
                        TimeUnit unit, // keepAliveTime的单位
    
                        BlockingQueue<Runnable> workQueue, // 任务队列,被提交但尚未被执行的任务
    
                        ThreadFactory threadFactory, // 线程工厂,用于创建线程,一般用默认的线程工厂即可
    
                        RejectedExecutionHandler handler) // 拒绝策略。当任务太多来不及处理时,如何拒绝任务

            关键参数:workQueue

    (1) 直接提交队列

            由 SynchronousQueue 实现,一种无缓冲的等待队列。

            SynchronousQueue 没有容量,即没有等待队列,总是将新任务提交给线程去执行,当没有空闲线程时,就新增一个线程,当线程数达到最大值maximumPoolSize时,即无法再新增线程时,则执行拒绝策略。

    (2) 有界的任务队列

            由 ArrayBlockingQueue 实现,其内部维护了一个定长数组,用于储存队列,其内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。ArrayBlockingQueue 在生产者放入数据和消费者获取数据时,都是共用同一个锁对象,由此也意味着两者无法真正并行运行,这点尤其不同于 LinkedBlockingQueue;按照实现原理来分析,ArrayBlockingQueue 完全可以采用分离锁,从而实现生产者和消费者操作的完全并行运行。之所以没这样去做,也许是因为 ArrayBlockingQueue 的数据写入和获取操作已经足够轻巧,以至于引入独立的锁机制,除了给代码带来额外的复杂性外,其在性能上完全占不到任何便宜。 ArrayBlockingQueue 和LinkedBlockingQueue 间还有一个明显的不同之处在于,前者在插入或删除元素时不会产生或销毁任何额外的对象实例,而后者则会生成一个额外的 Node 对象。这在长时间内需要高效并发地处理大批量数据的系统中,其对于GC的影响还是存在一定的区别。而在创建ArrayBlockingQueue 时,我们还可以控制对象的内部锁是否采用公平锁,默认采用非公平锁。

            当有新任务需要执行时,如果线程池的实际线程数小于 corePoolSize,则会新增一个线程,若大于 corePoolSize,则会将新任务加入等待队列。若队列已满,则在总线程数不大于maximumPoolSize 的前提下,新增一个线程,若大于 maximumPoolSize,则执行拒绝策略。也就是说,只有当等待队列满了的时候,才可能将线程数增加到 corePoolSize 以上。也就是说,除非系统非常繁忙,否则线程数量基本维持在 corePoolSize。

    (3) 无界的任务队列

            由 LinkedBlockingQueue 实现,其内部维护了一个链表(如果没有指定长度,则默认容量为无穷大),LinkedBlockingQueue 之所以能够高效的处理并发数据,是因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。作为开发者,我们需要注意的是,如果构造一个 LinkedBlockingQueue 对象,而没有指定其容量大小,LinkedBlockingQueue 会默认一个类似无限大小的容量(Integer.MAX_VALUE),这样的话,如果生产者的速度一旦大于消费者的速度,也许还没有等到队列满阻塞产生,系统内存就有可能已被消耗殆尽了。

            与有界队列相比,除非系统资源耗尽,否则无界队列不存在任务入队失败的情况,即无界队列的长度是无穷大。当有新的任务需要执行时,若线程池的实际数量小于corePoolSize,则会新增一个线程,且线程池的最大线程数为corePoolSize。若生产者的速度远小于消费者的速度,则等待队列会快速增长,直至系统资源耗尽。

    (4) 优先任务队列

            由 PriorityBlockingQueue 实现,其内部维护了一个数组,优先级的判断通过构造函数传入的 Comparator 对象来决定,需要注意的是 PriorityBlockingQueue 并不会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者。因此使用的时候要特别注意,生产者生产数据的速度绝对不能快于消费者消费数据的速度,否则时间一长,会最终耗尽所有的可用堆内存空间。在实现 PriorityBlockingQueue 时,内部控制线程同步的锁采用的是公平锁。

    这是一个有优先级的无界队列。

    (5) 几种常见的包装线程池类

    • newFixedThreadPool:设置 corePoolSize 和 maximumPoolSize 相等,使用无界的任务队列(LinkedBlockingQueue)

    • newSignalThreadExecutor:newFixedThreadPool 的一种特殊形式,即 corePoolSize为1

    • newCachedThreadPool:设置 corePoolSize 为0,maximumPoolSize 为无穷大,使用直接提交队列(SynchronousQueue)

    (6) 拒绝策略

    • AbortPolicy:直接抛出异常,阻止系统正常工作

    • CallerRunsPolicy:由调用线程直接执行当前任务,可能会造成任务提交线程(即调用线程)的性能急剧下降

    • DiscardOldestPolicy:丢弃等待队列头的一个任务,并再次提交该任务

    • DiscardPolicy:丢弃提交任务,即什么都不做

    8、ThreadLocal 类

            用处:保存线程的独立变量。对一个线程类(继承自Thread),当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。常用于用户登录控制,如记录session信息。

            实现:每个Thread都持有一个TreadLocalMap类型的变量(该类是一个轻量级的Map,功能与map一样,区别是桶里放的是entry而不是entry的链表。功能还是一个map。)以本身为key,以目标为value。主要方法是get()和set(T a),set之后在map里维护一个threadLocal -> a,get时将a返回。ThreadLocal是一个特殊的容器。

            ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

            #########################################################################

            每个Thread持有一个ThreadLocalMap,key-value,key为弱引用,value为强引用,解决方法间共享和实例线程间隔离;静态变量只能解决方法间共享

            某一个类的多个实例线程,多个类的多个实例线程

            ThreadLocal解决的是某类的多个实例线程间的隔离问题,ThreadLocal属于一个类,是一个类的私有变量。

            普通的私有变量可以保证不同类之间相互隔离,不能保证同一个类不同实例间的相互隔离,而ThreadLocal却可以。例如,

    public class ThreadRunnableDemo implement Runnable{ 
        private int a; 
        private ThreadLocal<String> b; 
    }

            (1) 分别使用不同 Runnable 生成 Thread

    Thread t1 = new Thread(new ThreadRunnableDemo ()); 
    Thread t2 = new Thread(new ThreadRunnableDemo ());

            (2) 用同一个 Runnable 生成 Thread

    ThreadRunnableDemo t = new ThreadRunnableDemo (); 
    Thread t1 = new Thread(t); 
    Thread t2 = new Thread(t);

            对于上述两种情况:

            a、(1) 中可保证变量a是线程间隔离的(即线程安全的);而 (2) 中却不可以,因为他们用同一个Runnable去生成Thread,由于变量a是属于Runnable的,所以会产生线程安全问题。

            b、(1)(2)中 ThreadLocal 是线程安全的,即使使用同一个Runnable生成Thread,他们也各自使用一个变量副本。

    9、线程安全

            Synchronized、volatile、原子类、Lock锁

    10、synchronized 和 lock 的区别

            Synchronized是关键字;不支持等待超时中断;读操作之间仍然是互斥的,不能同时进行;不需要手动释放锁

            Lock是一个类;可以只等待一定的时间或者能够响应中断;读操作之间不互斥,可以同时进行;需要手动释放锁

     

    展开全文
  • Java多线程和线程池

    万次阅读 2016-05-12 21:28:53
    一、Java自带线程池先看看Java自带线程池的例子,开启5个线程打印字符串List:package com.luo.test;import java.util.ArrayList; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import ...

    1.为什么要使用线程池

    在java中,如果每个请求到达就创建一个新线程,开销是相当大的。在实际使用中,服务器在创建和销毁线程上花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在一个jvm里创建太多的线程,可能会使系统由于过度消耗内存或“切换过度”而导致系统资源不足。为了防止资源不足,服务器应用程序需要采取一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利
    用已有对象来进行服务,这就是“池化资源”技术产生的原因。

    线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使用应用程序响应更快。另外,通过适当的调整线程中的线程数目可以防止出现资源不足的情况。

    2.线程池的组成部分

    一个比较简单的线程池至少应包含线程池管理器、工作线程、任务列队、任务接口等部分。其中线程池管理器的作用是创建、销毁并管理线程池,将工作线程放入线程池中;工作线程是一个可以循环执行任务的线程,在没有任务是进行等待;任务列队的作用是提供一种缓冲机制,将没有处理的任务放在任务列队中;任务接口是每个任务必须实现的接口,主要用来规定任务的入口、任务执行完后的收尾工作、任务的执行状态等,工作线程通过该接口调度任务的执行。

    线程池管理器至少有下列功能:创建线程池,销毁线程池,添加新任务。

    工作线程是一个可以循环执行任务的线程,在没有任务时将等待。

    任务接口是为所有任务提供统一的接口,以便工作线程处理。任务接口主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等。

    3.线程池适合应用的场合

    当一个服务器接受到大量短小线程的请求时,使用线程池技术是非常合适的,它可以大大减少线程的创建和销毁次数,提高服务器的工作效率。但是线程要求的运动时间比较长,即线程的运行时间比…….

    以上信息来自如下文章:http://www.blogjava.net/stevenjohn/archive/2011/12/12/366161.html

    一、Java自带线程池

    先看看Java自带线程池的例子,开启5个线程打印字符串List:

    package com.luo.test;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.ArrayBlockingQueue;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    public class ThreadTest {
    
        public static void main(String[] args) {
    
            List<String> strList = new ArrayList<String>();
            for (int i = 0; i < 100; i++) {
                strList.add("String" + i);
            }
            int threadNum = strList.size() < 5 ? strList.size() : 5;
            ThreadPoolExecutor executor = new ThreadPoolExecutor(2, threadNum, 300,
                    TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(3),
                    new ThreadPoolExecutor.CallerRunsPolicy());
            for (int i = 0; i < threadNum; i++) {
                executor.execute(new PrintStringThread(i,strList,threadNum));
            }
            executor.shutdown();
        }
    }
    
    class PrintStringThread implements Runnable {
    
        private int num;
    
        private List<String> strList;
    
        private int threadNum;
    
        public PrintStringThread(int num, List<String> strList, int threadNum) {
            this.num = num;
            this.strList = strList;
            this.threadNum = threadNum;
        }
    
        public void run() {
            int length = 0;
            for(String str : strList){
                if (length % threadNum == num) {
                    System.out.println("线程编号:" + num + ",字符串:" + str);
                }
                length ++;
            }
        }
    }
    

    Java自带线程池构造方法

    ThreadPoolExecutor(int corePoolSize, 
    int maximumPoolSize,
    long keepAliveTime,
    TimeUnit unit,
    BlockingQueue<Runnable> workQueue
    RejectedExecutionHandler handler)
    
    corePoolSize: 线程池维护线程的最少线程数,也是核心线程数,包括空闲线程
    maximumPoolSize: 线程池维护线程的最大线程数
    keepAliveTime: 线程池维护线程所允许的空闲时间
    unit: 程池维护线程所允许的空闲时间的单位
    workQueue: 线程池所使用的缓冲队列
    handler: 线程池对拒绝任务的处理策略
    
    当一个任务通过execute(Runnable)方法欲添加到线程池时:
    1、 如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
    2、 如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。
    3、如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。
    4、 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。也就是:处理任务的优先级为:核心线程corePoolSize、任务队列workQueue、最大线程 maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。
    5、 当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。

    分割线

    事实上上面的例子代码写得有不足之处,如果你看出不足之处,说明你理解了线程池。否则可以多看几遍哦。

    二、Spring线程池配置

    3.1、直接调用

    ThreadPoolTaskExecutor poolTaskExecutor = new ThreadPoolTaskExecutor();  
    //线程池所使用的缓冲队列  
    poolTaskExecutor.setQueueCapacity(200);  
    //线程池维护线程的最少数量  
    poolTaskExecutor.setCorePoolSize(5);  
    //线程池维护线程的最大数量  
    poolTaskExecutor.setMaxPoolSize(1000);  
    //线程池维护线程所允许的空闲时间  
    poolTaskExecutor.setKeepAliveSeconds(30000);  
    poolTaskExecutor.initialize(); 

    3.2、通过配置文件

    <bean id="poolTaskExecutor"      class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
       <!-- 核心线程数,默认为1 -->
       <property name="corePoolSize" value="5" />
       <!-- 最大线程数,默认为Integer.MAX_VALUE -->
       <property name="maxPoolSize" value="50" />
       <!-- 队列最大长度,一般需要设置值>=notifyScheduledMainExecutor.maxNum;默认为Integer.MAX_VALUE -->
       <property name="queueCapacity" value="2000" />
       <!-- 线程池维护线程所允许的空闲时间,默认为60s -->
       <property name="keepAliveSeconds" value="100" />
       <!-- 线程池对拒绝任务(无线程可用)的处理策略,目前只支持AbortPolicy、CallerRunsPolicy;默认为后者 -->
       <property name="rejectedExecutionHandler">
           <!-- AbortPolicy:直接抛出java.util.concurrent.RejectedExecutionException异常 -->
           <!-- CallerRunsPolicy:主线程直接执行该任务,执行完之后尝试添加下一个任务到线程池中,可以有效降低向线程池内添加任务的速度 -->
           <!-- DiscardOldestPolicy:抛弃旧的任务、暂不支持;会导致被丢弃的任务无法再次被执行 -->
           <!-- DiscardPolicy:抛弃当前任务、暂不支持;会导致被丢弃的任务无法再次被执行 -->
           <bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" />
       </property>
    </bean>
    corePoolSize
    核心线程数,核心线程会一直存活,即使没有任务需要处理。当线程数小于核心线程数时,即使现有的线程空闲,线程池也会优先创建新线程来处理任务,而不是直接交给现有的线程处理。
    核心线程在allowCoreThreadTimeout被设置为true时会超时退出,默认情况下不会退出。
    maxPoolSize
    当线程数大于或等于核心线程,且任务队列已满时,线程池会创建新的线程,直到线程数量达到maxPoolSize。如果线程数已等于maxPoolSize,且任务队列已满,则已超出线程池的处理能力,线程池会拒绝处理任务而抛出异常。
    keepAliveTime
    当线程空闲时间达到keepAliveTime,该线程会退出,直到线程数量等于corePoolSize。如果allowCoreThreadTimeout设置为true,则所有线程均会退出直到线程数量为0。
    allowCoreThreadTimeout
    是否允许核心线程空闲退出,默认值为false。
    queueCapacity
    任务队列容量。从maxPoolSize的描述上可以看出,任务队列的容量会影响到线程的变化,因此任务队列的长度也需要恰当的设置。
    
    线程池按以下行为执行任务
    当线程数小于核心线程数时,创建线程。
    当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
    当线程数大于等于核心线程数,且任务队列已满
    若线程数小于最大线程数,创建线程
    若线程数等于最大线程数,抛出异常,拒绝任务
    
    系统负载
    参数的设置跟系统的负载有直接的关系,下面为系统负载的相关参数:
    tasks,每秒需要处理的最大任务数量
    tasktime,处理第个任务所需要的时间
    responsetime,系统允许任务最大的响应时间,比如每个任务的响应时间不得超过2秒。
    
    参数设置
    
    corePoolSize:
    每个任务需要tasktime秒处理,则每个线程每钞可处理1/tasktime个任务。系统每秒有tasks个任务需要处理,则需要的线程数为:tasks/(1/tasktime),即tasks*tasktime个线程数。假设系统每秒任务数为100~1000,每个任务耗时0.1秒,则需要100*0.11000*0.1,即10~100个线程。那么corePoolSize应该设置为大于10,具体数字最好根据8020原则,即80%情况下系统每秒任务数,若系统80%的情况下第秒任务数小于200,最多时为1000,则corePoolSize可设置为20queueCapacity:
    任务队列的长度要根据核心线程数,以及系统对任务响应时间的要求有关。队列长度可以设置为(corePoolSize/tasktime)*responsetime: (20/0.1)*2=400,即队列长度可设置为400。
    队列长度设置过大,会导致任务响应时间过长,切忌以下写法:
    LinkedBlockingQueue queue = new LinkedBlockingQueue();
    这实际上是将队列长度设置为Integer.MAX_VALUE,将会导致线程数量永远为corePoolSize,再也不会增加,当任务数量陡增时,任务响应时间也将随之陡增。
    
    maxPoolSize:
    当系统负载达到最大值时,核心线程数已无法按时处理完所有任务,这时就需要增加线程。每秒200个任务需要20个线程,那么当每秒达到1000个任务时,则需要(1000-queueCapacity)*(20/200),即60个线程,可将maxPoolSize设置为60keepAliveTime:
    线程数量只增加不减少也不行。当负载降低时,可减少线程数量,如果一个线程空闲时间达到keepAliveTiime,该线程就退出。默认情况下线程池最少会保持corePoolSize个线程。
    
    allowCoreThreadTimeout:
    默认情况下核心线程不会退出,可通过将该参数设置为true,让核心线程也退出。
    
    以上关于线程数量的计算并没有考虑CPU的情况。若结合CPU的情况,比如,当线程数量达到50时,CPU达到100%,则将maxPoolSize设置为60也不合适,此时若系统负载长时间维持在每秒1000个任务,则超出线程池处理能力,应设法降低每个任务的处理时间(tasktime)。
    
    from:http://blog.csdn.net/zhouhl_cn/article/details/7392607
    
    1、线程池的线程数设置需结合业务量、程序处理中IO和CPU使用占比、服务器CPU个数  设定最大线程数, 无特殊需要最大不能超过50个。
    2、线程队列大小确保短时间业务量暴涨不会队列溢出或入队等待。
    3、线程池建议采用可伸缩的线程池(core + max),支持超时idle释放 .
    
    关闭线程池的时候需要优雅关闭:http://blog.csdn.net/luoxinwu123/article/details/7660625
    展开全文
  • Java多线程线程池的使用(记录)

    千次阅读 2019-04-16 17:43:49
    Java多线程: 可以通过new Thread(runnable).start()开启子线程,但是实际应用中这种开启多线程的方法在线程数非常多的情况下不一定能实现期望的功能,主要是在重复创建销毁线程时消耗了大量的资源,导致多线程的...

    Java多线程:

    可以通过new Thread(runnable).start()开启子线程,但是实际应用中这种开启多线程的方法在线程数非常多的情况下不一定能实现期望的功能,主要是在重复创建和销毁线程时消耗了大量的资源,导致多线程的效率反而没有单线程效率高。

    通过线程池Executors.newFixedThreadPool()对创建的线程进行复用,减少了资源的消耗。

    ExecutorService executorService = Executors.newFixedThreadPool(5);//创建线程池
            for (int i = 0; i < 100; i++) {
                Runnable runnable = new Runnable() {
    
                    @Override
                    public void run() {
                        Log.d("MainActivity", Thread.currentThread().getName());
                    }
                };
                executorService.execute(runnable);//将任务添加到线程池中
            }

    通过new ThreadPoolExecutor()创建线程池

    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 5, 1,
                TimeUnit.MINUTES, new LinkedBlockingQueue<Runnable>());
     ThreadLocal<LocalVariable> threadLocal = new ThreadLocal<>();
    for (int i = 0; i < 100; i++) {
                threadPoolExecutor.execute(new Runnable() {
                    @Override
                    public void run() {
                        threadLocal.set(new LocalVariable());
                        Log.d("MainActivity", Thread.currentThread().getName());
                        threadLocal.remove();
    
                    }
                });
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

    只是作为记录,没有实际项目中使用

    展开全文
  • Java 多线程:彻底搞懂线程池

    万次阅读 多人点赞 2019-07-09 19:27:00
    熟悉Java多线程编程的同学都知道,当我们线程创建过多时,容易引发内存溢出,因此我们就有必要使用线程池的技术了。 目录 1 线程池的优势 2 线程池的使用 3 线程池的工作原理 4 线程池的参数 4.1 任务队列...

    熟悉 Java 多线程编程的同学都知道,当我们线程创建过多时,容易引发内存溢出,因此我们就有必要使用线程池的技术了。

    目录

    1 线程池的优势

    2 线程池的使用

    3 线程池的工作原理

    4 线程池的参数

    4.1 任务队列(workQueue)

    4.2 线程工厂(threadFactory)

    4.3 拒绝策略(handler)

    5 功能线程池

    5.1 定长线程池(FixedThreadPool)

    5.2 定时线程池(ScheduledThreadPool )

    5.3 可缓存线程池(CachedThreadPool)

    5.4 单线程化线程池(SingleThreadExecutor)

    5.5 对比

    6 总结

    参考


    1 线程池的优势

    总体来说,线程池有如下的优势:

    (1)降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

    (2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

    (3)提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

    2 线程池的使用

    线程池的真正实现类是 ThreadPoolExecutor,其构造方法有如下4种:

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
    
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }
    
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }
    
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

    可以看到,其需要如下几个参数:

    • corePoolSize(必需):核心线程数。默认情况下,核心线程会一直存活,但是当将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
    • maximumPoolSize(必需):线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。
    • keepAliveTime(必需):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
    • unit(必需):指定 keepAliveTime 参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
    • workQueue(必需):任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。
    • threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。
    • handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。

    线程池的使用流程如下:

    // 创建线程池
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE,
                                                 MAXIMUM_POOL_SIZE,
                                                 KEEP_ALIVE,
                                                 TimeUnit.SECONDS,
                                                 sPoolWorkQueue,
                                                 sThreadFactory);
    // 向线程池提交任务
    threadPool.execute(new Runnable() {
        @Override
        public void run() {
            ... // 线程执行的任务
        }
    });
    // 关闭线程池
    threadPool.shutdown(); // 设置线程池的状态为SHUTDOWN,然后中断所有没有正在执行任务的线程
    threadPool.shutdownNow(); // 设置线程池的状态为 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表

    3 线程池的工作原理

    下面来描述一下线程池工作的原理,同时对上面的参数有一个更深的了解。其工作原理流程图如下:

    通过上图,相信大家已经对所有参数有个了解了。下面再对任务队列、线程工厂和拒绝策略做更多的说明。

    4 线程池的参数

    4.1 任务队列(workQueue)

    任务队列是基于阻塞队列实现的,即采用生产者消费者模式,在 Java 中需要实现 BlockingQueue 接口。但 Java 已经为我们提供了 7 种阻塞队列的实现:

    1. ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列(数组结构可配合指针实现一个环形队列)。
    2. LinkedBlockingQueue: 一个由链表结构组成的有界阻塞队列,在未指明容量时,容量默认为 Integer.MAX_VALUE
    3. PriorityBlockingQueue: 一个支持优先级排序的无界阻塞队列,对元素没有要求,可以实现 Comparable 接口也可以提供 Comparator 来对队列中的元素进行比较。跟时间没有任何关系,仅仅是按照优先级取任务。
    4. DelayQueue:类似于PriorityBlockingQueue,是二叉堆实现的无界优先级阻塞队列。要求元素都实现 Delayed 接口,通过执行时延从队列中提取任务,时间没到任务取不出来。
    5. SynchronousQueue: 一个不存储元素的阻塞队列,消费者线程调用 take() 方法的时候就会发生阻塞,直到有一个生产者线程生产了一个元素,消费者线程就可以拿到这个元素并返回;生产者线程调用 put() 方法的时候也会发生阻塞,直到有一个消费者线程消费了一个元素,生产者才会返回。
    6. LinkedBlockingDeque: 使用双向队列实现的有界双端阻塞队列。双端意味着可以像普通队列一样 FIFO(先进先出),也可以像栈一样 FILO(先进后出)。
    7. LinkedTransferQueue: 它是ConcurrentLinkedQueueLinkedBlockingQueue 和 SynchronousQueue 的结合体,但是把它用在 ThreadPoolExecutor 中,和 LinkedBlockingQueue 行为一致,但是是无界的阻塞队列。

    注意有界队列和无界队列的区别:如果使用有界队列,当队列饱和时并超过最大线程数时就会执行拒绝策略;而如果使用无界队列,因为任务队列永远都可以添加任务,所以设置 maximumPoolSize 没有任何意义。

    4.2 线程工厂(threadFactory)

    线程工厂指定创建线程的方式,需要实现 ThreadFactory 接口,并实现 newThread(Runnable r) 方法。该参数可以不用指定,Executors 框架已经为我们实现了一个默认的线程工厂:

    /**
     * The default thread factory.
     */
    private static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;
    
        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }
    
        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }

    4.3 拒绝策略(handler)

    当线程池的线程数达到最大线程数时,需要执行拒绝策略。拒绝策略需要实现 RejectedExecutionHandler 接口,并实现 rejectedExecution(Runnable r, ThreadPoolExecutor executor) 方法。不过 Executors 框架已经为我们实现了 4 种拒绝策略:

    1. AbortPolicy(默认):丢弃任务并抛出 RejectedExecutionException 异常。
    2. CallerRunsPolicy:由调用线程处理该任务。
    3. DiscardPolicy:丢弃任务,但是不抛出异常。可以配合这种模式进行自定义的处理方式。
    4. DiscardOldestPolicy:丢弃队列最早的未处理任务,然后重新尝试执行任务。

    5 功能线程池

    嫌上面使用线程池的方法太麻烦?其实Executors已经为我们封装好了 4 种常见的功能线程池,如下:

    • 定长线程池(FixedThreadPool)
    • 定时线程池(ScheduledThreadPool )
    • 可缓存线程池(CachedThreadPool)
    • 单线程化线程池(SingleThreadExecutor)

    5.1 定长线程池(FixedThreadPool)

    创建方法的源码:

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }
    • 特点:只有核心线程,线程数量固定,执行完立即回收,任务队列为链表结构的有界队列。
    • 应用场景:控制线程最大并发数。

    使用示例:

    // 1. 创建定长线程池对象 & 设置线程池线程数量固定为3
    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
    // 2. 创建好Runnable类线程对象 & 需执行的任务
    Runnable task =new Runnable(){
      public void run() {
         System.out.println("执行任务啦");
      }
    };
    // 3. 向线程池提交任务
    fixedThreadPool.execute(task);

    5.2 定时线程池(ScheduledThreadPool )

    创建方法的源码:

    private static final long DEFAULT_KEEPALIVE_MILLIS = 10L;
    
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue());
    }
    
    public static ScheduledExecutorService newScheduledThreadPool(
            int corePoolSize, ThreadFactory threadFactory) {
        return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
    }
    public ScheduledThreadPoolExecutor(int corePoolSize,
                                       ThreadFactory threadFactory) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue(), threadFactory);
    }
    • 特点:核心线程数量固定,非核心线程数量无限,执行完闲置 10ms 后回收,任务队列为延时阻塞队列。
    • 应用场景:执行定时或周期性的任务。

    使用示例:

    // 1. 创建 定时线程池对象 & 设置线程池线程数量固定为5
    ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
    // 2. 创建好Runnable类线程对象 & 需执行的任务
    Runnable task =new Runnable(){
      public void run() {
         System.out.println("执行任务啦");
      }
    };
    // 3. 向线程池提交任务
    scheduledThreadPool.schedule(task, 1, TimeUnit.SECONDS); // 延迟1s后执行任务
    scheduledThreadPool.scheduleAtFixedRate(task,10,1000,TimeUnit.MILLISECONDS);// 延迟10ms后、每隔1000ms执行任务

    5.3 可缓存线程池(CachedThreadPool)

    创建方法的源码:

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
    public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>(),
                                      threadFactory);
    }
    • 特点:无核心线程,非核心线程数量无限,执行完闲置 60s 后回收,任务队列为不存储元素的阻塞队列。
    • 应用场景:执行大量、耗时少的任务。

    使用示例:

    // 1. 创建可缓存线程池对象
    ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    // 2. 创建好Runnable类线程对象 & 需执行的任务
    Runnable task =new Runnable(){
      public void run() {
         System.out.println("执行任务啦");
      }
    };
    // 3. 向线程池提交任务
    cachedThreadPool.execute(task);

    5.4 单线程化线程池(SingleThreadExecutor)

    创建方法的源码:

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>(),
                                    threadFactory));
    }
    • 特点:只有 1 个核心线程,无非核心线程,执行完立即回收,任务队列为链表结构的有界队列。
    • 应用场景:不适合并发但可能引起 IO 阻塞性及影响 UI 线程响应的操作,如数据库操作、文件操作等。

    使用示例:

    // 1. 创建单线程化线程池
    ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
    // 2. 创建好Runnable类线程对象 & 需执行的任务
    Runnable task =new Runnable(){
      public void run() {
         System.out.println("执行任务啦");
      }
    };
    // 3. 向线程池提交任务
    singleThreadExecutor.execute(task);

    5.5 对比

    6 总结

    Executors 的 4 个功能线程池虽然方便,但现在已经不建议使用了,而是建议直接通过使用 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

    其实 Executors 的 4 个功能线程有如下弊端:

    • FixedThreadPool 和 SingleThreadExecutor:主要问题是堆积的请求处理队列均采用 LinkedBlockingQueue,可能会耗费非常大的内存,甚至 OOM。
    • CachedThreadPool 和 ScheduledThreadPool:主要问题是线程数最大数是 Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至 OOM。

     

    参考

     

    展开全文
  • java 多线程和线程池

    千次阅读 2016-06-23 00:01:47
    多线程的概念很好理解就是多条线程同时存在,但要用好多线程确不容易,涉及到多线程间通信,多线程共用一个资源等诸多问题。 使用多线程的优缺点: 优点: 1)适当的提高程序的执行效率(多个线程同时执行)。 2)...
  • Java多线程+线程池.docx

    2021-03-31 15:47:14
    JAVA多线程 线程的动态执行图解
  • 在当前的Java面试后台开发中,多线程线程池技术越来越重要。毫不夸张的说,如何你想进入任何一家好的网络公司并能够长足发展,多线程线程池技术是必须要掌握的技能!
  • Java多线程线程池深入分析(上)

    千次阅读 2019-03-21 08:55:31
    线程池是并发包里面很重要的一部分,在实际情况中也是使用很的一个重要组件。 下图描述的是线程池API的一部分。广义上的完整线程池可能还包括Thread/Runnable、Timer/TimerTask等部分。这里只介绍主要的高级...
  • 一、线程池定义使用 jdk 1.5 之后就引入了线程池。 1.1 定义 从上面的空间切换看得出来,线程是稀缺资源,它的创建与销毁是一个相对偏重且耗资源的操作,而Java线程依赖于内核线程,创建线程需要进行操作系统状态...
  •  在多线程开发中,会因为线程数量多,而且很多时侯,每个线程仅仅执行一小段时间就会结束,所以我们需要频繁的创建线程,创建线程也是会消耗系统资源的,当需求很大时,有可能导致系统负载过重而直接崩溃掉....
  • 实用标准文案 Java 线程池基础详解 对项目进行重构 目标是提高吞吐量可用性 在这个过程中对原有的线程模型 处理逻辑进行了修改 发现有很多基础的多线程的知识已经模糊不清 如底层 线程的运行情况现有的线程池的...
  • 线程可相互通信,可任意调度任务,类似Akka的Actor。如果内容对您有帮助,微信扫描下方的二维码支持下作者,谢谢!
  • Java多线程线程池配置合理线程数

    千次阅读 2019-05-30 20:11:52
    Java多线程线程池配置合理线程数 目录 代码查看公司服务器或阿里云是几核的 合理线程数配置之CPU密集型 合理线程数配置之IO密集型 1. 代码查看公司服务器或阿里云是几核的 要合理配置线程数首先要知道公司...
  • java多线程线程池使用例子

    千次阅读 2019-04-03 20:22:21
    1、今天在项目中使用了线程池,在这里简单的记录一下,一面以后回忘记。话不说直接上代码。 package com.proven.thread; import java.util.concurrent.ExecutorService; import java.util.concurrent....
  • 主要为大家详细介绍了java简单实现多线程,及java爬虫使用线程池实例,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • Java多线程使用线程池实现文件下载

    千次阅读 2018-11-21 11:08:35
    多线程下载原理: 1、基本思路是将文件分段切割、分段传输、分段保存。 2、分段切割用到HttpUrlConnection对象的setRequestProperty(“Range”, “bytes=” + start + “-” + end)方法。 3、分段传输用到...
  • java多线程,对多线程,线程池进行封装,方便使用
  • Java 线程和线程池区别

    万次阅读 2017-01-15 14:45:23
    java默认创建的线程都是属于系统线程组,而同一个线程组的线程是可以相互修改对方的数据的。 但如果在不同的线程组中,那么就不能“跨线程组”修改数据,可以从一定程度上保证数据安全. 线程池线程池存在的意义...
  • 主要介绍了Java多线程实现同时打印的相关资料,需要的朋友可以参考下
  • 多线程技术可以解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力; 什么时候使用线程池 需要大量的线程来完成任务,且完成任务的时间比较短,如WEB服
  • Java多线程线程池面试常见问题

    千次阅读 2020-01-07 16:04:21
    文章目录为什么要使用线程池线程池的工作原理常用的线程池线程池的核心参数线程池的拒绝策略线程池中的常用任务队列如何终止线程池高并发、任务执行时间短的业务怎样使用线程池?并发不高、任务执行时间长的业务...
  • Java多线程线程池使用 前言 学习使用线程池,而不是每次用线程的时候手动去创建,然后再进行销毁,浪费系统资源。 对此线程池应运而生,将一些线程进行复用,循环使用,等下一次业务请求时候,提前创建好的线程对...
  • 文章目录单个线程固定线程创建线程池 单个线程 package threadPool; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @title: SingleThreadPollDemo * @project...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 206,794
精华内容 82,717
关键字:

java多线程和线程池

java 订阅