Java 并发

2022/2/28 20:22:34

本文主要是介绍Java 并发,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

Java并发

基础问题

线程和进程的含义及区别

  • 线程
  • 进程
    线程与进程

线程的状态和变迁

线程的状态和变迁

如何创建一个线程?

继承Thread类,实现Runable接口,Callable和Future

start() 和 run() 区别

如下图所示,start()方法只能调用一次,重复调用会抛出IllegalThreadStateException,run()方法可以重复调用

为什么调用start()会执行run()方法

见下面JDK Thread start()中的注释;总结就是,start()方法会让JVM调用该Thread的run()方法,并在新线程上执行,即主线程调用start()过后返回并继续执行,新的线程将会被创建并用来执行run()方法

start()方法中call了一个native的start0()

native方法是指该方法的实现由非Java语言实现,如C或C++,不提供实现体,是个原生态方法

Java语言本身不能对操作系统底层进行访问和操作,但可以通过JNI(Java Native Interface)接口调用其他语言来实现对底层的访问,JNI是JDK的一部分

为什么不能直接调用run()方法

不会创建新的线程,该run()方法就是个普通的方法将在主线程执行,一般来说直接调用run()是一个bug或者失误

/**
     * Causes this thread to begin execution; the Java Virtual Machine
     * calls the <code>run</code> method of this thread.
     * <p>
     * The result is that two threads are running concurrently: the
     * current thread (which returns from the call to the
     * <code>start</code> method) and the other thread (which executes its
     * <code>run</code> method).
     * <p>
     * It is never legal to start a thread more than once.
     * In particular, a thread may not be restarted once it has completed
     * execution.
     *
     * @exception  IllegalThreadStateException  if the thread was already
     *               started.
     * @see        #run()
     * @see        #stop()
     */
    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 */
            }
        }
    }

    private native void start0();

    /**
     * If this thread was constructed using a separate
     * <code>Runnable</code> run object, then that
     * <code>Runnable</code> object's <code>run</code> method is called;
     * otherwise, this method does nothing and returns.
     * <p>
     * Subclasses of <code>Thread</code> should override this method.
     *
     * @see     #start()
     * @see     #stop()
     * @see     #Thread(ThreadGroup, Runnable, String)
     */
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

yield()方法有什么作用?

yield()方法只是让线程从RUNNING状态变为READY状态,但是也可能变为READY后又立刻被CPU调度器执行(变为RUNNING);如下图JDK注释说的,yield()只是告诉cpu调度器,我可以让出我的时间片,但是cpu调度器可以忽视这个消息;一般来说这个方法没有很多应该使用的场景,多是用于测试或者debug。

sleep()和yield()方法有什么区别?

sleep()让线程从RUNABLE状态变为TIMED_WAITING状态,让出CPU的时间片,不考虑线程优先级,yield()只会让优先级高的运行;sleep的线程不会丧失任何monitor的所有权

为什么sleep()和yield()方法是静态的?

sleep()和yield()都是谁调谁sleep/yield也都是自愿的,如果是实例方法会造成很多混乱,即我可以让你这个线程sleep

/**
     * A hint to the scheduler that the current thread is willing to yield
     * its current use of a processor. The scheduler is free to ignore this
     * hint.
     *
     * <p> Yield is a heuristic attempt to improve relative progression
     * between threads that would otherwise over-utilise a CPU. Its use
     * should be combined with detailed profiling and benchmarking to
     * ensure that it actually has the desired effect.
     *
     * <p> It is rarely appropriate to use this method. It may be useful
     * for debugging or testing purposes, where it may help to reproduce
     * bugs due to race conditions. It may also be useful when designing
     * concurrency control constructs such as the ones in the
     * {@link java.util.concurrent.locks} package.
     */
    public static native void yield();

    /**
     * Causes the currently executing thread to sleep (temporarily cease
     * execution) for the specified number of milliseconds, subject to
     * the precision and accuracy of system timers and schedulers. The thread
     * does not lose ownership of any monitors.
     *
     * @param  millis
     *         the length of time to sleep in milliseconds
     *
     * @throws  IllegalArgumentException
     *          if the value of {@code millis} is negative
     *
     * @throws  InterruptedException
     *          if any thread has interrupted the current thread. The
     *          <i>interrupted status</i> of the current thread is
     *          cleared when this exception is thrown.
     */
    public static native void sleep(long millis) throws InterruptedException;

