多线程核心知识

线程生命周期(线程状态)

Java中的线程的生命周期大体可分为5种状态。
  新建:创建完线程、还没调用start方法。
  就绪:已经调用start方法,等待CPU分配时间片。
  运行:run方法正在运行中。
  阻塞:wait、sleep、yield、join 使线程阻塞住。
  死亡:run方法运行完毕。

多线程通信

jion

我们现在有两个线程的话,在第二个线程里面调用第一个线程的join方法,程序会立即切换到第一个线程去执行。点进去thread.join()源码发现,join主要是下面几行代码实现的。
    public final void join() throws InterruptedException {
        // 最多等待millis毫秒,此线程才能死;millis=0意味着永远等待。
        join(0);
    }
    
    // 可以看到调用join(long millis)方法,实际还是通过synchronized实现的。
    public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {//如果线程还没执行完
                wait(0);//释放对象锁,程序停止执行。
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
可以看到join的底层还是对方法加锁,然后通过wait()使自己阻塞,从而切换到其他线程执行;join()方法的java文档中写到:a thread terminates the  this.notifyAll method is invoked.也就是说,其他线程run()方法运行完了以后,会调用this.notifyAll,释放锁。这样线程获得锁以后就可以继续往下执行了。

yield

yield()让出线程时间片,增大线程切换的几率。由于它是native修饰的,所以是通过其他语言直接操作机器实现的。

public static native void yield();

sleep

sleep(long millis)会让线程阻塞住millis秒,在这个时间段内不再参与到CPU竞争。sleep()是通过millis参数来设置一个定时器实现的,时间一结束,若没有其他线程正在执行,则会同其他线程一起抢占cpu资源。

public static native void sleep(long millis) throws InterruptedException;

wait

wait()会让线程休眠,释放锁。
(1)为什么 wait/notify/notifyAll一定要放在同步代码块里,我们可以看下面这个案例。

boolean flag = false;

// A线程代码
while(!flag){
    wait();
}

// B线程代码
if(!flag){
    condition = true;
    // nofity随机唤醒一个等待的线程;notifyAll唤醒等待该锁的所有线程
    notify();
}

1. 如果线程A刚执行完while(!flag),准备执行wait(),此时线程A的时间片已经耗尽
2. B线程执行完 condition = true; notify();的操作,由于A并没有wait,所以B的notify不会起任何效果
3. 此时A又获得了时间片,继续执行wait(),此时由于没有notify()唤醒她,那么她会一直沉睡下去。
所以相当于:锁对象里面维护了一个队列,线程A执行lock的wait方法,把线程A保存到list中,线程B中执行lock的notify方法,从等待队列中取出线程A继续执行。

(2)为什么 wait要放在while中判断而不是if中
如果采用if判断,当线程从wait中唤醒时,判断条件已经不满足处理业务逻辑的条件了,从而出现错误的结果。所以我们业务需要像下面一样在判断一次。而循环则是对上述写法的简化
synchronized (monitor) { // 判断条件谓词是否得到满足 if(!locked) { // 等待唤醒 monitor.wait(); if(locked) { // 处理其他的业务逻辑 } else { // 跳转到monitor.wait(); } } }

为什么要使用线程池

诸如 Web 服务器、数据库服务器、文件服务器或邮件服务器之类的许多服务器应用程序都面向处理来自某些远程来源的大量短小的任务。线程池为线程生命周期开销问题和资源不足问题提供了解决方案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。其好处是,因为在请求到达时线程已经存在,所以无意中也消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使应用程序响应更快。而且,通过适当地调整线程池中的线程数目,也就是当请求的数目超过某个阈值时,就强制其它任何新到的请求一直等待,直到获得一个线程来处理为止,从而可以防止资源不足。风险与机遇:用线程池构建的应用程序容易遭受任何其它多线程应用程序容易遭受的所有并发风险,诸如同步错误和死锁,它还容易遭受特定于线程池的少数其它风险,诸如与池有关的死锁、资源不足和线程泄漏。
总结起来就三点:
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,使用线程池可以进行统一分配、调优和监控。但是,要做到合理利用。

下面4种是比比较常用的线程池创建方式,第一个用的比较多。

 

public class ExecutorDemo {
    public static void main(String[] args) {
        // 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
        ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 15; i++) {
            final int temp = i;
            newFixedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                }
            });
        }
        // 关闭线程池
        newFixedThreadPool.shutdown();
    }
    /**
     * 控制台输出
     * pool-1-thread-1
     * pool-1-thread-1
     * pool-1-thread-1
     * pool-1-thread-1
     * pool-1-thread-1
     * pool-1-thread-1
     * pool-1-thread-1
     * pool-1-thread-1
     * pool-1-thread-1
     * pool-1-thread-1
     * pool-1-thread-1
     * pool-1-thread-2
     * pool-1-thread-3
     * pool-1-thread-4
     * pool-1-thread-5
     * 
     * 总结:
     *     因为线程池大小为5,超出的请求需要排队等待线程的分配。定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()
     */
}
newFixedThreadPool
public class ExecutorDemo {
    public static void main(String[] args) {
        // 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
        ExecutorService executorService3 = Executors.newSingleThreadExecutor();

        for (int i = 0; i < 10; i++) {
            final int index = i;
            executorService3.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                }
            });
        }
    }
}
newSingleThreadExecutor
public class ExecutorDemo {
    public static void main(String[] args) {
        // 创建一个定长线程池,能延迟执行、定时执行任务,支持周期性任务执行。
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
        System.out.println(LocalTime.now());
        for (int i = 0; i < 10; i++) {
            scheduledExecutorService.schedule(new Runnable() {
                public void run() {
                    System.out.println(Thread.currentThread().getName()+" 现在时间:"+ LocalTime.now());
                }
            }, 3, TimeUnit.SECONDS);
        }
        /**
         * 控制台输出
         * 19:06:29.623
         * pool-1-thread-1 现在时间:19:06:32.626
         * pool-1-thread-1 现在时间:19:06:32.626
         * pool-1-thread-1 现在时间:19:06:32.626
         * pool-1-thread-1 现在时间:19:06:32.626
         * pool-1-thread-1 现在时间:19:06:32.626
         * pool-1-thread-1 现在时间:19:06:32.626
         * pool-1-thread-1 现在时间:19:06:32.626
         * pool-1-thread-1 现在时间:19:06:32.626
         * pool-1-thread-1 现在时间:19:06:32.626
         * pool-1-thread-1 现在时间:19:06:32.626
         */
    }
}
newScheduledThreadPool
public class ExecutorDemo {
    public static void main(String[] args) {
        // 创建一个可缓存线程池,如果线程池无可用线程,则回收空闲线程、否则新建线程(线程太多可能会引起OOM)。
        ExecutorService executorService = Executors.newCachedThreadPool();
//        executorService.submit(() -> {
//            System.out.println(Thread.currentThread().getName());
//        });
        for (int i = 0; i < 15; i++) {
            final int temp = i;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                }
            });
        }
        /**
         * 控制台输出
         * pool-1-thread-1
         * pool-1-thread-2
         * pool-1-thread-2
         * pool-1-thread-3
         * pool-1-thread-1
         * pool-1-thread-7
         * pool-1-thread-6
         * pool-1-thread-5
         * pool-1-thread-4
         * pool-1-thread-8
         * pool-1-thread-9
         * pool-1-thread-10
         * pool-1-thread-11
         * pool-1-thread-12
         * pool-1-thread-13
         *
         * 总结:
         *     线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
         *
         */
    }
}
newCachedThreadPool

