笔记——Java并发

原子操作

原子操作即不可中断的操作,原子操作本身是线程安全的。比如赋值操作int i = 5;,它对应着一条计算机指令,cpu将数1放到变量 i 的内存地址中即可。

但是 i++ 事实上是有3个原子操作组成的。1. 取 i 的值 2. i + 1 3. 把新的值赋予i。这三个步骤,每一步都是一个原子操作,但是合在一起,就不是原子操作,也非线程安全。

线程状态

第一种说法:
在这里插入图片描述
新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();

就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;

运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就 绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;

阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:

1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;

2.同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;

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

死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

状态转换:

就绪状态转换为运行状态:当此线程得到处理器资源;

运行状态转换为就绪状态:当此线程主动调用yield()方法或在运行过程中失去处理器资源。

运行状态转换为死亡状态:当此线程线程执行体执行完毕或发生了异常。

此处需要特别注意的是:当调用线程的yield()方法时,线程从运行状态转换为就绪状态,但接下来CPU调度就绪状态中的哪个线程具有一定的随机性,因此,可能会出现A线程调用了yield()方法后,接下来CPU仍然调度了A线程的情况。

第二种说法:
在这里插入图片描述

新建(New) : 创建后尚未启动的线程处于这种状态。
·运行(Runnable) : 包括操作系统线程状态中的Running和Ready, 也就是处于此状态的线程有可
能正在执行, 也有可能正在等待着操作系统为它分配执行时间。
·无限期等待(Waiting) : 处于这种状态的线程不会被分配处理器执行时间, 它们要等待被其他线
程显式唤醒。 以下方法会让线程陷入无限期的等待状态:
■没有设置Timeout参数的Object::wait()方法;
■没有设置Timeout参数的Thread::join()方法;
■LockSupport::park()方法。
·限期等待(Timed Waiting) : 处于这种状态的线程也不会被分配处理器执行时间, 不过无须等待
被其他线程显式唤醒, 在一定时间之后它们会由系统自动唤醒。 以下方法会让线程进入限期等待状
态:
■Thread::sleep()方法;
■设置了Timeout参数的Object::wait()方法;
■设置了Timeout参数的Thread::join()方法;
■LockSupport::parkNanos()方法;
■LockSupport::parkUntil()方法。
·阻塞(Blocked) : 线程被阻塞了, “阻塞状态”与“等待状态”的区别是“阻塞状态”在等待着获取到
一个排它锁, 这个事件将在另外一个线程放弃这个锁的时候发生; 而“等待状态”则是在等待一段时
间, 或者唤醒动作的发生。 在程序等待进入同步区域的时候, 线程将进入这种状态。
·结束(Terminated) : 已终止线程的线程状态, 线程已经结束执行。

线程池

Java线程池详解
由于创建和销毁线程都是消耗系统资源的,所以当你想要频繁的创建和销毁线程的时候就可以考虑使用线程池来提升系统的性能。

线程池就是提前创建若干个线程,如果有任务需要处理,线程池里的线程就会处理任务,处理完之后线程并不会被销毁,而是等待下一个任务。

线程池的优势

(1)降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗
(2)提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行
(3)方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换(cpu切换线程是有时间成本的(需要保持当前执行线程的现场,并恢复要执行线程的现场))
(4)提供更强大的功能,延时定时线程池

Executors

Executors.newSingleThreadExecutor():它的特点在于工作线程数目被限制为 1,操作一个无界的工作队列,所以它保证了所有任务的都是被顺序执行,最多会有一个任务处于活动状态,并且不允许使用者改动线程池实例,因此可以避免其改变线程数目;

Executors.newCachedThreadPool:它是一种用来处理大量短时间工作任务的线程池,具有几个鲜明特点:它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;如果线程闲置的时间超过 60 秒,则被终止并移出缓存;长时间闲置时,这种线程池,不会消耗什么资源。其内部使用 SynchronousQueue 作为工作队列;

Executors.newFixedThreadPool(int nThreads):重用指定数目(nThreads)的线程,其背后使用的是无界的工作队列,任何时候最多有 nThreads 个工作线程是活动的。这意味着,如果任务数量超过了活动队列数目,将在工作队列中等待空闲线程出现;如果有工作线程退出,将会有新的工作线程被创建,以补足指定的数目 nThreads;

