精华内容
下载资源
问答
  • 线程、多线程和线程池面试专题.线程、多线程和线程池面试专题.线程、多线程和线程池面试专题.线程、多线程和线程池面试专题.
  • 在当前的Java面试后台开发中,多线程线程池技术越来越重要。毫不夸张的说,如何你想进入任何一家好的网络公司并能够长足发展,多线程线程池技术是必须要掌握的技能!
  • Java多线程和线程池

    2018-02-06 16:06:09
    在实际使用中,服务器在创建销毁线程上花费的时间消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间资源要多的多。 除了创建销毁线程的开销之外,活动的线程也需要消耗系统资源。...

    点击上方“Java知音”,选择“置顶公众号”

    技术文章第一时间送达!

    1.为什么要使用线程池

    在java中,如果每个请求到达就创建一个新线程,开销是相当大的。在实际使用中,服务器在创建和销毁线程上花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。


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


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


    2.线程池的组成部分

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


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

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


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


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

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


    一、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)方法欲添加到线程池时:

    • 如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。

    • 如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。

    • 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。

    • 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。也就是:处理任务的优先级为:核心线程corePoolSize、任务队列workQueue、最大线程 maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。

    • 当线程池中的线程数量大于 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的描述上可以看出,任务队列的容量会影响到线程的变化,因此任务队列的长度也需要恰当的设置。


    线程池按以下行为执行任务

    1. 当线程数小于核心线程数时,创建线程。

    2. 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。

    3. 当线程数大于等于核心线程数,且任务队列已满

    4. 若线程数小于最大线程数,创建线程

    5. 若线程数等于最大线程数,抛出异常,拒绝任务


    系统负载

    参数的设置跟系统的负载有直接的关系,下面为系统负载的相关参数:

    1. tasks:每秒需要处理的最大任务数量

    2. tasktime:处理第个任务所需要的时间

    3. responsetime:系统允许任务最大的响应时间,比如每个任务的响应时间不得超过2秒。


    参数设置


    corePoolSize:

    每个任务需要tasktime秒处理,则每个线程每钞可处理1/tasktime个任务。系统每秒有tasks个任务需要处理,则需要的线程数为:tasks/(1/tasktime),即tasks*tasktime个线程数。假设系统每秒任务数为100~1000,每个任务耗时0.1秒,则需要100*0.1至1000*0.1,即10~100个线程。那么corePoolSize应该设置为大于10,具体数字最好根据8020原则,即80%情况下系统每秒任务数,若系统80%的情况下第秒任务数小于200,最多时为1000,则corePoolSize可设置为20。


    queueCapacity:

    任务队列的长度要根据核心线程数,以及系统对任务响应时间的要求有关。队列长度可以设置为(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设置为60。


    keepAliveTime:

    线程数量只增加不减少也不行。当负载降低时,可减少线程数量,如果一个线程空闲时间达到keepAliveTiime,该线程就退出。默认情况下线程池最少会保持corePoolSize个线程。


    allowCoreThreadTimeout:

    默认情况下核心线程不会退出,可通过将该参数设置为true,让核心线程也退出。


    推荐

    技术文章:Java中的设计模式

    源码分享:Javaweb练手项目下载


    • 来源:CSDN

    • 原文:http://blog.csdn.net/u013142781/article/details/51387749



    展开全文
  • 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是一个类;可以只等待一定的时间或者能够响应中断;读操作之间不互斥,可以同时进行;需要手动释放锁

     

    展开全文
  • fixThreadPool 正规线程(传统线程池)cacheThreadPool 缓存线程池singleThreadPoll 单线程线程池(单例线程池)ScheduledThreadPoll 周期性执行任务的线程池 fixThreadPool 正规线程(传统线程池) 含有核心线程...

    线程池的概念

    线程池大类总共分为4种

    • fixThreadPool 正规线程(传统线程池)
    • cacheThreadPool 缓存线程池
    • singleThreadPoll 单线程线程池(单例线程池)
    • ScheduledThreadPoll 周期性执行任务的线程池
    fixThreadPool 正规线程(传统线程池)

    含有核心线程,核心线程即为最大线程数量,没有非核心线程

    cacheThreadPool 缓存线程池

    可缓存线程池,最大线程数很大,它会为每一个任务添加一个新的线程,这边有一个超时机制,当空闲的线程超过一定时间内没有用到的话,就会被回收。

    singleThreadPoll 单线程线程池(单例线程池)

    只存在一个线程,通过指定的顺序将任务一个个丢到线程,排队等待执行,不处理并发的操作,不会被回收。

    ScheduledThreadPoll 周期性执行任务的线程池

    周期性执行任务的线程池,按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程,非核心线程的大小也为无限大。适用于执行周期性的任务。

    线程池与多线程的区别

    • 线程池是在程序运行开始,创建好的n个线程,并且这n个线程挂起等待任务的到来。而多线程是在任务到来得时候进行创建,然后执行任务。
    • 线程池中的线程执行完之后不会回收线程,会继续将线程放在等待队列中;多线程程序在每次任务完成之后会回收该线程。
    • 由于线程池中线程是创建好的,所以在效率上相对于多线程会高很多。
    • 线程池也在高并发的情况下有着较好的性能;不容易挂掉。多线程在创建线程数较多的情况下,很容易挂掉。
    展开全文
  • java多线程,对多线程线程池进行封装,方便使用
  • 主要介绍了C#多线程ThreadPool线程池的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • C# Window编程随记——多线程和线程池在进行C#编程时,我们常常遇到一些任务量很大的情况,例如在读取多个表格的数据,并且要创建多个文件来保存解析之后的数据时,假如只是用简单的for循环来完成的话,会有明显的...

    C# Window编程随记——多线程和线程池

    在进行C#编程时,我们常常遇到一些任务量很大的情况,例如在读取多个表格的数据,并且要创建多个文件来保存解析之后的数据时,假如只是用简单的for循环来完成的话,会有明显的延迟现象,当数据量比较大时,甚至可能要等上好几分钟。为了提高任务的完成效率,我们需要适当地引入多个线程来并行地执行任务。

    • 多线程的概念
    • 线程池的使用

    2.多线程的概念

    多线程是程序设计的逻辑层概念,它是进程中并发运行的一段代码。多线程可以实现线程间的切换执行。


    3.线程池的使用

    一个线程池是一个可以完成一系列当前环境下任务的线程的集合,.NET Framework提供了包含ThreadPool类的System.Threading 空间,这是一个可直接访问的静态类,该类对线程池是必不可少的。
    ThreadPool.SetMaxThreads 方法

    public static bool SetMaxThreads(
        int workerThreads,
        int completionPortThreads
    )

    参数:
    workerThreads
    类型:System.Int32
    线程池中辅助线程的最大数目。
    completionPortThreads
    类型:System.Int32
    线程池中异步 I/O 线程的最大数目。

    一下方法可将任务添加进线程池:

    ThreadPool.QueueUserWorkItem(new WaitCallback(方法名));

    重载

    ThreadPool.QueueUserWorkItem(new WaitCallback(方法名), 参数);

    创建一个新的类,在里面重写一个方法,方法名可以自定义,但是返回值必须为void,而且传入形参必须是一个object类型的,这是线程池回调方法的一个规定:

    public class thr
    {
        /// <summary>
        /// 解析和生成文件
        /// </summary>
        /// <returns></returns>
        public void AnalyseAndBuildFiles(Object data)
        {
             //根据具体需求将参数object转换为指定类型
             string config_strs = data as string;
        }
    }

    创建完这个类之后,接下来我们就要看看怎么在我们的业务中来使用线程池了,方法很简单:创建一个类的实体,然后调用实体的方法作为线程池的回调方法即可,QueueUserWorkItem方法的第二个参数,就是传递给回调函数的object类型的参数:

    thr t = new thr();
    ThreadPool.QueueUserWorkItem(new WaitCallback(t.AnalyseAndBuildFiles), excel_config_list[i]);

    展开全文
  • 多线程编程线程池

    2018-07-06 15:21:40
    创建多线程操作是非常昂贵的,如果每个运行时间非常短的操作,都创建多线程进行操作,可能并不能提高效率,反而降低... 如果你有非常多的执行时间非常短的操作,那么适合作用线程池来提高效率,而不是自行创建多线程
  • 本程序详细介绍了线程和线程池的用法,使用多线程进行异步编程实现数据库操作日志的记录
  • java 多线程和线程池

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

    万次阅读 多人点赞 2018-04-19 00:51:36
    关于线程和线程池的学习,我们可以从以下几个方面入手:第一,什么是线程,线程进程的区别是什么第二,线程中的基本概念,线程的生命周期第三,单线程和多线程第四,线程池的原理解析第五,常见的几种线程池的特点...
  • 深入理解多线程和线程池

    千次阅读 2019-04-22 00:57:50
    1、什么是线程? 在理解线程前,要先理解什么是进程; 【1】那么,什么是进程呢? 进程是指运行中的应用程序,每个进程都有自己独立的地址空间(内存...目前操作系统都支持进程。 要点,用户每启动一个进程,...
  • c#多线程以及线程池的学习 c#多线程以及线程池的学习 c#多线程以及线程池的学习
  • 多线程 线程池 线程同步--DEMO】 软件平台:Visual Studio 2008 开发语言:C# 引用系统类:System.Threading System.Threading.ThreadPool 模拟多线程以及线程池的使用,对概念理解很有帮助的。
  • Python 多线程-线程池

    千次阅读 2020-08-06 14:35:35
    文章目录Python 多线程-线程池2.线程池 Python 多线程-线程池 在编程中的多任务多线程,有两种方式 1. 来一个任务建一个线程 2. 通过线程池控制 本文给出两种范例 ### 1. 普通方式构建线程 def runinThread(func,...
  • 现在有很多文章对多线程和线程池都有解释,但是大多是比较生涩的专业术语,让初学者很是难懂。本文以一个实际的示例来进行解释,让你很快就了解三者的作用和区别
  • Android 多线程线程池使用 目录Android 多线程线程池使用1:创建线程池2:向线程池提交任务3:关闭线程池shutdown() 1:创建线程池 Executor threadPool = new ThreadPoolExecutor( CORE_POOL_SIZE, ...
  • Java、Android多线程线程池Demo
  • JAVA多线程线程池

    2017-02-21 19:48:29
    JAVA多线程线程池
  • python 多线程线程池

    千次阅读 2019-05-12 14:00:04
    简单的多线程—使用线程池 from multiprocessing import Pool def f(x): # 基本函数返回 x的平方 return x * x def my_callback(x): # 回调函数,打印x print(x) def multicore(): pool = Pool(4) # 创建可容纳...
  • Java多线程-线程池ThreadPoolExecutor构造方法规则

    万次阅读 多人点赞 2017-05-03 17:15:37
    为什么用线程池博客地址 http://blog.csdn.net/qq_25806863原文地址 http://blog.csdn.net/qq_25806863/article/details/71126867有时候,系统需要处理非常的执行时间很短的请求,如果每一个请求都开启一个新线程...
  • Java线程池是运用最多的并发框架,学号多线程以及合理的使用多线程可以带来很大的好处,今天就来一起学习线程池相关的知识吧! 线程池一、线程池的实现原理二、使用线程池1. 使用线程池的好处2.线程池的创建3. 向...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 279,673
精华内容 111,869
关键字:

多线程和线程池区别