threadPoolExecutor实现原理

/**
 * 自定义线程池
 */
public class ExecutorDemo {
    public static void main(String[] args) {
        /**
         * 上面4种线程池的创建其实都是对于ThreadPoolExecutor的封装,点进去看发现就是:
         * new ThreadPoolExecutor(int corePoolSize,
         *                           int maximumPoolSize,
         *                        long keepAliveTime,
         *                        TimeUnit unit,
         *                        new LinkedBlockingQueue<Runnable>())
         */
        /**
         * @param corePoolSize 核心线程数(空闲也不会被回收)
         * @param maximumPoolSize 最大线程数
         * @param keepAliveTime 线程空闲时的超时时间
         * @param TimeUnit 超时时间单位
         * @param BlockingQueue<Runnable> 阻塞队列,线程排队时就装在这个队列里面
         */
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,2,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(3));
        /**
         * 上面已经创建好线程池了,核心线程为1,也就是初始线程数为1;最大线程2;队列最多容纳3个线程等待。下面我们有6个任务要执行
         * (1) 第一个任务开始,直接就交给了核心线程去执行
         * (2) 后面的线程进来,由于没有线程了就会放到队列里面去等待。如果之前的线程空闲了就会继续复用;否则放入队列等待新的线程创建
         * (3) 假使线程都在使用中,因为最大线程数为2,那么最多同时运行2个线程,由于队列也只能排队3个线程,那么这个线程池最多就只能容纳5个线程
         * (4) 如果线程池已经被占满,此时还有第6个任务要进来,那么线程池就会溢出报错。
         */
        for (int i = 1; i <= 6; i++) {
            final int temp = i;
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "任务" + temp);
                }
            });
        }
        /**
         * 控制台输出
         * pool-1-thread-1任务1
         * pool-1-thread-1任务2
         * pool-1-thread-1任务3
         * pool-1-thread-1任务4
         * pool-1-thread-2任务5
         * java.util.concurrent.RejectedExecutionException: Task com.Thread.A_newThread.pool.ExecutorDemo$1@6d6f6e28 rejected from java.util.concurrent.ThreadPoolExecutor@135fbaa4[Running, pool size = 2, active threads = 2, queued tasks = 3, completed tasks = 0]
         *     at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
         *     at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
         *     at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
         *     at com.Thread.A_newThread.pool.ExecutorDemo.main(ExecutorDemo.java:35)
         */
    }
}