Executors.newSingleThreadScheduledExecutor():创建单线程池,返回 ScheduledExecutorService,可以进行定时或周期性的工作调度;

Executors.newScheduledThreadPool(int corePoolSize):和newSingleThreadScheduledExecutor()类似,创建的是个 ScheduledExecutorService,可以进行定时或周期性的工作调度,区别在于单一工作线程还是多个工作线程;

Executors.newWorkStealingPool(int parallelism):这是一个经常被人忽略的线程池,Java 8 才加入这个创建方法,其内部会构建ForkJoinPool,利用Work-Stealing算法,并行地处理任务,不保证处理顺序;

Executors.ThreadPoolExecutor():是最原始的线程池创建,上面1-3创建方式都是对ThreadPoolExecutor的封装。

execute() submit()

execute()方法没有返回值。可以执行任务,但无法判断任务是否成功完成。

public void execute(Runnable command) {
    e.execute(command); 
}

submit()方法返回一个future。可以用这个future来判断任务是否成功完成。

public Future<?> submit(Runnable task) {
    return e.submit(task);
}
public <T> Future<T> submit(Callable<T> task) {
    return e.submit(task);
}
public <T> Future<T> submit(Runnable task, T result) {
    return e.submit(task, result);
}

调用 ExecutorService 的 shutdown() 方法会等待线程都执行完毕之后再关闭,但是如果调用的是 shutdownNow() 方法,则相当于调用每个线程的 interrupt() 方法。

ThreadPoolExecutor参数

1、corePoolSize(线程池基本大小):当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize时,(除了利用提交新任务来创建和启动线程(按需构造),也可以通过 prestartCoreThread() 或 prestartAllCoreThreads() 方法来提前启动线程池中的基本线程。)

2、maximumPoolSize(线程池最大大小):线程池所允许的最大线程个数。当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。另外,对于无界队列,可忽略该参数。

3、keepAliveTime(线程存活保持时间)当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数。

4、workQueue(任务队列):用于传输和保存等待执行任务的阻塞队列。

5、threadFactory(线程工厂):用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)。

5、handler(线程饱和策略):当线程池和队列都满了,再加入线程会执行此策略。

线程池的状态

RUNNING:这是最正常的状态,接受新的任务,处理等待队列中的任务。
SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务。
STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。
TIDYING:所有的任务都销毁了,workCount 为 0,线程池的状态在转换为 TIDYING 状态时,会执行钩子方法 terminated()。
TERMINATED:terminated()方法结束后,线程池的状态就会变成这个。

同步 异步 阻塞 非阻

同步: 同步就是发起一个调用后,被调用者未处理完请求之前,调用不返回。

异步: 异步就是发起一个调用后,立刻得到被调用者的回应表示已接收到请求,但是被调用者并没有返回结果,此时我们可以处理其他的请求,被调用者通常依靠事件,回调等机制来通知调用者其返回结果。

同步和异步的区别最大在于调用后的结果,同步要等待返回结果,异步调用没有返回结果,结果靠回调返回。

阻塞: 阻塞就是发起一个请求,调用者一直等待请求结果返回,也就是当前线程会被挂起,无法从事其他任务,只有当条件就绪才能继续。

非阻塞: 非阻塞就是发起一个请求,调用者不用一直等着结果返回,可以先去干其他事情。

那么同步阻塞、同步非阻塞和异步非阻塞又代表什么意思呢?

举个生活中简单的例子,你妈妈让你烧水,小时候你比较笨啊,在哪里傻等着水开(同步阻塞)。等你稍微再长大一点,你知道每次烧水的空隙可以去干点其他事,然后只需要时不时来看看水开了没有(同步非阻塞)。后来,你们家用上了水开了会发出声音的壶,这样你就只需要听到响声后就知道水开了,在这期间你可以随便干自己的事情,你需要去倒水了(异步非阻塞)。

并发 并行

并发:当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间 段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状。.这种方式我们称之为并发(Concurrent)。

并行:当系统有一个以上CPU时,则线程的操作有可能非并发。当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。

公平锁 非公平锁

非公平锁:阻塞状态的线程处在一个等待队列,在锁空闲时会唤醒队列最前面的线程占用锁。假如此时有个线程要加锁,因为此时锁空闲还未被队列前面的线程占有,所以这个线程就插队先占用了锁,导致被队列最前面的线程还要等待。