sleep()和wait()方法有什么区别?

wait()是Object的实例方法,有几个重点:

  • 当前线程必须持有该object的锁(monitor)

  • wait()方法使得当前线程阻塞,并释放该object的锁(monitor)

  • 当前线程将等待其他线程调用该object的notify()或者notifyAll()的方法重新获得锁并继续执行下去

  • 响应中断

  • wait()方法必须用在循环里面

    为什么wait()方法必须用在循环里面,即用while(condition not hold)而不是if(condition not hold):

    当有多个线程由于相同condition not hold在wait()这行代码处阻塞等待持锁的时候,一旦condition change某个线程拿到锁了从wait()往下执行,又改变了condition,释放锁,由于另外个阻塞的线程是if判断,不再判断condition,拿到锁再处理时会发生错误。假设condition是array size > 0 而wait()之后会进行remove,那么就会出现IndexOutOfArray的问题

显而易见的相同点为:都使得当前线程阻塞了(TIMED_WAITING/WAITING状态)且都响应中断;不同点为:一个是Thread的静态方法,一个是Object的实例方法;wait()需要当前线程持有该object的monitor,sleep是当前线程自愿的行为;wait()调用后当前线程会释放monitor,sleep不会释放任何monitor;sleep自动唤醒,wait需要其他线程调用该object的notify和notifyAll()方法

/**
     * Causes the current thread to wait until another thread invokes the
     * {@link java.lang.Object#notify()} method or the
     * {@link java.lang.Object#notifyAll()} method for this object.
     * In other words, this method behaves exactly as if it simply
     * performs the call {@code wait(0)}.
     * <p>
     * The current thread must own this object's monitor. The thread
     * releases ownership of this monitor and waits until another thread
     * notifies threads waiting on this object's monitor to wake up
     * either through a call to the {@code notify} method or the
     * {@code notifyAll} method. The thread then waits until it can
     * re-obtain ownership of the monitor and resumes execution.
     * <p>
     * As in the one argument version, interrupts and spurious wakeups are
     * possible, and this method should always be used in a loop:
     * <pre>
     *     synchronized (obj) {
     *         while (&lt;condition does not hold&gt;)
     *             obj.wait();
     *         ... // Perform action appropriate to condition
     *     }
     * </pre>
     * This method should only be called by a thread that is the owner
     * of this object's monitor. See the {@code notify} method for a
     * description of the ways in which a thread can become the owner of
     * a monitor.
     *
     * @throws  IllegalMonitorStateException  if the current thread is not
     *               the owner of the object's monitor.
     * @throws  InterruptedException if any thread interrupted the
     *             current thread before or while the current thread
     *             was waiting for a notification.  The <i>interrupted
     *             status</i> of the current thread is cleared when
     *             this exception is thrown.
     * @see        java.lang.Object#notify()
     * @see        java.lang.Object#notifyAll()
     */
    public final void wait() throws InterruptedException {
        wait(0);
    }

进入等待的方法

join()方法怎么使用?

  1. join()方法不是一个静态方法,是Thread类的实例方法
  2. 当前线程会等待t.join()的t线程执行完成后才继续执行
  3. 响应中断
/**
     * Waits for this thread to die.
     *
     * <p> An invocation of this method behaves in exactly the same
     * way as the invocation
     *
     * <blockquote>
     * {@linkplain #join(long) join}{@code (0)}
     * </blockquote>
     *
     * @throws  InterruptedException
     *          if any thread has interrupted the current thread. The
     *          <i>interrupted status</i> of the current thread is
     *          cleared when this exception is thrown.
     */
    public final void join() throws InterruptedException {
        join(0);
    }

守护线程和用户线程有什么区别?

  1. 主线程结束后用户线程还会继续运行,JVM存活
  2. 如果没有用户线程,都是守护线程,那么JVM结束(所有的线程都会结束)

守护线程是一种特殊的线程,在后台默默地完成一些系统性的服务,比如垃圾回收线程JIT线程都是守护线程。与之对应的是用户线程,用户线程可以理解为是系统的工作线程,它会完成这个程序需要完成的业务操作。如果用户线程全部结束了,意味着程序需要完成的业务操作已经结束了,系统可以退出了。所以当系统只剩下守护进程的时候,java虚拟机会自动退出

什么是线程安全,有哪些线程安全程度?

点我