如何合理分配线程池

  一个线程池究竟设置多大才合适这个并没有一个固定值。很多博文说是有两个计算公式:CPU密集型(一般为CPU核心数+1);IO密集型(2*CPU核心数)。但是!但是要我说,压根就没有什么公式,也没什么花里胡哨的定义,因为每个系统是不一样的:就好比你在服务器里面跑一个tomcat,那它几百个线程又怎么说?比如你在服务器部署很多东西那又该怎么算?所以我觉得应该是一开始就设置线程数量为cpu核数,然后不断的压测,把各项性能指标保持在80%左右就够了,此时的线程数量才是最合理的。

Fixed 源码解析

初始化

  fixed线程池在初始化的时候,就设置了固定数量的线程池,默认核心线程数量和最大数量是一致的,然后等待的线程是放在 LinkedBlockingQueue 中。他还有一个比较核心的属性  ctl,ctl是32位的int类型,前3位表示线程状态、默认是 RUNNING ;后29位是线程数量,一开始是0来着。

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

  然后可以看到创建线程的话,默认是用的 Executors.defaultThreadFactory() ,就是设置了一些名称(getAndIncrement自增),是否后台线程,以及线程优先级之内的。

线程池原理

提交一个任务到线程池中,线程池的处理流程如下,其实代码注释已经说的很清楚了哈:
  1、如果当前池子里的数量  <  corePoolSize ,那就创建一个线程来执行。默认是0,那么前几次肯定都是创建的嘛。
  2、如果当前池子里的数量 >= corePoolSize ,那就把请求压入等待队列中。
  3、如果当前池子里的数量 >= corePoolSize ,然后压入队列也失败,就会判断 线程数量是否 < maximumPoolSize 。如果小于则尝试创建新线程,反之走 reject 策略,拒绝你的任务。所有超过 corePollSzie 的线程,在keepAliveTime的时间范围内都空闲之后,比如说 60s 的线程就会被回收掉。