在锁被释放时, 任何一个等待锁的线程都有机会获得锁 ——出自《深入理解Java虚拟机》 (有无解释?这句与上面的机制有冲突)

公平锁:一个新线程要加锁时,要先检查有没有其他线程在 wait,如果有自己要挂起,加到队列后面,然后唤醒队列最前面的线程。这种情况下相比较非公平锁多了一次挂起和唤醒

线程切换的开销,其实就是非公平锁效率高于公平锁的原因。因为非公平锁减少了线程挂起的几率,后来的线程有一定几率逃离被挂起的开销。

ReentrantLock在默认情况下也是非公平的, 但可以通过带布尔值的构造函数要求使用公平锁。 不过一旦使用了公平锁, 将会导致ReentrantLock的性能急剧下降, 会明显影响吞吐量。

sleep() yield()

①sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会

② 线程执行sleep()方法后转入阻塞(blocked)状态,而执行yield()方法后转入就绪(ready)状态

(yield只是让出CPU的控制权,使当前的线程处于可运行状态,说明它让出控制权之后有可能还是会立即执行的。用这个方法的话,线程的运行是处于不可控状态的;sleep是明确指定线程进入不可运行状态,更容易由程序控制)

③ sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常

④ sleep()方法比yield()方法(跟操作系统CPU调度相关)具有更好的可移植性。

(sleep具有更高的抽象,sleep具体执行的操作是抽象的,不同架构和系统乃至虚拟机都可以简单实现,而yield比较底层,部分体系内可能未实现这个语义,或者是以不期望的方式。)

⑤调用sleep和yeild都不会释放对象锁

stop() suspend() interrupt()

stop()会释放该线程所持有的所有的锁。一般任何进行加锁的代码块,都是为了保护数据的一致性,如果在调用thread.stop()后导致了该线程所持有的所有锁的突然释放,那么被保护数据就有可能呈现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用程序错误。

suspend()方法容易发生死锁。调用suspend()的时候,目标线程会停下来,但是该线程仍然持有同步锁。此时其他任何线程都不能访问锁定的资源,除非被”挂起”的线程使用resume()方法恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。所以不应该使用suspend(),而应在自己的Thread类中置入一个标志,指出线程应该活动还是挂起。若标志指出线程应该挂起,便用 wait()命其进入等待状态。若标志指出线程应当恢复,则用一个notify()重新启动线程。

调用 interrupt() 方法后,会当前线程中打一个停止标记,并不是真的停止线程。但是如果该线程处于阻塞、限期等待或者无限期等待状态,那么就会抛出 InterruptedException,从而提前结束该线程。但是不能中断 I/O 阻塞和 synchronized 锁阻塞。可以在线程的run()方法中加一个判断条件this.isInterrupted(),如果为true,就return。另外Thread.interrupted()静态方法是判断的当前线程的停止标记是否为true,并将interrupt重置为false。

实现线程的几种方法

1)继承Thread类创建线程
2)实现Runnable接口创建线程
3)使用Callable和FutureTask创建线程
4)使用线程池,例如用Executor框架
5)Spring实现多线程(底层是线程池)
6)定时器Timer (底层封装了一个TimerThread对象)

violate

保证了不同线程对被修饰的变量进行操作时的可见性,即一个线程修改了该变量的值,这新值对其他线程来说是立即可见的。(实现可见性)

大坑,留

生产者消费者模式

生产者消费者问题是线程模型中的经典问题:生产者和消费者在同一时间段内共用同一存储空间,生产者向空间里生产数据,而消费者取走数据。

生产者消费者模型的优点

1、解耦
假设生产者和消费者分别是两个类。如果让生产者直接调用消费者的某个方法,那么生产者对于消费者就会产生依赖(也就是耦合)。将来如果消费者的代码发生变化, 可能会影响到生产者。而如果两者都依赖于某个缓冲区,两者之间不直接依赖,耦合也就相应降低了。

2、支持并发
由于生产者与消费者是两个独立的并发体,他们之间是用缓冲区作为桥梁连接,生产者只需要往缓冲区里丢数据,就可以继续生产下一个数据,而消费者只需要从缓冲区了拿数据即可,这样就不会因为彼此的处理速度而发生阻塞。

3、支持忙闲不均
缓冲区还有另一个好处。如果制造数据的速度时快时慢,缓冲区的好处就体现出来了。当数据制造快的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中。 等生产者的制造速度慢下来,消费者再慢慢处理掉。

