精华内容
下载资源
问答
  • 多线程 1. 线程的声明周期 新建 :从新建一个线程对象到程序start() 这个线程之间的状态,都是新建状态; 就绪 :线程对象调用start()方法后,就处于就绪状态,等到JVM里的线程调度器的调度; 运行 :就绪状态下的...

    多线程

    1. 线程的声明周期

    • 新建 :从新建一个线程对象到程序start() 这个线程之间的状态,都是新建状态;
    • 就绪 :线程对象调用start()方法后,就处于就绪状态,等到JVM里的线程调度器的调度;
    • 运行 :就绪状态下的线程在获取CPU资源后就可以执行run(),此时的线程便处于运行状态,运行状态的线程可变为就绪、阻塞及死亡三种状态。
    • 等待/阻塞/睡眠 :在一个线程执行了sleep(睡眠)、suspend(挂起)等方法后会失去所占有的资源,从而进入阻塞状态,在睡眠结束后可重新进入就绪状态。
    • 终止 :run()方法完成后或发生其他终止条件时就会切换到终止状态。

    2. 线程的创建方法

    2.1 继承Thread类

    实现步骤:

    1. 定义类继承Thread
    2. 重写Thread类中的run方法
    3. 调用线程的start方法
    public class ThreadDemo1 {
    
        public static void main(String[] args) {
            
            //创建两个线程
            ThreadDemo td = new ThreadDemo("zhangsan");
            ThreadDemo tt = new ThreadDemo("lisi");
            //执行多线程特有方法,如果使用td.run();也会执行,但会以单线程方式执行。
            td.start();
            tt.start();
            //主线程
            for (int i = 0; i < 5; i++) {
                System.out.println("main" + ":run" + i);
            }
        }
    }
    //继承Thread类
    class ThreadDemo extends Thread{
        
        //设置线程名称
        ThreadDemo(String name){
            super(name);
        }
        //重写run方法。
        public void run(){
            for(int i = 0; i < 5; i++){
           //currentThread()  获取当前线程对象(静态)。  getName() 获取线程名称。
            System.out.println(this.getName() + ":run" + i);  
            }
        }
    }
    

    2.2 实现Runnable接口

    接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为run 的无参方法。

    实现步骤:

    1. 定义类实现Runnable接口
    2. 覆盖Runnable接口中的run方法
    3. 通过Thread类建立线程对象
    4. Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
    5. 调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
    public class RunnableDemo {
        public static void main(String[] args) {
            RunTest rt = new RunTest();
            //建立线程对象
            Thread t1 = new Thread(rt);
            Thread t2 = new Thread(rt);
            //开启线程并调用run方法。
            t1.start();
            t2.start();
        }
    }
    
    //定义类实现Runnable接口
    class RunTest implements Runnable{
        private int tick = 10;
        //覆盖Runnable接口中的run方法,并将线程要运行的代码放在该run方法中。
        public void run(){
            while (true) {
                if(tick > 0){
                    System.out.println(Thread.currentThread().getName() + "..." + tick--);
                }
            }
        }
    }
    

    2.3 通过Callable和Future创建线程

    实现步骤:

    1. 创建Callable接口的实现类,并实现call()方法,改方法将作为线程执行体,且具有返回值。
    2. 创建Callable实现类的实例,使用FutureTask类进行包装Callable对象,FutureTask对象封装了Callable对象的call()方法的返回值
    3. 使用FutureTask对象作为Thread对象启动新线程。
    4. 调用FutureTask对象的get()方法获取子线程执行结束后的返回值。
    public class CallableFutrueTest {
        public static void main(String[] args) {
            CallableTest ct = new CallableTest();                        //创建对象
             //使用FutureTask包装CallableTest对象
            FutureTask<Integer> ft = new FutureTask<Integer>(ct);       
            for(int i = 0; i < 100; i++){
                //输出主线程
                System.out.println(Thread.currentThread().getName() + "主线程的i为:" + i);
                //当主线程执行第30次之后开启子线程
                if(i == 30){        
                    Thread td = new Thread(ft,"子线程");
                    td.start();
                }
            }
            //获取并输出子线程call()方法的返回值
            try {
                System.out.println("子线程的返回值为" + ft.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }
    class CallableTest implements Callable<Integer>{
        //复写call() 方法,call()方法具有返回值
        public Integer call() throws Exception {
            int i = 0;
            for( ; i<100; i++){
                System.out.println(Thread.currentThread().getName() + "的变量值为:" + i);
            }
            return i;
        }
    }
    

    2.3.1 拓展——Future和FutureTask是什么

    从源码看Future是一个接口,文档说得很清楚,里面定义了获得异步计算结果的方法,如图

    FutureTask是一个具体类,继承实现关系如下:

    它实现了Future接口和Runnable接口,其中Runnable接口应该不陌生,它是线程创建的默认接口。而Future接口提供了获取返回值的必要方法。那么,它和Callable接口有什么关系呢?

    直接看源码

    public class FutureTask<V> implements RunnableFuture<V> {   
        /**
         * The run state of this task, initially NEW.  The run state
         * transitions to a terminal state only in methods set,
         * setException, and cancel.  During completion, state may take on
         * transient values of COMPLETING (while outcome is being set) or
         * INTERRUPTING (only while interrupting the runner to satisfy a
         * cancel(true)). Transitions from these intermediate to final
         * states use cheaper ordered/lazy writes because values are unique
         * and cannot be further modified.
         *
         * Possible state transitions:
         * NEW -> COMPLETING -> NORMAL
         * NEW -> COMPLETING -> EXCEPTIONAL
         * NEW -> CANCELLED
         * NEW -> INTERRUPTING -> INTERRUPTED
         */
        private volatile int state;
        private static final int NEW          = 0;
        private static final int COMPLETING   = 1;
        private static final int NORMAL       = 2;
        private static final int EXCEPTIONAL  = 3;
        private static final int CANCELLED    = 4;
        private static final int INTERRUPTING = 5;
        private static final int INTERRUPTED  = 6;
    
        /** The underlying callable; nulled out after running */
        private Callable<V> callable;
       
        //.....省略很多方法  
        
        public void run() {
            if (state != NEW ||
                !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                             null, Thread.currentThread()))
                return;
            try {
                Callable<V> c = callable;
                if (c != null && state == NEW) {
                    V result;
                    boolean ran;
                    try {
                        result = c.call();   //这里返回了结果
                        ran = true;
                    } catch (Throwable ex) {
                        result = null;
                        ran = false;
                        setException(ex);
                    }
                    if (ran)
                        set(result);//这里传送出去
                }
            } finally {
               //....
            }
        }
    }
    

    看到下方private Callable<V> callable;可以直观地了解到FutureTask是通过聚合Callable变量和重写run方法的方式对Runnable进行了增强

    2.4 三种方法的对比

    方法优势劣势
    继承Thread编写简单,可直接用this.getname()获取当前线程,不必使用Thread.currentThread()方法。已经继承了Thread类,无法再继承其他类。
    实现Runnable避免了单继承的局限性、多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。比较复杂、访问线程必须使用Thread.currentThread()方法、无返回值。
    实现Callable有返回值、避免了单继承的局限性、多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。比较复杂、访问线程必须使用Thread.currentThread()方法

    线程池

    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(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。

    线程池的使用流程如下:

    // 创建线程池
    Executor 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: 它是ConcurrentLinkedQueueLinkedBlockingQueueSynchronousQueue的结合体,但是把它用在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 拒绝策略

    当线程池的线程数达到最大线程数时,需要执行拒绝策略。拒绝策略需要实现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 对比

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

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

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

    最佳用例

    public static void main(String[] args) {
            final int[][] testNumber = {{1,100000},{100001,200000},{200001,300000},{300001,400000},{400001,500000},{500001,600000}};
            ThreadPoolExecutor threadPool = (ThreadPoolExecutor)Executors.newFixedThreadPool(6);
            List<Future<Long>> futureList = new ArrayList<>();
            for(int i = 0;i < 6;i++){
                Future<Long> future = threadPool.submit(new CallableTest(testNumber[i][0], testNumber[i][1]));
                futureList.add(future);
            }
    
            Long num = 0L;
            Long startTime =  System.currentTimeMillis();
    
            do {
                for(int i = 0;i < futureList.size();i++){
                    Future<Long> future = futureList.get(i);
                    System.out.printf("Task %d : %s \n",i,future.isDone());
                }
            }while (threadPool.getCompletedTaskCount() < futureList.size());
    
            for (Future<Long> future : futureList) {
                try {
                    num += future.get();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }
    
            Long endTime =  System.currentTimeMillis();
            System.out.println("用时:"+(endTime-startTime)+"ms");
            System.out.println(num);
    
    
            threadPool.shutdown();
    
        }
    

    面试题

    1 调用两次start会发生什么

    首先我们来看源码

    public synchronized void start() {
        /**
             * This method is not invoked for the main method thread or "system"
             * group threads created/set up by the VM. Any new functionality added
             * to this method in the future may have to also be added to the VM.
             *
             * A zero status value corresponds to state "NEW".
             */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
    
        /* Notify the group that this thread is about to be started
             * so that it can be added to the group's list of threads
             * and the group's unstarted count can be decremented. */
        group.add(this);
    
        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                      it will be passed up the call stack */
            }
        }
    }
    

    结论很明显,会抛出IllegalThreadStateException异常,线程创建时,会有一个初始状态status对应创建,就绪,阻塞,等待,运行,终止等不同的状态,如果想重复start,会先去检查这个状态,如果不是初始值,就会报错

    2 直接调用run会发生什么

    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
    

    还是看上面两个的源码,首先看run(),让当前Tjhread直接调用run方法,没有经过start方法,如果有线程池,不能添加到线程池中;

    run()方法只是类中的一个方法,直接调用run()方法,那还是一个主线程,执行完第一个run()在执行下一个调用的run(),是个单线程;
    start()是创建了一个线程,使新线程运行Thread类的run()方法里面的代码,而主线程继续,实现了多线程,

    3 execute和submit的区别

    从源码来看

    //-----------------------------submit
    public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }
    
    public <T> Future<T> submit(Runnable task, T result) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task, result);
        execute(ftask);
        return ftask;
    }
    
    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }
    
    //----------------------------execute
    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        
        //看线程池的工作原理   上面线程池->3.线程池的工作原理
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }
    
    • submit不管是Runnable还是Callable类型的任务都可以接受,而且都返回了Future<T>
    • execute只支持Runnable类型,而且返回值为null
    • 但是他们实际上都调用了execute方法,不同的时submit做了RunnableFuture<T> ftask = newTaskFor(...)方法

    从异常来看

    都会抛出空指针异常,或者根据拒绝策略返回

    4 怎么确认线程数

    • IO密集型:因为阻塞,所以可以两倍甚至更多
    • 计算密集型:最好和cpu线程数相当

    Java 确定线程池中工作线程数的大小

    5 happen-before

    [Chapter 17 of the Java Language Specification]  defines the happens-before relation on memory operations such as reads and writes of shared variables.The results of a write by one thread are guaranteed to be visible to a read by another thread only if the write operation happens-before the read operation. The synchronized and volatile constructs, as well as the Thread.start() and Thread.join() methods, can form happens-before relationships. In particular:
    · Each action in a thread happens-before every action in that thread that comes later in the program's order.
    · An unlock (synchronized block or method exit) of a monitor happens-before every subsequent lock (synchronized block or method entry) of that same monitor. And because the happens-before relation is transitive, all actions of a thread prior to unlocking happen-before all actions subsequent to any thread locking that monitor.
    · A write to a volatile field happens-before every subsequent read of that same field. Writes and reads of volatile fields have similar memory consistency effects as entering and exiting monitors, but do not entail mutual exclusion locking.
    · A call to start on a thread happens-before any action in the started thread.
    · All actions in a thread happen-before any other thread successfully returns from a join on that thread.
    

    渣翻一下:

    happens-before其实有两种不同的解释,一种是多线程情况下对共享变量的内存操作(例如读写)问题说明(请看)(如果写操作发生在读操作前,那么必须保证一个线程的写操作在另一个线程的读操作之前可见);另一种是synchronizedvolatile关键字修饰的区域,还有Thread.start()Thread.join()方法也遵循happens-before的关系

    • 同一个线程中所有的代码都是顺序执行的(ActionA和ActionB属于同一个线程,且ActionA在前,ActionA happens-before ActionB)
    • 对于同一个monitor监视器的对象,它的解锁unlock(包括synchronized修饰的代码块或方法退出)操作happens-before之后的加锁lock(包括synchronized修饰的代码块或方法进入同步区域)操作。而且因为happens-before具有可传递性,对于同一个monitor监视器的对象,线程对它的所有解锁操作优先于其他线程对它进行的加锁操作
    • 对于同一个volatile修饰的变量,在对它进行读操作之前,之前发生的所有写操作必须完成。
    • 线程的start方法优先于线程的其他方法
    • join方法之前,这个线程的所有动作都已经发生,这样其他线程才能成功接收到join的返回

    引用

    Java多线程:彻底搞懂线程池

    展开全文
  • 深入理解多线程和线程池

    千次阅读 2019-04-22 00:57:50
    理解线程前,要先理解什么是进程; 【1】那么,什么是进程呢? 进程是指运行中的应用程序,每个进程都有自己独立的地址空间(内存空间),比如用户点击桌面的IE浏览器,就启动了一个进程,操作系统就会为该进程...

    个人博客网:https://wushaopei.github.io/    (你想要这里多有)

    1、什么是线程?

    在理解线程前,要先理解什么是进程;

    【1】那么,什么是进程呢?

    进程是指运行中的应用程序,每个进程都有自己独立的地址空间(内存空间),比如用户点击桌面的IE浏览器,就启动了一个进程,操作系统就会为该进程分配独立的地址空间。当用户再次点击左面的IE浏览器,又启动了一个进程,操作系统将为新的进程分配新的独立的地址空间。目前操作系统都支持多进程。

    • 要点,用户每启动一个进程,操作系统就会为该进程分配一个独立的内存空间。

    【2】由进程到线程,就比较好理解线程是什么了?

    所谓线程,就是进程中的一个实体,是被系统独立调度和分派的基本单位,线程本身不具有系统资源的调度权,。仅仅拥有一点点用于在系统中运行所需的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。

    一个线程可以创建和撤销其它的线程,同一进程中的多个线程之间可以并发执行。线程有就绪、阻塞和运行 三种基本状态。

                             < - - -阻塞状态 <- - -

                             |                              |

    新建状态 - - - 就绪状态 - - - - - 运行状态 - - - 死亡状态

    【3】关于阻塞状态有一下:

    等待阻塞:执行wait()方法 等待, 由 notify()或notifyAll()唤醒

    同步阻塞:使用同步锁 synchronized 上锁,

    其他阻塞:执行sleep()或join()方法,或者发出了I/O请求时,

    线程的wait阻塞:

    Wait()方法是让线程释放对象锁,让其他线程拿到锁之后去优先执行,当其他线程唤醒wait()中的线程或者拿到对象锁的线程执行完释放了对象锁之后,wait()中的线程才会再次拿到对象锁从而执行。

    线程的sleep阻塞:

    Sleep()方法是让线程睡眠,此时并没有释放对象锁,其他想要拿到睡眠线程的对象锁的线程也就拿不到相应的对象锁,从而不能抢在它前面执行。

    2、多线程的安全问题:

    【1】多个线程之间为什么会出现不安全?

    线程随机性原理,线程会被CPU随机切换,而线程访问的资源如果是堆或者方法区的资源的话,那么每个线程都可以改变这个数据,外加上线程额执行会被CPU随机切换。

    【2】多线程中对同步锁的使用:

    •     解决线程安全问题

    ①使用synchronized同步锁的引入;

    ②使用对象锁(对当前对象进行加锁);

    ③synchronized锁多对象

    【3】多线程案例:

    ① 我锁住了一个方法,当我执行了这个方法,其他方法能够执行这个方法吗?

    ② 我锁的是这个对象呢?

    获得当前对象锁的线程,在调用加锁的方法,不会阻塞;

    调用该对象普通方法(即没有同步的方法),不会阻塞;

    既没有获得锁,调用的又是同步方法或代码块,会被阻塞。

    ③ 在类中有两个对象,我对class加锁,我对class中的一个对象进行访问,其他线程能够访问另一个对象?

    不能,锁住整个class时,锁只能在方法执行后备释放才能执行其他方法或本方法,因为当前class处于被锁定的范围中。

    这里要注意,锁住class和锁住this是不同的,锁住this时类中的其他对象或方法可以被另一条线程使用,而锁住class时是不行的。

    3、什么是线程池?怎么使用?

    【1】线程池

    线程池是一种多线程处理方法,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程,每个线程使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程处于空闲状态,线程池将会调度一个任务给它。如果所有线程都始终保持繁忙,但将任务放入到一个队列中,则线程池将在过一段时间后创建另一个辅助线程,但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动

    【2】线程池的工作原理:

    引用大神的文章   https://www.cnblogs.com/easycloud/p/3726089.html

    【3】线程池的参数有哪些?

    1. corePoolSize:核心线程数
    2. queueCapacity:任务队列容量(阻塞队列)
    3. maxPoolSize:最大线程数
    4. keepAliveTime:线程空闲时间
    5. allowCoreThreadTimeout:允许核心线程超时
    6. rejectedExecutionHandler:任务拒绝处理器

    队列:

    线程池的队列长度是无限大的,但是在实际生产环境下,当请求速度远大于处理速度是,队列的无限加入会造成资源耗尽,服务宕掉。

    线程被释放后会被杀掉?

    不会,会回到池子中,等待线程池的再次调度

    知道线程数达到最大值后,会发生什么事吗?

    剩下的请求任务会进入等待队列

    【4】线程池在实际开发项目中的使用:

    ① 在执行消息入库的时候需要线程池,使用通用线程池,用于异步执行写操作不影响主程序

    https://www.cnblogs.com/xinxindiandeng/p/6383311.html

    ② 在上传图片的时候,后台服务器是不支持多张图片上传的,利用线程池,可以进行多张图片上传,从而减少上传的时间。

    https://blog.csdn.net/androidstarjack/article/details/81124183

    【5】常用的线程池有4种:

    1. newCachedThreadPool —— 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
    2. newFixedThreadPool —— 创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。
    3. newSingleThreadExecutor —— 创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
    4. newScheduleThreadPool —— 创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。

    https://www.cnblogs.com/aaron911/p/6213808.html     (线程池案例链接地址)

    4、 线程池中的数据库连接池,它有什么好处?

    数据库连接池负责分配,管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。

    原因: 数据库连接是一种关键的有限的昂贵的资源,这一点在多用户的网页应用程序中体现的尤为突出,对数据库连接的管理显著影响到整个应用程序的伸缩性和健壮性,影响到程序的性能指标。

    展开全文
  • 话不说,直接上代码,要说的都在代码注释里面: public class ThreadPools { //TODO:ThreadPoolExecutor的执行规则—— //1.如果线程池中的线程数量未达到核心线程的数量,那么直接启动一个核心线程; //2...

    话不多说,直接上代码,要说的都在代码注释里面:

    public class ThreadPools {
    	
    	//TODO:ThreadPoolExecutor的执行规则——
    		//1.如果线程池中的线程数量未达到核心线程的数量,那么直接启动一个核心线程;
    		//2.如果线程池中的线程数量已经达到或者超过核心线程的数量,那么任务会被插入到任务队列中排队等待执行。
    		//3.如果在步骤2中无法将任务插入到任务队列中,这往往是因为任务队列已满,这时如果线程池先吃数量未达到线程池规定的最大值,会立即启动一个非核心线程来执行任务。
    		//4.如果步骤3中的线程数量已经达到线程池规定的最大值,那么就拒绝执行此任务,ThreadPoolExecutor会调用RejectedExecutionHandler的rejectedExecution方法来通知调用者。
    
    
    	public static void main(String[] args) {
    		ExecutorService service = getCachedThreadPool(new Runnable() {
    			@Override
    			public void run() {
    				System.out.println("风满楼,卷黄沙,舞剑春秋,名震天下;雨缥缈,眷红尘,还君明珠,秋水浮萍,");
    			}
    		});
    		//一定要在合适的地方调用shutdown()方法,以避免内存泄漏。
    		//TODO:shutdown()关闭线程,调用这个方法的时候,线程不一定会立即关闭,因为可能任务还没有执行完毕。
    		//TODO:shutdownNow()关闭线程,调用这个方法的时候,直接关闭,不管还有没有任务执行。
    		//TODO:isShutdown(),只要调用了shutdown()方法,就会返回true;
    		//TODO:isTerminated(),任务执行完成的后,isTerminated()方法才会返回true;
    		//TODO:所以采用一个while死循环+判断isTerminated()的方法来确定任务是否执行完成。
    		service.shutdown();
    		System.out.println(service.isShutdown());
    		System.out.println(service.isTerminated());
    	}
    
        /**
         * 创建一个根据需要创建新线程的线程池,它没有核心线程,对线程池大小没有做限制,理论上可以有无线多个,实际上线程池大小完全依赖于JVM
         * 如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,
         * 当任务数增加时,此线程池又可以智能的添加新线程来处理任务。
         * @param runnable
         */
        public static ExecutorService getCachedThreadPool(Runnable runnable){
            ExecutorService service = Executors.newCachedThreadPool();
            service.execute(runnable);
            return service;
        }
    
        /**
         * 创建一个单线程的线程池
         * @param runnable
         */
        public static ExecutorService getSingleThreadPool(Runnable runnable){
            ExecutorService service = Executors.newSingleThreadExecutor();
            service.execute(runnable);
            return service;
        }
    
        static ExecutorService newFixedThreadPoolService = null;
    
        /**
         * 创建一个可重用的固定长度的线程池,控制线程最大并发数,超出的线程会在队列中等待。
         * @param nThreads:线程池长度
         * @param runnable:线程
         */
        public synchronized static ExecutorService getNewFixedThreadPool(int nThreads, Runnable runnable){
            if (null == newFixedThreadPoolService){
                newFixedThreadPoolService = Executors.newFixedThreadPool(nThreads);
            }
            newFixedThreadPoolService.submit(runnable);
            return newFixedThreadPoolService;
        }
    
        static ExecutorService newScheduledThreadPoolService = null;
    
        /**
         * 创建一个定长线程池,支持定时及周期性任务执行。
         * @param nThreads:线程池可缓存线程的数量
         * @param runnable:线程
         */
        public synchronized static ExecutorService  getScheduledThreadPool(int nThreads, Runnable runnable){
            if (newScheduledThreadPoolService == null){
                newScheduledThreadPoolService = Executors.newScheduledThreadPool(nThreads);
            }
            newScheduledThreadPoolService.submit(runnable);
            return newScheduledThreadPoolService;
        }
        
        static ScheduledExecutorService mScheduledExecutorServiceFixedRate;
        /**同getScheduledThreadPoolExecutor()方法*/
        public synchronized static ScheduledExecutorService getScheduledExecutorServiceFixedRate(
                        int nThreads, Runnable runnable, long initialDelay, long period, TimeUnit unit){
            if (mScheduledExecutorServiceFixedRate == null){
                mScheduledExecutorServiceFixedRate = Executors.newScheduledThreadPool(nThreads);
            }
            mScheduledExecutorServiceFixedRate.scheduleAtFixedRate(runnable, initialDelay, period, unit);
            return mScheduledExecutorServiceFixedRate;
        }
    
        static ScheduledExecutorService mScheduledExecutorServiceFixedDelay;
        /**同getScheduledThreadPoolExecutorWithFixedDelay()方法*/
        public synchronized static ScheduledExecutorService getScheduledExecutorServiceFixedDelay(
                            int nThreads, Runnable runnable, long initialDelay, long period, TimeUnit unit){
            if (mScheduledExecutorServiceFixedDelay == null){
                mScheduledExecutorServiceFixedDelay = Executors.newScheduledThreadPool(nThreads);
            }
            mScheduledExecutorServiceFixedDelay.scheduleWithFixedDelay(runnable, initialDelay, period, unit);
            return mScheduledExecutorServiceFixedDelay;
        }
    
    
    
    
    //TODO:ScheduledThreadPoolExecutor创建线程池。它使用的工作队列是DelayedWorkQueue,DelayedWorkQueue是一个无界队列;我们仅仅需要指定其核心线程数量
    
        static ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = null;
        /**
         * 创建一个可缓存并且可以周期性执行任务的线程池,优先保证任务执行的频率。
         * 该方法在initialDelay时长后第一次执行任务,以后每隔period时长,再次执行任务。
         * 如果上一个任务执行完毕,则当前任务立即执行,如果上一个任务没有执行完毕,则需要等上一个任务执行完毕后立即执行。
         * @param nThreads:线程池可缓存线程的数量
         * @param runnable:线程
         * @param initialDelay:延迟执行的时间
         * @param period:每次任务执行的时间间隔
         * @param unit:时间单位——S,ms,min,h......
         */
        public synchronized static ScheduledThreadPoolExecutor getScheduledThreadPoolExecutor(
        				int nThreads, Runnable runnable, long initialDelay, long period,TimeUnit unit){
            if (null == scheduledThreadPoolExecutor){
                scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(nThreads);
            }
            // scheduleAtFixedRate:该方法在initialDelay时长后第一次执行任务,以后每隔period时长,再次执行任务。
            // 注意,period是从任务开始执行算起的。开始执行任务后,定时器每隔period时长检查该任务是否完成,
            // 如果完成则再次启动任务,否则等该任务结束后才再次启动任务。
            scheduledThreadPoolExecutor.scheduleAtFixedRate(runnable, initialDelay, period, unit);
            return scheduledThreadPoolExecutor;
        }
    
        static ScheduledThreadPoolExecutor scheduleWithFixedDelay;
    
        /**
         * 创建一个可缓存并且可以周期性执行任务的线程池,优先保证任务执行的间隔。
         * 任务在在initialDelay时长后第一次执行任务,以后每当任务执行完成后,等待delay时长,再次执行任务。
         * 如果任务执行过程中抛出了异常,那么过ScheduledExecutorService就会停止执行任务,且也不会再周期地执行该任务了。
         * 所以你如果想保住任务都一直被周期执行,那么catch一切可能的异常。
         * @param nThreads:线程池可缓存线程的数量
         * @param runnable:线程
         * @param initialDelay:延迟执行的时间
         * @param period:每次任务执行的时间间隔
         * @param unit:时间单位——S,ms,min,h......
         */
        public synchronized static ScheduledThreadPoolExecutor getScheduledThreadPoolExecutorWithFixedDelay(
        						int nThreads, Runnable runnable, long initialDelay, long period, TimeUnit unit){
            if (null == scheduleWithFixedDelay){
                scheduleWithFixedDelay = new ScheduledThreadPoolExecutor(nThreads);
            }
            // 在initialDelay时长后第一次执行任务,以后每当任务执行完成后,等待delay时长,再次执行任务。
            scheduleWithFixedDelay.scheduleWithFixedDelay(runnable, initialDelay, period, unit);
            return scheduleWithFixedDelay;
        }
    
        static ScheduledThreadPoolExecutor scheduleService = null;
        /**
         * 创建一个可缓存并且可以周期性执行任务的线程池,任务在延迟delay时长后执行,只执行一次。
         * @param nThreads:线程池可缓存线程的数量
         * @param runnable:线程
         * @param delay:延迟执行的时间
         * @param unit:时间单位——S,ms,min,h......
         */
        public synchronized static ScheduledThreadPoolExecutor getScheduledThreadPoolExecutor(
        							int nThreads, Runnable runnable, long delay, TimeUnit unit){
            if (null == scheduleService){
                scheduleService = new ScheduledThreadPoolExecutor(nThreads);
            }
            // schedule方法调度的任务在delay时长的延迟后只执行一次。
            scheduleService.schedule(runnable, delay, unit);
            return scheduleService;
        }
    
        static ScheduledThreadPoolExecutor scheduleServiceExecute = null;
    
        /**
         * 创建一个可缓存的线程池
         * @param nThreads:线程池可缓存线程的数量
         * @param runnable:线程
         */
        public synchronized static ScheduledThreadPoolExecutor getScheduledThreadPoolExecutorExecute(int nThreads, Runnable runnable){
            if (null == scheduleServiceExecute){
                scheduleServiceExecute = new ScheduledThreadPoolExecutor(nThreads);
            }
            // execute方法调度的任务异步执行。
            scheduleServiceExecute.execute(runnable);
            return scheduleServiceExecute;
        }
    }
    

    执行结果:
    在这里插入图片描述

    展开全文
  • 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
    展开全文
  • 文章目录多线程和线程池的c++实现1. linux pthread库中线程的操作1.1 线程的创建资源回收1.2 线程的互斥同步2. 生产者-消费者的多线程模型3. 线程池3.1 为什么需要线程池?3.2 线程池需要解决什么技术问题?3.3 ...
  • 深入理解线程和线程池(图文详解)

    万次阅读 多人点赞 2018-04-19 00:51:36
    关于线程和线程池的学习,我们可以从以下几个方面入手:第一,什么是线程,线程进程的区别是什么第二,线程中的基本概念,线程的生命周期第三,单线程和多线程第四,线程池的原理解析第五,常见的几种线程池的特点...
  • 而在日常开发中,内存资源是及其宝贵的,所以,我们这里就有了本篇文章QT 多线程线程池QThreadPool。在程序逻辑中经常会碰到需要处理大批量任务的情况,比如密集的网络请求,或者日志分析等等。一般会创建一个队列...
  • 线程、多线程线程池总结

    千次阅读 2016-10-07 09:46:36
    多线程的运行是根据CPU切换完成,如何切换由CPU决定,因此多线程运行具有不确定性。 线程池:基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处
  • 为什么要使用多线程、线程锁、互斥量、信号量? 为什么需要线程同步? 什么是线程池? 本文一文详解多线程。 本文作者原创,转载请附上文章出处与本文链接。 目录 1. 线程进程的区别 1.1 进程 : 1.2 线程...
  • 线程是进程内的一个执行单元,进程至少有一个线程,多线程共享进程的地址空间,而进程有自己独立的地址空间。 操作系统以进程为单位分配资源,同一个进程内的线程,共享进程的资源。 线程是处理器调度的基本单位,但...
  • 线程池就是一个线程缓存,负责对线程进行统一分配、调优与监控。(数据库连接池也是一样的道理) 什么时候使用线程池? 单个任务处理时间比较短;需要处理的任务数量很大。 线程池优势? 重用存在的线程,减少线程...
  • 多线程和异步调用之前一直不理解有什么区别,发现,这两个是一件事情的不同角度,多线程是方法,异步是目的 在springboot 可以通过注解@Async 搞定。 线程池线程池引入的目的是为了解决:多次使用线程意味着,...
  • 目录 1,线程池相关的类接口(类)及其关系 ... ThreadPoolExecutor作为线程池的主要实现类,在线程池的创建使用中都起到了很大的作用,ThreadPoolExecutor的构造方法如下: public Threa...
  • Java线程池是运用最多的并发框架,学号多线程以及合理的使用多线程可以带来很大的好处,今天就来一起学习线程池相关的知识吧! 线程池一、线程池的实现原理二、使用线程池1. 使用线程池的好处2.线程池的创建3. 向...
  • Java多线程线程池深入分析(上)

    千次阅读 2019-03-21 08:55:31
    线程池是并发包里面很重要的一部分,在实际情况中也是使用很的一个重要组件。 下图描述的是线程池API的一部分。广义上的完整线程池可能还包括Thread/Runnable、Timer/TimerTask等部分。这里只介绍主要的高级...
  • 带你通俗易懂的理解——线程、多线程线程池.pdf
  • 存放线程对象的资源池 用于提高启动线程的性能(类比连接池) 当程序中涉及创建大量生存期很短的线程,推荐使用线程池。 内置线程池 SingleThreadPool:创建一个单线程化的线程池,只有唯一一个线程来...
  • Java多线程线程池浅析

    千次阅读 2021-03-15 01:00:36
    第一种是集成Thread类,第二种是实现Runable接口(无返回值、不抛异常),第三种是实现Callable接口(有返回值、抛异常),第四种是通过线程池实现,本文主要介绍通过线程池的方式实现多线程。一、为什么要使用线程池、...
  • 我们在学习JAVA或者面试过程中,往往会碰到进程、 线程线程池的之间的错综关系,下面我结合网上的资料自己的理解,总结了三者的关系,从以下几个方面说起: 1、进程、线程线程池的概念  进程是一个动态的...
  • 通过线程池,可以在项目初始化时就创建一个线程集合,然后在需要执行新任务时重用这些线程而不是每次都新建一个线程,一旦任务已经完成了,线程回到线程池中并等待下一次分配任务。 二、创建线程池: 1、通过...
  • 线程池为线程生命周期开销问题资源不足问题提供了解决方案,因为线程是稀缺资源,如果被无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,合理的使用线程池对线程进行统一分配、调优监控; 多线程技术...
  • 线程池的实现原理 ...创建系统开销很大,使用线程池可以避免重复的开销 方便复用,提高相应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。 提高线程的可管理性。线程是稀缺资源
  • Java多线程——线程池原理

    千次阅读 2018-02-28 07:29:48
    合理地使用线程池对线程进行统一分配、调优监控,有以下好处: 降低资源消耗 提高响应速度 提高线程的可管理性 Java1.5引入的Executor框架把任务的提交执行进行解耦,只需要定义好任务,然后提交给线程池。...
  • 1.开启线程的三中方式?https://blog.csdn.net/longshengguoji/article/details/41126119(转)...线程和进程的区别?https://blog.csdn.net/mxsgoden/article/details/882193...
  • 线程池由任务队列工作线程组成,它可以重用线程来避免线程创建的开销,在任务过多时通过排队避免创建过多线程来减少系统资源消耗竞争,确保任务有序完成;ThreadPoolExecutor 继承自 AbstractExecutorService ...
  • 多线程线程池

    千次阅读 2018-04-30 11:16:20
    前言: 目前进入的多线程的深入学习,这次还是在学习进阶之光的一个阶段 一、什么是进程?什么是线程?   相信这是许多刚学多线程的人会被问傻的一个问题,明明自己在java se的时候是学过了java多线程编写,...
  • 据博主的理解多线程就是该应用的主线程任命其他多个线程去协助它完成需要的功能,并且主线程协助线程是完全独立进行的。不知道这样说好不好理解,后面慢慢在使用中会有更加详细的讲解。 2、多线程的使用: (1)...
  • 什么是线程,什么是多线程多线程如何使用? 二、解析 众所周知,实现多线程有两种方式,一个是继承Thread类,另一个是实现Runnable接口; 请见代码: 1.线程; ①、代码 public static void main(String[] args) ...
  • Android线程线程池

    千次阅读 2017-06-02 11:23:07
    那么我们考虑一个问题,我如果需要同时做很事情,是不是给每一个事件都开启一个线程呢?那如果我的事件无限呢?频繁地创建/销毁线程,CPU该吃不消了吧。所以,这时候线程池的概念就来了。我们举个例子来阐述一下...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 115,288
精华内容 46,115
关键字:

对多线程和线程池的理解