线程创建与启动

   线程执行任务是调用 execute() 方法,就是通过 addWorker 方法来创建线程的。

    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            // 拿到 ctl 值
            int c = ctl.get();
            // 这个 rs 就是当前的状态
            int rs = runStateOf(c);

            // 判断线程状态的一些异常信息,略过。
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                // 获取当前线程数量 wc
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                // 先 cas 来递增线程数量
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                // 如果当前线程状态不是之前的状态,那就再次循环,尝试cas操作
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            // work就是工作线程,负责执行你提交的任务。下面的thread就是这个work的thread
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                // 加锁
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    // 拿到当前线程池的状态, 如果是被关掉了就抛异常
                    int rs = runStateOf(ctl.get());
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                    
                        // 线程加入线程池,works是 hashset 结构,非线程安全的
                        workers.add(w);
                        // 获取线程池的线程数量,largestPoolSize是最大数量
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    // 成功就释放锁咯
                    mainLock.unlock();
                }
                if (workerAdded) {
                    // 启动线程,实际执行的是 ThreadPoolExecutor.run() 方法
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            // 添加失败,就把线程从池中拿掉,并且把线程池数量cas递减
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

线程执行

  在上面调用 t.start(); 启动线程后,就来到了这里。

    final void runWorker(Worker w) {
        // 这就是我们启动的线程
        Thread wt = Thread.currentThread();
        // 这个 task 就是我们提交的 Runnable 中的那个run() 方法逻辑
        Runnable task = w.firstTask;
        w.firstTask = null;
        // 解锁
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
       // 在这个while循环里面,当前线程执行完了,就会从等待队列中获取其他线程。这个getTask()会一直等待,所以线程池不执行 shutdown() 进程就不会关闭。
while (task != null || (task = getTask()) != null) { // 这里的lock不是加锁的意思,Worker执行一个任务的时候,会通过自己的AQS机制更新一下自己的状态,相当于更新自己当前执行的线程是谁 w.lock(); // If pool is stopping, ensure thread is interrupted; // if not, ensure thread is not interrupted. This // requires a recheck in second case to deal with // shutdownNow race while clearing interrupt // 一些异常情况下,就打断该线程。忽略。 if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) wt.interrupt(); try { // 在执行我们的线程逻辑前会干的事情,我们可以重写覆盖他,一般都是没有的。 beforeExecute(wt, task); Throwable thrown = null; try { task.run();// 这里就是执行我们的线程逻辑 } catch (RuntimeException x) { thrown = x; throw x; } catch (Error x) { thrown = x; throw x; } catch (Throwable x) { thrown = x; throw new Error(x); } finally { // 执行 线程逻辑 后,触发的方法 afterExecute(task, thrown); } } finally { task = null; w.completedTasks++; w.unlock(); } } completedAbruptly = false; } finally { processWorkerExit(w, completedAbruptly); } }

线程满了怎么办

    public boolean offer(E e) {
        if (e == null) throw new NullPointerException();
        final AtomicInteger count = this.count;
        // 队列满了就直接返回 false ,不会阻塞等待。capacity是integer的最大值,你可以理解为它永远不会满
        if (count.get() == capacity)
            return false;
        int c = -1;
        Node<E> node = new Node<E>(e);
        final ReentrantLock putLock = this.putLock;
        putLock.lock();
        try {
            if (count.get() < capacity) {
                // 入队,挂载到 node 的 next 中去。
                enqueue(node);
                c = count.getAndIncrement();
                if (c + 1 < capacity)
                    // 唤醒阻塞线程 (在执行线程之后,会for循环通过take去队列中去获取下一个线程,没有的话就会阻塞在那里)
                    notFull.signal();
            }
        } finally {
            putLock.unlock();
        }
        // 唤醒阻塞线程 (在执行线程之后,会for循环通过take去队列中获取下一个线程,没有的话就会阻塞在那里)
        if (c == 0)
            signalNotEmpty();
        return c >= 0;
    }

获取队列线程

    private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            // 获取线城状态 rs
            int c = ctl.get();
            int rs = runStateOf(c);

            // 检查,线程被关了就数量-1
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }
            // 拿到线程数量
            int wc = workerCountOf(c);

            // Are workers subject to culling?
            // 是否允许线程超时 = 配置了超时(默认false) || 线程数量超过了核心线程数量
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                // 正常情况下 timed 肯定就是false了,那就走 workQueue.take(); 阻塞住,等待任务进来
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

关闭线程池

    public void shutdown() {
        // 先加锁
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            // 安全相关校验,略过
            checkShutdownAccess();
            // 强制的把线程池状态设置为 SHUTDOWN
            advanceRunState(SHUTDOWN);
            // 关闭处于空闲状态的work,遍历线程池 workers ,将线程中断掉。
            // Worker本身是一个AQS,执行任务的时候,state = 1;执行完一个任务,state = 0
            interruptIdleWorkers();
            // 回调钩子而已,可以自己重写逻辑
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }

 

posted @ 2020-01-06 23:46  吴磊的  阅读(507)  评论(0编辑  收藏  举报
//生成目录索引列表