线程回调

就是客户程序C调用服务程序S中的某个方法A,然后S又在某个时候反过来调用C中的某个方法B,对于C来说,这个B便叫做回调方法。

这样就可以实现异步了,而不必让C一直等待S执行完A才能进行下一步操作。

synchronized

JVM 实现的锁。

深入理解synchronized

synchronized在底层是通过monitorenter进行加锁(底层是通过monitor对象来完成的,其中的wait/notify等方法也是依赖于monitor对象的。只有在同步块或者是同步方法中才可以调用wait/notify等方法的。因为只有在同步块或者是同步方法中,JVM才会调用monitory对象的);通过monitorexit来退出锁的。

wait()让占用了同步对象的线程临时释放占有,并且等待

notify() 随机通知一个等待在同步对象上的线程,让其有机会重新占用同步对象

notifyAll() 通知所有的等待在同步对象上的线程苏醒,让其有机会重新占用同步对象

这三个方法都是Object类的方法。

是可重入锁,即某线程已经获得某个锁,可以再次获取锁而不会出现死锁。

更多的东西写在了笔记——并发之JVM篇

J.U.C

即java.util.concurrent,J.U.C大大提高了并发性能,AQS 被认为是 J.U.C 的核心。

AQS

AbstractQueuedSynchronizer,抽象队列同步器,简称同步器。它是用来构建锁或者其他同步组件的基础框架。

它内部有一个int表示同步状态,通过内置的双向队列获取线程的排队工作。

volatile int state;//0 表示无锁状态 大于0表示锁定状态

双向队列的结点内部类Node:

static final class Node {
    static final Node SHARED = new Node();
    static final Node EXCLUSIVE = null;
    static final int CANCELLED =  1;
    static final int SIGNAL    = -1;
    static final int CONDITION = -2;
    static final int PROPAGATE = -3;
    volatile int waitStatus;
    volatile Node prev;
    volatile Node next;
    volatile Thread thread;
    Node nextWaiter;
    Node() { }   // Used to establish initial head or SHARED marker
    Node(Thread thread, Node mode) {     // Used by addWaiter
        this.nextWaiter = mode;
        this.thread = thread;
    }
    Node(Thread thread, int waitStatus) { // Used by Condition
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
    final boolean isShared() {
        return nextWaiter == SHARED;
    }
    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }
}

下图为例,Head是空结点,表示当时持有锁的线程,需要等待队列时创建后会一直存在。
在这里插入图片描述
Node有个waitStatus变量,有4种状态,当且仅当某结点的前一个结点prev为SIGNAL状态,该节点的thread才会挂起。

  • CANCELLED 取消状态
  • SIGNAL 等待触发状态
  • CONDITION 等待条件状态
  • PROPAGATE 状态需要向后传播

假设有两个线程A与B在同时竞争空闲的锁:

  1. 线程A执行CAS执行成功,state值被修改并返回true,线程A继续执行。
  2. 线程A执行CAS指令失败,说明线程B也在执行CAS指令且成功,这种情况下线程A会执行步骤3。
  3. 生成新Node节点node,并通过CAS指令插入到等待队列的队尾(同一时刻可能会有多个Node节点插入到等待队列中),如果tail节点为空,则将head节点指向一个空节点(代表线程B),具体实现如下:
private Node addWaiter(Node mode) {//mode是nextWaiter
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}
  1. node插入到队尾后,该线程不会立马挂起,会进行自旋操作。因为在node的插入过程,线程B(即之前没有阻塞的线程)可能已经执行完成,所以要判断该node的前一个节点pred是否为head节点(代表线程B),如果prev != head,则进行步骤5;如果prev == head,表明当前节点是队列中第一个“有效的”节点,因此再次尝试tryAcquire获取锁,
    1、如果成功获取到锁,表明线程B已经执行完成,线程A不需要挂起。
    2、如果获取失败,表示线程B还未完成,进行步骤5。
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
  1. 前面我们已经说过只有前一个节点pred的线程状态为SIGNAL时,当前节点的线程才能被挂起。
    1、如果pred的waitStatus为Node.SIGNAL即-1,就回到acquireQueued方法继续执行parkAndCheckInterrupt(),通过LockSupport.park()方法把线程A挂起,并等待被唤醒,被唤醒后进入步骤6。
    2、如果pred的waitStatus > 0,表明pred的线程状态CANCELLED,需从队列中删除,然后循环判断是否还要删除。回到步骤4。
    3、其他情况,通过CAS指令修改waitStatus为Node.SIGNAL,回到步骤4。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        return true;
    if (ws > 0) {
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}
  1. 线程每次被唤醒时,都要进行中断检测,如果发现当前线程被中断,那么抛出InterruptedException并退出循环。从无限循环的代码可以看出,并不是被唤醒的线程一定能获得锁,必须调用tryAccquire重新竞争,因为锁是非公平的,有可能被新加入的线程获得,从而导致刚被唤醒的线程再次被阻塞,这个细节充分体现了“非公平”的精髓。
  2. 线程释放锁过程:如果头结点head的waitStatus值为-1,则用CAS指令重置为0;找到waitStatus值小于0的节点s,通过LockSupport.unpark(s.thread)唤醒线程。

