多线程知识汇总

1.使用线程

创建线程四种方法

new Thread()

实现runnable接口

实现callable接口

线程池

2基础线程机制

四种线程池

1、newFixedThreadPool 定长线程池
一个有指定的线程数的线程池,有核心的线程,里面有固定的线程数量,响应的速度快。正规的并发线程,多用于服务器。固定的线程数由系统资源设置。核心线程是没有超时机制的,队列大小没有限制,除非线程池关闭了核心线程才会被回收。
2、newCachedThreadPool 可缓冲线程池
只有非核心线程,最大线程数很大,每新来一个任务,当没有空余线程的时候就会重新创建一个线程,这边有一个超时机制,当空闲的线程超过60s内没有用到的话,就会被回收,它可以一定程序减少频繁创建/销毁线程,减少系统开销,适用于执行时间短并且数量多的任务场景。
3、ScheduledThreadPool 周期线程池
创建一个定长线程池,支持定时及周期性任务执行,通过过schedule方法可以设置任务的周期执行
4、newSingleThreadExecutor 单任务线程池
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行,每次任务到来后都会进入阻塞队列,然后按指定顺序执行

守护线程

Daemon

睡眠当前线程

sleep

申明完成当前的最重要的部分

yield

申明当前线程周期中的最重要的一部分

3.中断

一个线程执行完毕以后会自动结束,如果运行中发生异常也会提前结束

interrupted(),可以判断线程中断标志,此时调用interrupted()方法返回true;

shutdown()等线程都执行完毕再关闭,如果调用的shutdownNow()相当于调用每个线程interrupt()方法

4.同步互斥

synchronized

ReentrantLock

1.锁的实现

synchronized是JVM实现的,ReentrantLock是JDK实现的

2.性能

新版本synchronized增加了自旋锁,synchronized和ReentrantLock不同

3.等待可中断

synchronized wait notify 

ReentrantLock await signal

4.公平锁

公平锁指多个线程等待同一个锁,必须按照申请锁的时间获得锁

synchronized是非公平的,ReentrantLock 可以是公平的,也可以是非公平的

 

5线程之间的协作

synchronized wait notify notifyAll

ReentrantLock await signal signalAll

6.线程状态