操作系统可以保证每个进程只能访问分配给自己的地址空间。而每个进程中都有一块内存空间(堆内存)是公共的,是所有线程都能访问的,就像小区的公园一样。操作系统也会为每个线程分配自己的内存空间(栈内存),只有自己这个线程能访问。

线程的安全其实是内存的安全。

如何保证线程安全?

  1. 放到栈内存,例如局部变量。但是也就只有自己能访问了,当变量从(方法内的)局部变量变为(类的)成员变量的时候,也就是从栈内存到公共的堆内存,就可能会出现问题
  2. 人人有份。ThreadLocal,每个Thread都有自己的一个map存储变量。这些变量虽然整体是放到堆内存的,但是由于自己拷贝一份到自己的map,自己处理自己的,就像是本地的一样,也就安全了。
  3. 只能看不能摸。例如,常量或者只读变量。
  4. 制定规则,先入为主。例如可重入锁。
  5. 地广人稀,CAS。

线程安全

线程安全的程度

什么是中断

另外一个问题,我们应该

如何安全地结束一个线程?

stop等方法已经被舍弃了

  1. 使用退出标志,用一个while()循环,再设置一个boolean标志
  2. 使用中断thread.interrupt()将设置该线程中断位为true,包含两种,一种本质就是1,用while循环加上isInterrupted()来判断;一种通过catch InterruptedException来处理

interrupt()方法只是改变中断状态,不会中断一个正在运行的线程需要用户自己去监视线程的状态为并做处理。支持线程中断的方法(也就是线程中断后会抛出interruptedException的方法)就是在监视线程的中断状态,一旦线程的中断状态被置为“中断状态”,就会抛出中断异常。这一方法实际完成的是,给受阻塞的线程发出一个中断信号,这样受阻线程检查到中断标识,就得以退出阻塞的状态。

更确切的说,如果线程被Object.wait, Thread.join和Thread.sleep三种方法之一阻塞,此时调用该线程的interrupt()方法,那么该线程将抛出一个 InterruptedException中断异常(该线程必须事先预备好处理此异常),从而提早地终结被阻塞状态。如果线程没有被阻塞,这时调用 interrupt()将不起作用,直到执行到wait(),sleep(),join()时,才马上会抛出 InterruptedException

/**
     * Interrupts this thread.
     *
     * <p> Unless the current thread is interrupting itself, which is
     * always permitted, the {@link #checkAccess() checkAccess} method
     * of this thread is invoked, which may cause a {@link
     * SecurityException} to be thrown.
     *
     * <p> If this thread is blocked in an invocation of the {@link
     * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link
     * Object#wait(long, int) wait(long, int)} methods of the {@link Object}
     * class, or of the {@link #join()}, {@link #join(long)}, {@link
     * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)},
     * methods of this class, then its interrupt status will be cleared and it
     * will receive an {@link InterruptedException}.
     *
     * <p> If this thread is blocked in an I/O operation upon an {@link
     * java.nio.channels.InterruptibleChannel InterruptibleChannel}
     * then the channel will be closed, the thread's interrupt
     * status will be set, and the thread will receive a {@link
     * java.nio.channels.ClosedByInterruptException}.
     *
     * <p> If this thread is blocked in a {@link java.nio.channels.Selector}
     * then the thread's interrupt status will be set and it will return
     * immediately from the selection operation, possibly with a non-zero
     * value, just as if the selector's {@link
     * java.nio.channels.Selector#wakeup wakeup} method were invoked.
     *
     * <p> If none of the previous conditions hold then this thread's interrupt
     * status will be set. </p>
     *
     * <p> Interrupting a thread that is not alive need not have any effect.
     *
     * @throws  SecurityException
     *          if the current thread cannot modify this thread
     *
     * @revised 6.0
     * @spec JSR-51
     */
    public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }
/**
     * Tests whether the current thread has been interrupted.  The
     * <i>interrupted status</i> of the thread is cleared by this method.  In
     * other words, if this method were to be called twice in succession, the
     * second call would return false (unless the current thread were
     * interrupted again, after the first call had cleared its interrupted
     * status and before the second call had examined it).
     *
     * <p>A thread interruption ignored because a thread was not alive
     * at the time of the interrupt will be reflected by this method
     * returning false.
     *
     * @return  <code>true</code> if the current thread has been interrupted;
     *          <code>false</code> otherwise.
     * @see #isInterrupted()
     * @revised 6.0
     */
    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }

    /**
     * Tests whether this thread has been interrupted.  The <i>interrupted
     * status</i> of the thread is unaffected by this method.
     *
     * <p>A thread interruption ignored because a thread was not alive
     * at the time of the interrupt will be reflected by this method
     * returning false.
     *
     * @return  <code>true</code> if this thread has been interrupted;
     *          <code>false</code> otherwise.
     * @see     #interrupted()
     * @revised 6.0
     */
    public boolean isInterrupted() {
        return isInterrupted(false);
    }

    /**
     * Tests if some Thread has been interrupted.  The interrupted state
     * is reset or not based on the value of ClearInterrupted that is
     * passed.
     */
    private native boolean isInterrupted(boolean ClearInterrupted);

interrupted和isInterrupted方法的区别?

如上图所示,interrupted()是线程的静态方法,返回当前线程的中断标记且会重置中断标记;isInterrupted()是实例方法,返回this的中断标记且不会重置中断标记。

可重入锁 & synchronized

可重入锁和synchronized的主要区别

  1. 机制是不一样的:synchronized是java内置的关键字,在JVM层面实现,系统会监控锁的释放,且自动释放,同步执行完或发生异常将释放锁;lock是JDK代码实现的,需要手动在finally模块释放,并可以非阻塞地获取锁
  2. 性能不一样:竞争激烈的情况下lock会比synchronized好;竞争不激烈的情况下synchronized性能好,且synchronized会从偏向锁-->轻量级锁-->重量级锁升级
  3. 场景范围不一样:使用lock,线程2等待线程1释放锁的时候,可以不用一直等待,synchronized做不到
  4. 总的来说,lock提供了比synchronized更多的功能:如下图PPT所示

锁和synchronized

  • synchronized 同步格式
synchronized(需要一个任意的对象(锁)){
     代码块中放操作共享数据的代码;
} 
  • 关于Lock
  1. Lock其实只是一个接口

Java中的锁

  1. ReentrantLock是唯一实现了Lock接口的类

可重入锁

可重入锁的实现?

先看JUC中的[AQS]

什么是synchronized的锁升级?

出自这里

Java对象在内存中的存储结构包括:请求头,实例数据,填充数据;对象头包含Mark Word(hashCode, GC分代年龄,锁信息),Class Metadata address(指向对象类型数据的指针),Array Length(该对象为数组时,数组的长度)

  • 偏向锁

    当对象被创建出来的时候,就有了偏向锁的标志位“01”,但状态为0,即被创建的对象的偏向锁不生效;但是,当线程执行到临界区(critical section)的时候,此时会利用CAS操作将线程ID插入到Markword中,并修改偏向锁的标志位,即状态为0,且能知道是哪个线程拥有了偏向锁。

    当此线程执行之后,若这个同步块代码又被进入了,则会:

    1. 判断当前线程是否与Markword当中的线程id一致,若一致则继续执行
    2. 若不一致,检查对象是否还是可偏向的,即偏向锁的状态
    3. 如果未偏向,则利用CAS竞争锁,也就是第一次获取锁时的操作
    4. 如果已经偏向了,可能需要重新偏向,或者大部分时候升级为轻量级锁

    偏向锁存在的意义:大部分时候都是同一个线程进入同一个同步块,那么这个时候就不需要再有加锁解锁的开销;偏向锁实际上是偏向第一个拿到锁的线程。

  • 升级为轻量级锁

    • 锁撤销-有开销

      1. 在一个安全的点停止拥有锁的线程
      2. 遍历线程栈,存在锁记录的话,需要修复锁记录和Markword使其变成无锁状态
      3. 唤醒当前线程,升级成轻量级锁
    • 轻量级锁:锁标志位为“00”,此时Markword bitfield被替换为指向LockRecord的指针,之前的bitfield将会复制到创建的这个LockRecord里面,LockRecord有个owner的指针指向对象

    • 轻量级锁又分为自旋锁和自适应自旋锁

    • 自旋锁:

      当有线程竞争锁时,会在原地循环等待,直到锁被释放可以立刻获取锁,而这个原地循环会消耗CPU。适用于同步代码块执行很快的场景。经验表明,大部分同步代码块都执行很快。设置一个原地循环的最大限制次数,如果超过就升级为重量级锁。默认为10次,可以通过-XX:PreBlockSpin来进行更改。

    • 自适应自旋锁:

      即可动态调整自旋等待的次数而不是个固定值。

    • 轻量级锁也被称为非阻塞同步,乐观锁

  • 升级为重量级锁

    • 重量级锁以来对象内部monitor,monitor又依赖于操作系统的mutex锁,此时的标志位为“10”,bitfield将会指向mutex的指针
    • 为什么重量级锁开销大?
      1. 此时,等待的线程会被阻塞,不会消耗CPU但是阻塞或者唤醒一个线程时都需要操作系统来帮忙,这就需要从用户态转换到内核态,这个转换很耗时。
    • 重量级锁又被称为阻塞同步,悲观锁