ReentrantLock

JDK 实现的锁。

可实现公平锁,Lock lock = new ReentrantLock(True);

在中断,即调用interrupt()方法时,若处于阻塞状态,会抛出中断异常InterruptedException。

通过调用对应的API方法来获取锁和释放锁:lock.lock(); lock.unlock();

与synchronized相对应的线程交互为await() signal() signalAll()

使用tryLock()方法可以限时等待,可以选择传入时间参数,表示等待指定的时间,无参则表示立即返回锁申请的结果。返回true表示获取锁成功。

public boolean tryLock() {
    return sync.nonfairTryAcquire(1);
}
//TimeUnit为时间单位枚举类,例TimeUnit.MILLISECONDS
public boolean tryLock(long timeout, TimeUnit unit)
        throwsInterruptedException {
    return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}

是可重入锁,即某线程已经获得某个锁,可以再次获取锁而不会出现死锁。

Lock应该确保在finally块中释放锁, 否则一旦受同步保护的代码块中抛出异常, 则有可能永远不会释放持有的锁。 这一点必须由程序员自己来保证, 而使用synchronized的话则可以由Java虚拟机来确保即使出现异常, 锁也能被自动释放。

在非公平锁中,每当线程执行lock方法时,都尝试利用CAS把state从0设置为1。如果成功,就不用挂起了,直接获得锁。

在公平锁中,每当线程执行lock方法时,如果同步器的队列中有线程在等待,则直接加入到队列中。

一些组件

CountDownLatch

维护了一个计数器 cnt,创建对象的时候传入计数器的初始值即线程的数量,每次调用 countDown() 方法会让计数器的值减 1,减到 0 的时候,那些因为调用 await() 方法而在等待的线程就会被唤醒。

CyclicBarrier

线程执行await()方法之后计数器会减 1,并进行等待,直到计数器为 0,所有调用 await() 方法而在等待的线程才能继续执行。

CyclicBarrier 的计数器通过调用 reset() 方法可以循环使用,所以它才叫做循环屏障。

CyclicBarrier 有两个构造函数,其中 parties 指示计数器的初始值,barrierAction 在所有线程都到达屏障的时候会执行一次。

public CyclicBarrier(int parties, Runnable barrierAction) {
    if (parties <= 0) throw new IllegalArgumentException();
    this.parties = parties;
    this.count = parties;
    this.barrierCommand = barrierAction;
}
public CyclicBarrier(int parties) {
    this(parties, null);
}

Semaphore

可以对特定资源允许同时访问的操作数量进行控制。初始化时固定个数的许可,在进行操作的时候,需要先acquire()获取到许可,才可以继续执行任务,如果获取失败,则进入阻塞;处理完成之后需要release()释放许可,阻塞的线程就会从acquire()获取到许可,继续执行任务。

FutureTask

public class FutureTask<V> implements RunnableFuture<V>
public interface RunnableFuture<V> extends Runnable, Future<V>

FutureTask 可用于异步获取执行结果或取消执行任务的场景。当一个计算任务需要执行很长时间,那么就可以用 FutureTask 来封装这个任务,主线程在完成自己的任务之后再去获取结果。

FutureTask<Integer> futureTask = new FutureTask<Integer>(new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
        int result = 0;
        for (int i = 0; i < 100; i++) {
            Thread.sleep(10);
            result += i;
        }
        return result;
    }
});
Thread computeThread = new Thread(futureTask);
computeThread.start();
System.out.println(futureTask.get());
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值