1. 新建(NEW):新创建了一个线程对象。
2. 可运行(RUNNABLE):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。
3. 运行(RUNNING):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。
4. 阻塞(BLOCKED):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种: 
(一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。
(二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
(三). 其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
5. 死亡(DEAD):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。

 

7.JUC和AQS

CountDownLatch

CountDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完毕再执行。

CountDownLatch使用示例

 

package com.zwx.concurrent.jucUtil;

import java.util.concurrent.CountDownLatch;

public class CountDownLatchDemo {

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        new Thread(()->{
            System.out.println("----------");
            countDownLatch.countDown();
        }).start();
        countDownLatch.await();
        System.out.println("==========");
    }
}

CountDownLatch的构造函数接收一个int类型的参数作为计数器,可以视自己的需求设置一个合法的int数值,执行一次countDown()方法,计数器就会减1,await()方法会阻塞线程,直到线程计数减少为0才会继续运行。如果说为了防止某一个线程卡死导致await()一直阻塞的话,也可以调用await(long timeout, TimeUnit unit)方法设定超时时间,到达超时时间之后,即使计数器不到0,也可以继续执行后面的代码。

CountDownLatch源码分析

CountDownLatch(count)

这个方法比较简单,就是维护了一个计数器:

 

 

在这里插入图片描述

我们可以看到,调用了Sync类的构造器,而Sync继承了AbstractQueuedSynchronizer类,最终实际上是设置到AbstractQueuedSynchronizer中state属性上,state属性我们前面的AQS文章中提到过,0表示无锁状态,>=1之后表示加锁次数,所以这里的计数器就相当于加锁了N次。

 

 

在这里插入图片描述

CountDownLatch#await()

进入await()方法

 

 

在这里插入图片描述

然后继续调用了sync的acquireSharedInterruptibly(arg)方法:

 

 

如果被中断

1325行的if判断就是判断当前计数器state是不是等于0了,最终调用的是CountDownLatch中的内部类Sync的tryAcquireShared(arg)方法:

 

 

在这里插入图片描述

如果state!=0,那就返回-1,返回-1之后需要阻塞线程,所以就会继续执行之后的方法doAcquireSharedInterruptibly(arg)方法。

AQS#doAcquireSharedInterruptibly(arg)

 

在这里插入图片描述

注意一下999行,这里也是将当前线程封装成一个节点,并构建一个AQS同步队列,前面我们分析重入锁的时候提到了AQS有两种功能,一个是独占,一个就是共享,前面讲过的ReentrantLock中就是以独占模式封装的Node,而这里是以共享模式构建。
共享模式和独占模式在对象中表现出来的区别我们可以进入Node类看一下:

 

 

在这里插入图片描述

 

在这里插入图片描述

所以独占和共享模式构建的节点唯一区别就是共享节点中的nextWaiter不为空(另外还有Condition队列中的nextWaiter也不为空)。

这个方法中前面的一些逻辑AQS中分析过来,这里就不重复分析,这时候我们进来r>=0肯定是不成立的,所以会走到后面的线程挂起,挂起之后线程就阻塞了,那么阻塞了就一定需要被唤醒,所以我们猜测上文示例中的countDown()不但是将计数器减1,肯定还会有判断当减少到0的时候需要唤醒线程。

CountDownLatch#countDown()

 

调用之后进入CountDownLatch

调用的是sync类中的方法releaseShared(arg),注意这里固定传的是1,因为调用一次countDown()方法计数减1。

AQS#releaseShared(arg)

 

在这里插入图片描述

这里做了一个if判断,尝试是否可以释放,如果可以释放之后再执行释放,我们进入tryReleaseShared(arg)方法中一窥究竟。

CountDownLatch#tryReleaseShared(releases)

 

在这里插入图片描述

注意上面是一个死循环,只有两种情况可以跳出循环,一种就是当前state已经等于0,另一种就是CAS成功,也就是说减1成功。
如果返回false,就说明还需要阻塞等待其他线程;如果返回的是true,就会直接后面的doReleaseShared()方法。

AQS#doReleaseShared()

这个方法主要就是通过一个循环将head节点唤醒,因为中途可能会被其他线程唤醒了或者也可能加入了新节点,所以需要通过一个死循环来确保释放成功

 

 

在这里插入图片描述

回到AQS#doAcquireSharedInterruptibly(arg)

 

在这里插入图片描述

上面await()方法的线程阻塞在1014这个if条件这里,唤醒之后如果没有被中断过,那么会继续执行for循环,这时候r>=0肯定成立了,所以会进入setHeadAndPropagate(Node,int)方法,去依次传播所有需要唤醒的节点

AQS#setHeadAndPropagate(Node,int)

 

在这里插入图片描述

这里注意到参数中的Node是head节点的下一个节点,所以这里要做的是把第二个节点替换成Node节点,然后执行同一个方法doReleaseShared()方法去唤醒头节点,唤醒之后会回到上面的for循环,继续唤醒后一个节点,直到全部线程均被唤醒。

CyclicBarrier

CyclicBarrier的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一 组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会 开门,所有被屏障拦截的线程才会继续运行。CyclicBarrier可以用于多线程计算数据,最后合并计算结果的场景

CyclicBarrier使用示例1

 

package com.zwx.concurrent.jucUtil;

import java.util.concurrent.CyclicBarrier;

public class CyclicbarrierDemo {
    static CyclicBarrier cyclicBarrier = new CyclicBarrier(2);
    public static void main(String[] args) {
        new Thread(()-> {
            try {
                cyclicBarrier.await();
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("我是线程t1");
        },"t1").start();
        new Thread(()-> {
            try {
                cyclicBarrier.await();
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("我是线程t2");
        },"t2").start();

        System.out.println("主线程==end");
    }
}

输出结果:

 

 

在这里插入图片描述

这个t1和t2的输出结果是随机的。

CyclicBarrier还提供一个更高级的构造函数CyclicBarrier(int parties,Runnable barrier- Action),用于在线程到达屏障时,优先执行barrierAction。

CyclicBarrier使用示例2

 

package com.zwx.concurrent.jucUtil;

import java.util.concurrent.CyclicBarrier;

public class CyclicbarrierDemo2 {
    static CyclicBarrier cyclicBarrier = new CyclicBarrier(2,new MyThread());
    public static void main(String[] args) {
        new Thread(()-> {
            try {
                cyclicBarrier.await();
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("我是线程t1");
        },"t1").start();
        new Thread(()-> {
            try {
                cyclicBarrier.await();
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("我是线程t2");
        },"t2").start();

        System.out.println("主线程==end");
    }

    static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("do something");
        }
    }
}

输出结果:

 

 

在这里插入图片描述

我们可以看到,在线程t1和t2输出前会先输出自定义线程的信息。

CyclicBarrier实现原理

CyclicBarrier 相比 CountDownLatch 来说,要简单很多,源码实现是基于 ReentrantLock 和 Condition 的组合使用

CyclicBarrier源码分析

CyclicBarrier(parties)

进入CyclicBarrier默认构造器:

 

 

在这里插入图片描述

可以发现,最终其实还是调用的CyclicBarrier(int parties,Runnable barrier- Action)构造器:

 

 

在这里插入图片描述

注意了,构造CyclicBarrier对象时,初始化了多少个parties,则必须对应有parties个线程调用await()方法,否则线程不会往后执行。

CyclicBarrier#await()

 

在这里插入图片描述

调用了dowait(timed,nanos)方法,第一个参数false表示未设置超时时间,后面表示纳秒数,因为await还有另一个对应的方法带上超时时间:await(long,timeunit),这个方法中调用dowait(timed,nanos)方法时第一个参数就会是true,然后带上超时时间,表示到了设定时间之后线程就不会被阻塞,会继续往后执行。

CyclicBarrier#dowait()

 

/**
     * Main barrier code, covering the various policies.
     * 主要屏障代码,覆盖了各种策略
     */
    private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
        final ReentrantLock lock = this.lock;//定义一个重入锁:private final ReentrantLock lock = new ReentrantLock();
        lock.lock();
        try {
            //同一个屏障初始进来时属于同一代或者说一个周期,构建一个"代"(Generation)对象,同一个Generation表示同一代
            final Generation g = generation;//Generation中设置了broke=false,表示屏障没有损坏

            if (g.broken)//如果broken=true表示当前屏障被损坏了,抛出异常
                throw new BrokenBarrierException();

            if (Thread.interrupted()) {//如果线程被中断过
                breakBarrier();//设置屏障为损坏状态并唤醒所有持有锁的线程
                throw new InterruptedException();//抛出中断异常
            }

            int index = --count;//未调用await()方法的线程计数-1
            if (index == 0) {//如果屏障数为0,(表示所有线程都到达await()方法)
                boolean ranAction = false;
                try {
                    final Runnable command = barrierCommand;
                    if (command != null)
                        command.run();//表示到达屏障之后,如果我们有设置barrierCommand,则优先执行
                    ranAction = true;
                    //执行到这里的时候,说明所有线程都到了await()方法,且设置的barrierCommand也已经执行完了
                    //接下来要做的事情就是换代(所以CyclicBarrier是通过换代的方式实现重新计数的)
                    //换代之后相当于进入一个新的周期,所有线程在后续中又可以通过await()阻塞一次
                    nextGeneration();
                    return 0;
                } finally {
                    if (!ranAction)//如果ranAction = false说明当前屏障还有流程没执行完,所以需要屏障设置会损坏状态
                        breakBarrier();
                }
            }

            // loop until tripped, broken, interrupted, or timed out
            //死循环等到count=0,调用breakBarrier方法(表示屏障有问题的场景),中断或者超时
            for (;;) {
                try {
                    if (!timed)
                        //private final Condition trip = lock.newCondition();
                        trip.await();//即Condition队列的await()阻塞,相当于把线程加入到Condition队列中阻塞
                    else if (nanos > 0L)//超时时间大于0
                        nanos = trip.awaitNanos(nanos);//阻塞指定时间
                } catch (InterruptedException ie) {
                    //如果当前屏障没有换代,也没有损坏,那么就设置为损坏状态之后再抛出中断异常
                    if (g == generation && ! g.broken) {
                        breakBarrier();
                        throw ie;
                    } else {
                        // We're about to finish waiting even if we had not
                        // been interrupted, so this interrupt is deemed to
                        // "belong" to subsequent execution.
                        Thread.currentThread().interrupt();
                    }
                }

                if (g.broken)//如果屏障已经被损坏了
                    throw new BrokenBarrierException();

                if (g != generation)//如果发现已经换代了,就不继续循环了,直接返回就好了
                    return index;//返回当前还有多少个线程没有执行await()方法

                if (timed && nanos <= 0L) {//表示超时时间到了
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            lock.unlock();
        }
    }

这里的方法看起来很长,但其实除了一系列的判断,并没有多代码,结合注释,如果前面理解了ReentrantLock和Condition队列的话,应该非常好看懂,里面调用的其他一些子方法这里也不做单独介绍。

使用CyclicBarrier注意事项

1、对于指定计数值 parties,若由于某种原因,没有足够的线程调用 CyclicBarrier 的await()方法,则所有调用 await 的线程都会被阻塞;
2、若有多余线程执行了await()方法,那么最后一个到达屏障的线程会被阻塞
3、通过 reset 重置计数,会使得进入 await 的线程出现BrokenBarrierException;我们可以通过捕获异常重新处理业务逻辑
4、如果采用是 CyclicBarrier(int parties, Runnable barrierAction) 构造方法,执行 barrierAction 操作的是最后一个到达的线程。

CountDownLatch和CyclicBarrier区别

  • CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置。所以CyclicBarrier能处理更为复杂的业务场景。例如,如果计算发生错误,可以重置计数 器,并让线程重新执行一次。

Semaphore

Semaphore也就是我们常说的信号灯,Semaphore可以控制同时访问的线程个数,通过 acquire 获取一个许可,如果没有就等待,通过 release 释放一个许可。有点类似限流
的作用。叫信号灯的原因也和他的用处有关,比如某商场就 5 个停车位,每个停车位只能停一辆车,如果这个时候来了 10 辆车,必须要等前面有空的车位才能进入。

Semaphore使用示例

 

package com.zwx.concurrent.jucUtil;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class SemaphoreDemo {
    public static void main(String[] args) {
        Semaphore semaphore=new Semaphore(2);
        for(int i=1;i<=5;i++){
            new Car(i,semaphore).start();
        }
    }
}

class Car extends Thread{
    private int num;
    private Semaphore semaphore;

    public Car(int num, Semaphore semaphore) {
        this.num = num;
        this.semaphore = semaphore;
    }
    @Override
    public void run() {
        try {
            semaphore.acquire();//获取一个许可
            System.out.println("第"+num+"辆车进来了");

            TimeUnit.SECONDS.sleep(2);
            System.out.println("第"+num+"辆车出去了");
            semaphore.release();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出结果:

 

 

在这里插入图片描述

我们可以看到,一开始进来2辆车之后就阻塞了,后面必须出去一辆车才能进来一辆车。

Semaphore源码分析

Semaphore(permits)

Semaphore实际上和CountDownLatch实现非常相似,构造器最终的结果也是调用Sync类中将AbstractQueuedSynchronizer类中的state属性设置为permits:

 

 

在这里插入图片描述

上图说明默认构造的是非公平锁,但是还提供了另一个构造器由我们自己决定使用非公平锁还是公平锁,构造器最终是调用的下面这个方法设置state属性:

 

 

 

Semaphore#acquire()

这个方法和CountDownLatch中的await()调用的是同一个方法,这里就不再做分析

Semaphore#release()

这个方法调用的和CountDownLatch中的countDown()调用的也是同一个方法

 

 

最终调用的tryReleaseShared(arg)会和CountDownLatch会有一点点差异:

Semaphore#tryReleaseShared()

 

 

当释放了一个令牌之后,通过将允许的令牌总数+1实现多进来一个线程。

AQS#doReleaseShared()

上面tryReleaseShared()返回true之后,就会去唤醒下一个线程,这个和上面CountDownLatch中的countDown()方法调用的也是同一个方法去唤醒下一个线程。

总结

本篇文章主要介绍了三个常用的工具CountDownLatch,CyclicBarrier,Semaphore,其中,CountDownLatch和CyclicBarrier在一定场景下是可以替换使用的,而Semaphore一般用于限流。


 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值