为什么竞争激烈的情况下lock比synchronized性能好?

volatile关键字

volatile是什么意思?原理是什么?

CAS的特点是什么?

CAS

监视器(Monitor)和Condition区别

操作系统在面对 进程/线程 间的同步的时候所支持的同步原语中,信号量(semaphore)和互斥量(mutex)是最重要的。在使用mutex进行并发控制时,要非常小心地控制其down和up的操作,否则将引起死锁。在此基础出现了更高层次的同步原语,使我们不需要亲自去操作变量进行阻塞和唤醒,这个更高级的同步原语就是monitor---也是编程语言在语法上提供的语法糖,如何实现属于编译器的工作,不是操作系统的范畴。

  • monitor的基本元素

    • 临界区:互斥进入临界区
    • monitor对象和锁:monitor object有相应数据结构保存被阻塞的线程,和基于mutex的锁
    • 条件变量和定义在monitor对象上的wait和notify操作
  • java中的每一个对象都有一个监视器(Monitor)

    • 使用synchronized关键字来圈定临界区,实现互斥的界限

    • monitor object:

      synchronized需要指定一个对象与之关联,如果synchronized修饰的是实例方法,那么其实关联的就是this,如果修饰的是类方法,关联的对象就是this.class,这个关联的对象就是monitor object。

      锁:

      Java对象存储在内存中,分别分为三个部分:对象头,实例数据和对齐填充,在对象头中就保存了锁标识。

    • wait和notify的操作:

      这些方法的实现是JVM内部基于C++实现的一套机制,原理如下图:总结就是,waitThread(即调用object.wait()方法的线程)先要获取object的锁(也就是为什么需要配合synchronized关键字使用),如果获取成功,执行到object.wait()这行代码处,该线程就放进了这个object的waitQueue里面,表示在等待中。如果获取失败,该线程将被放进synchronizedQueue中,想要继续尝试获取锁。

      当调用object.notifyAll()的方法时,所有等在waitQueue里的线程将移动到synchronizedQueue里面去和那些waitThread没有获取锁的线程一起尝试获取锁。

      一旦某个线程获取成功了,就继续往下执行。

  • 总结:Monitor是一套机制,它界于操作系统和我们实际编程中间,提供并发编程的方式。

Monitor机制

Java中的等待/通知机制

Condition接口1

Condition接口2

Condition接口3

J.U.C.

AQS

AQS是什么?

AQS叫做队列同步器(AbstractQueuedSynchronizer),是用来构建锁和同步组件的基础框架

  • AQS有一个内部类Node,每个Node主要就是存一个Thread,Thread的状态,父节点prev和子节点next
static final class Node {
    /** Marker to indicate a node is waiting in shared mode */
    static final Node SHARED = new Node();
    /** Marker to indicate a node is waiting in exclusive mode */
    static final Node EXCLUSIVE = null;

    /** waitStatus value to indicate thread has cancelled */
    static final int CANCELLED =  1;
    /** waitStatus value to indicate successor's thread needs unparking */
    static final int SIGNAL    = -1;
    /** waitStatus value to indicate thread is waiting on condition */
    static final int CONDITION = -2;
    /**
     * waitStatus value to indicate the next acquireShared should
     * unconditionally propagate
     */
    static final int PROPAGATE = -3;

    /**
     * Status field, taking on only the values:
     *   SIGNAL:     The successor of this node is (or will soon be)
     *               blocked (via park), so the current node must
     *               unpark its successor when it releases or
     *               cancels. To avoid races, acquire methods must
     *               first indicate they need a signal,
     *               then retry the atomic acquire, and then,
     *               on failure, block.
     *   CANCELLED:  This node is cancelled due to timeout or interrupt.
     *               Nodes never leave this state. In particular,
     *               a thread with cancelled node never again blocks.
     *   CONDITION:  This node is currently on a condition queue.
     *               It will not be used as a sync queue node
     *               until transferred, at which time the status
     *               will be set to 0. (Use of this value here has
     *               nothing to do with the other uses of the
     *               field, but simplifies mechanics.)
     *   PROPAGATE:  A releaseShared should be propagated to other
     *               nodes. This is set (for head node only) in
     *               doReleaseShared to ensure propagation
     *               continues, even if other operations have
     *               since intervened.
     *   0:          None of the above
     *
     * The values are arranged numerically to simplify use.
     * Non-negative values mean that a node doesn't need to
     * signal. So, most code doesn't need to check for particular
     * values, just for sign.
     *
     * The field is initialized to 0 for normal sync nodes, and
     * CONDITION for condition nodes.  It is modified using CAS
     * (or when possible, unconditional volatile writes).
     */
    volatile int waitStatus;

    /**
     * Link to predecessor node that current node/thread relies on
     * for checking waitStatus. Assigned during enqueuing, and nulled
     * out (for sake of GC) only upon dequeuing.  Also, upon
     * cancellation of a predecessor, we short-circuit while
     * finding a non-cancelled one, which will always exist
     * because the head node is never cancelled: A node becomes
     * head only as a result of successful acquire. A
     * cancelled thread never succeeds in acquiring, and a thread only
     * cancels itself, not any other node.
     */
    volatile Node prev;

    /**
     * Link to the successor node that the current node/thread
     * unparks upon release. Assigned during enqueuing, adjusted
     * when bypassing cancelled predecessors, and nulled out (for
     * sake of GC) when dequeued.  The enq operation does not
     * assign next field of a predecessor until after attachment,
     * so seeing a null next field does not necessarily mean that
     * node is at end of queue. However, if a next field appears
     * to be null, we can scan prev's from the tail to
     * double-check.  The next field of cancelled nodes is set to
     * point to the node itself instead of null, to make life
     * easier for isOnSyncQueue.
     */
    volatile Node next;

    /**
     * The thread that enqueued this node.  Initialized on
     * construction and nulled out after use.
     */
    volatile Thread thread;

    /**
     * Link to next node waiting on condition, or the special
     * value SHARED.  Because condition queues are accessed only
     * when holding in exclusive mode, we just need a simple
     * linked queue to hold nodes while they are waiting on
     * conditions. They are then transferred to the queue to
     * re-acquire. And because conditions can only be exclusive,
     * we save a field by using special value to indicate shared
     * mode.
     */
    Node nextWaiter;

    /**
     * Returns true if node is waiting in shared mode.
     */
    final boolean isShared() {
        return nextWaiter == SHARED;
    }

    /**
     * Returns previous node, or throws NullPointerException if null.
     * Use when predecessor cannot be null.  The null check could
     * be elided, but is present to help the VM.
     *
     * @return the predecessor of this node
     */
    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }

    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;
    }
}
  • AQS本身维护一个Node节点的双向链表队列,和一个表征同步状态的state字段
/**
     * Head of the wait queue, lazily initialized.  Except for
     * initialization, it is modified only via method setHead.  Note:
     * If head exists, its waitStatus is guaranteed not to be
     * CANCELLED.
     */
    private transient volatile Node head;

    /**
     * Tail of the wait queue, lazily initialized.  Modified only via
     * method enq to add new wait node.
     */
    private transient volatile Node tail;

    /**
     * The synchronization state.
     */
    private volatile int state;

AQS独占式和共享式的含义?

  • 独占式:同一时刻只有一个线程持有同步状态,如ReentrantLock的实现

    独占式获取同步状态过程:

AQS获取同步状态

  • 共享式:共享资源可以被多个线程同时占有,如ReadWriteLock,CountdownLatch

AQS的模板模式是什么?

  • AQS只是一个基础框架,tryAcquire(int arg)tryRelease(int arg)tryAcquireShared(int arg)tryReleaseShared(int arg)isHeldExclusively()都没有具体实现,因为公平锁有公平锁的实现方式,非公平锁有非公平锁的实现方式等,需要子类去实现

AQS

AQS的实现

todo

简述并发工具Semaphore, CountdownLatch, CyclicBarrier的特点使用场景

线程安全的集合有哪些?

ConcurrentHashMap

BlockingQueue

CopyOnWriteArrayList

ConcurrentLinkedQueue

线程池



这篇关于Java 并发的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程