juc学习笔记

JUC

详情参考:https://www.bilibili.com/video/BV1Kw411Z7dF

一、JUC概述

1. 关于JUC

JUC即位于java.util.concurrent包下用于并发编程的一些工具包。

2. 进程和线程

进程

是系统进行资源分配和调度的基本单位,是操作系统的基础。

是程序动态的执行过程,是有生命周期的。

线程

是操作系统能够进行运算调度的最小单位。同一个进程中,多个线程可以同时执行。

3. 线程的状态

/**
* Thread state for a thread which has not yet started.
*/
NEW,

/**
* Thread state for a runnable thread.  A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,

/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,

/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
*   <li>{@link Object#wait() Object.wait} with no timeout</li>
*   <li>{@link #join() Thread.join} with no timeout</li>
*   <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,

/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
*   <li>{@link #sleep Thread.sleep}</li>
*   <li>{@link Object#wait(long) Object.wait} with timeout</li>
*   <li>{@link #join(long) Thread.join} with timeout</li>
*   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
*   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,

/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;

4. wait与sleep区别

  1. sleep是Thread的静态方法,wait是Object的方法,任何对象都可以调用。
  2. sleep不会释放锁,wait会释放锁。
  3. 它们都可以被interrupted方法中断。

5. 并发和并行

1). 串行模式

按照先后顺序,依次执行。串行是一次只能取得一个任务,并执行这个任务。

2). 并行模式

并行可以同时获取多个任务,并同时执行这些任务。

并发

多个程序可以"同时"运行的现象。

6. 管程

(Monitor)是一种同步机制,保证同一时间,只有一个线程访问资源。

jvm同步的进入和退出,是使用管程对象实现的。

7. 用户线程和守护线程

用户线程:自定义线程基本上都是用户线程。

守护线程:运行在后台,依赖于用户线程。

主线程结束,用户线程还在,jvm存活。没有用户线程,都是守护线程,jvm结束。

二、Lock接口

1. synchronized关键字

  • 修饰代码块

    同步代码块。作用范围{}之间,作用的对象是调用这个代码块的对象

  • 修饰方法

    同步方法。作用范围整个方法,作用的对象是调用这个方法的对象。

    synchronized方法被继承后,不具有synchronized属性,需手动添加。

  • 修饰静态方法

    作用于整个静态方法,作用对象是这个类的所有对象。

  • 修饰类

    作用于整个类,作用对象是这个类的所有对象。

售票实例:

3个窗口最多售卖30张票

public class SynchronizedDemo {

    public static void main(String[] args) {
        Ticket ticket = new Ticket();

        new Thread(() -> {
            for(int i = 0; i < 40; i ++){
                ticket.sale();
            }
        },"A").start();

        new Thread(() -> {
            for(int i = 0; i < 40; i ++){
                ticket.sale();
            }
        },"B").start();

        new Thread(() -> {
            for(int i = 0; i < 40; i ++){
                ticket.sale();
            }
        },"C").start();
    }


}

class Ticket{
    private int count = 30;

    public synchronized void sale(){
        if (count > 0){
            System.out.println(Thread.currentThread().getName() + "卖票:" + (count --));
            System.out.println("剩余票数:" + count);
        }
    }
}

2. 多线程编程步骤

1). 编写资源类,并创建属性和方法。

2). 在资源类操作方法

  • 判断
  • 干活
  • 通知

3). 编写多个线程,调用资源类的方法。

4). 防止虚假唤醒问题

3. lock

lock也可以实现锁的功能,比synchronized关键字的功能更多。

与synchronized的区别:

  1. Lock是一个接口,synchronized是一个关键字。
  2. synchronized发生异常时,会自动释放锁,不会导致死锁;而lock发生异常时,没有在finally中释放锁,则可能发生死锁。
  3. Lock可以让等待锁的线程响应中断,而synchronized不可以,使用synchronized时,等待的线程会一直等待下去,不能响应中断。
  4. 通过Lock可以知道获取锁是否成功,synchronized不可以。
  5. Lock可以提高多个线程进行读操作的效率。

通过lock实现售票问题

public class LockDemo {
    public static void main(String[] args) {
        LTicket ticket = new LTicket();

        new Thread(() -> {
            for (int i = 0; i < 40; i ++){
                ticket.sale();
            }
        },"A").start();

        new Thread(() -> {
            for (int i = 0; i < 40; i ++){
                ticket.sale();
            }
        },"B").start();

        new Thread(() -> {
            for (int i = 0; i < 40; i ++){
                ticket.sale();
            }
        },"C").start();
    }
}

class LTicket{
    private int count = 30;
    //创建可重入锁
    private final ReentrantLock lock = new ReentrantLock();

    public void sale(){
        //上锁
        lock.lock();
        try{
            if (count > 0)
                System.out.println(Thread.currentThread().getName() + "售票:" + (count --) + ",剩余:" + count);
        }finally {
            //解锁
            lock.unlock();
        }
    }
}

4. Condition

Lock代替synchronized,Condition用来代替Object监视器的相关方法(如等待,唤醒等)。

该对象通过Lock.newCondition()获取。

package java.util.concurrent.locks;
import java.util.concurrent.TimeUnit;
import java.util.Date;

/**
 * {@code Condition} factors out the {@code Object} monitor
 * methods ({@link Object#wait() wait}, {@link Object#notify notify}
 * and {@link Object#notifyAll notifyAll}) into distinct objects to
 * give the effect of having multiple wait-sets per object, by
 * combining them with the use of arbitrary {@link Lock} implementations.
 * Where a {@code Lock} replaces the use of {@code synchronized} methods
 * and statements, a {@code Condition} replaces the use of the Object
 * monitor methods.
 *
 * <p>Conditions (also known as <em>condition queues</em> or
 * <em>condition variables</em>) provide a means for one thread to
 * suspend execution (to &quot;wait&quot;) until notified by another
 * thread that some state condition may now be true.  Because access
 * to this shared state information occurs in different threads, it
 * must be protected, so a lock of some form is associated with the
 * condition. The key property that waiting for a condition provides
 * is that it <em>atomically</em> releases the associated lock and
 * suspends the current thread, just like {@code Object.wait}.
 *
 * <p>A {@code Condition} instance is intrinsically bound to a lock.
 * To obtain a {@code Condition} instance for a particular {@link Lock}
 * instance use its {@link Lock#newCondition newCondition()} method.
 *
 * <p>As an example, suppose we have a bounded buffer which supports
 * {@code put} and {@code take} methods.  If a
 * {@code take} is attempted on an empty buffer, then the thread will block
 * until an item becomes available; if a {@code put} is attempted on a
 * full buffer, then the thread will block until a space becomes available.
 * We would like to keep waiting {@code put} threads and {@code take}
 * threads in separate wait-sets so that we can use the optimization of
 * only notifying a single thread at a time when items or spaces become
 * available in the buffer. This can be achieved using two
 * {@link Condition} instances.
 * <pre>
 * class BoundedBuffer {
 *   <b>final Lock lock = new ReentrantLock();</b>
 *   final Condition notFull  = <b>lock.newCondition(); </b>
 *   final Condition notEmpty = <b>lock.newCondition(); </b>
 *
 *   final Object[] items = new Object[100];
 *   int putptr, takeptr, count;
 *
 *   public void put(Object x) throws InterruptedException {
 *     <b>lock.lock();
 *     try {</b>
 *       while (count == items.length)
 *         <b>notFull.await();</b>
 *       items[putptr] = x;
 *       if (++putptr == items.length) putptr = 0;
 *       ++count;
 *       <b>notEmpty.signal();</b>
 *     <b>} finally {
 *       lock.unlock();
 *     }</b>
 *   }
 *
 *   public Object take() throws InterruptedException {
 *     <b>lock.lock();
 *     try {</b>
 *       while (count == 0)
 *         <b>notEmpty.await();</b>
 *       Object x = items[takeptr];
 *       if (++takeptr == items.length) takeptr = 0;
 *       --count;
 *       <b>notFull.signal();</b>
 *       return x;
 *     <b>} finally {
 *       lock.unlock();
 *     }</b>
 *   }
 * }
 * </pre>
 *
 * (The {@link java.util.concurrent.ArrayBlockingQueue} class provides
 * this functionality, so there is no reason to implement this
 * sample usage class.)
 *
 * <p>A {@code Condition} implementation can provide behavior and semantics
 * that is
 * different from that of the {@code Object} monitor methods, such as
 * guaranteed ordering for notifications, or not requiring a lock to be held
 * when performing notifications.
 * If an implementation provides such specialized semantics then the
 * implementation must document those semantics.
 *
 * <p>Note that {@code Condition} instances are just normal objects and can
 * themselves be used as the target in a {@code synchronized} statement,
 * and can have their own monitor {@link Object#wait wait} and
 * {@link Object#notify notification} methods invoked.
 * Acquiring the monitor lock of a {@code Condition} instance, or using its
 * monitor methods, has no specified relationship with acquiring the
 * {@link Lock} associated with that {@code Condition} or the use of its
 * {@linkplain #await waiting} and {@linkplain #signal signalling} methods.
 * It is recommended that to avoid confusion you never use {@code Condition}
 * instances in this way, except perhaps within their own implementation.
 *
 * <p>Except where noted, passing a {@code null} value for any parameter
 * will result in a {@link NullPointerException} being thrown.
 *
 * <h3>Implementation Considerations</h3>
 *
 * <p>When waiting upon a {@code Condition}, a &quot;<em>spurious
 * wakeup</em>&quot; is permitted to occur, in
 * general, as a concession to the underlying platform semantics.
 * This has little practical impact on most application programs as a
 * {@code Condition} should always be waited upon in a loop, testing
 * the state predicate that is being waited for.  An implementation is
 * free to remove the possibility of spurious wakeups but it is
 * recommended that applications programmers always assume that they can
 * occur and so always wait in a loop.
 *
 * <p>The three forms of condition waiting
 * (interruptible, non-interruptible, and timed) may differ in their ease of
 * implementation on some platforms and in their performance characteristics.
 * In particular, it may be difficult to provide these features and maintain
 * specific semantics such as ordering guarantees.
 * Further, the ability to interrupt the actual suspension of the thread may
 * not always be feasible to implement on all platforms.
 *
 * <p>Consequently, an implementation is not required to define exactly the
 * same guarantees or semantics for all three forms of waiting, nor is it
 * required to support interruption of the actual suspension of the thread.
 *
 * <p>An implementation is required to
 * clearly document the semantics and guarantees provided by each of the
 * waiting methods, and when an implementation does support interruption of
 * thread suspension then it must obey the interruption semantics as defined
 * in this interface.
 *
 * <p>As interruption generally implies cancellation, and checks for
 * interruption are often infrequent, an implementation can favor responding
 * to an interrupt over normal method return. This is true even if it can be
 * shown that the interrupt occurred after another action that may have
 * unblocked the thread. An implementation should document this behavior.
 *
 * @since 1.5
 * @author Doug Lea
 */
public interface Condition {

    /**
     * Causes the current thread to wait until it is signalled or
     * {@linkplain Thread#interrupt interrupted}.
     *
     * <p>The lock associated with this {@code Condition} is atomically
     * released and the current thread becomes disabled for thread scheduling
     * purposes and lies dormant until <em>one</em> of four things happens:
     * <ul>
     * <li>Some other thread invokes the {@link #signal} method for this
     * {@code Condition} and the current thread happens to be chosen as the
     * thread to be awakened; or
     * <li>Some other thread invokes the {@link #signalAll} method for this
     * {@code Condition}; or
     * <li>Some other thread {@linkplain Thread#interrupt interrupts} the
     * current thread, and interruption of thread suspension is supported; or
     * <li>A &quot;<em>spurious wakeup</em>&quot; occurs.
     * </ul>
     *
     * <p>In all cases, before this method can return the current thread must
     * re-acquire the lock associated with this condition. When the
     * thread returns it is <em>guaranteed</em> to hold this lock.
     *
     * <p>If the current thread:
     * <ul>
     * <li>has its interrupted status set on entry to this method; or
     * <li>is {@linkplain Thread#interrupt interrupted} while waiting
     * and interruption of thread suspension is supported,
     * </ul>
     * then {@link InterruptedException} is thrown and the current thread's
     * interrupted status is cleared. It is not specified, in the first
     * case, whether or not the test for interruption occurs before the lock
     * is released.
     *
     * <p><b>Implementation Considerations</b>
     *
     * <p>The current thread is assumed to hold the lock associated with this
     * {@code Condition} when this method is called.
     * It is up to the implementation to determine if this is
     * the case and if not, how to respond. Typically, an exception will be
     * thrown (such as {@link IllegalMonitorStateException}) and the
     * implementation must document that fact.
     *
     * <p>An implementation can favor responding to an interrupt over normal
     * method return in response to a signal. In that case the implementation
     * must ensure that the signal is redirected to another waiting thread, if
     * there is one.
     *
     * @throws InterruptedException if the current thread is interrupted
     *         (and interruption of thread suspension is supported)
     */
    void await() throws InterruptedException;

    /**
     * Causes the current thread to wait until it is signalled.
     *
     * <p>The lock associated with this condition is atomically
     * released and the current thread becomes disabled for thread scheduling
     * purposes and lies dormant until <em>one</em> of three things happens:
     * <ul>
     * <li>Some other thread invokes the {@link #signal} method for this
     * {@code Condition} and the current thread happens to be chosen as the
     * thread to be awakened; or
     * <li>Some other thread invokes the {@link #signalAll} method for this
     * {@code Condition}; or
     * <li>A &quot;<em>spurious wakeup</em>&quot; occurs.
     * </ul>
     *
     * <p>In all cases, before this method can return the current thread must
     * re-acquire the lock associated with this condition. When the
     * thread returns it is <em>guaranteed</em> to hold this lock.
     *
     * <p>If the current thread's interrupted status is set when it enters
     * this method, or it is {@linkplain Thread#interrupt interrupted}
     * while waiting, it will continue to wait until signalled. When it finally
     * returns from this method its interrupted status will still
     * be set.
     *
     * <p><b>Implementation Considerations</b>
     *
     * <p>The current thread is assumed to hold the lock associated with this
     * {@code Condition} when this method is called.
     * It is up to the implementation to determine if this is
     * the case and if not, how to respond. Typically, an exception will be
     * thrown (such as {@link IllegalMonitorStateException}) and the
     * implementation must document that fact.
     */
    void awaitUninterruptibly();

    /**
     * Causes the current thread to wait until it is signalled or interrupted,
     * or the specified waiting time elapses.
     *
     * <p>The lock associated with this condition is atomically
     * released and the current thread becomes disabled for thread scheduling
     * purposes and lies dormant until <em>one</em> of five things happens:
     * <ul>
     * <li>Some other thread invokes the {@link #signal} method for this
     * {@code Condition} and the current thread happens to be chosen as the
     * thread to be awakened; or
     * <li>Some other thread invokes the {@link #signalAll} method for this
     * {@code Condition}; or
     * <li>Some other thread {@linkplain Thread#interrupt interrupts} the
     * current thread, and interruption of thread suspension is supported; or
     * <li>The specified waiting time elapses; or
     * <li>A &quot;<em>spurious wakeup</em>&quot; occurs.
     * </ul>
     *
     * <p>In all cases, before this method can return the current thread must
     * re-acquire the lock associated with this condition. When the
     * thread returns it is <em>guaranteed</em> to hold this lock.
     *
     * <p>If the current thread:
     * <ul>
     * <li>has its interrupted status set on entry to this method; or
     * <li>is {@linkplain Thread#interrupt interrupted} while waiting
     * and interruption of thread suspension is supported,
     * </ul>
     * then {@link InterruptedException} is thrown and the current thread's
     * interrupted status is cleared. It is not specified, in the first
     * case, whether or not the test for interruption occurs before the lock
     * is released.
     *
     * <p>The method returns an estimate of the number of nanoseconds
     * remaining to wait given the supplied {@code nanosTimeout}
     * value upon return, or a value less than or equal to zero if it
     * timed out. This value can be used to determine whether and how
     * long to re-wait in cases where the wait returns but an awaited
     * condition still does not hold. Typical uses of this method take
     * the following form:
     *
     *  <pre> {@code
     * boolean aMethod(long timeout, TimeUnit unit) {
     *   long nanos = unit.toNanos(timeout);
     *   lock.lock();
     *   try {
     *     while (!conditionBeingWaitedFor()) {
     *       if (nanos <= 0L)
     *         return false;
     *       nanos = theCondition.awaitNanos(nanos);
     *     }
     *     // ...
     *   } finally {
     *     lock.unlock();
     *   }
     * }}</pre>
     *
     * <p>Design note: This method requires a nanosecond argument so
     * as to avoid truncation errors in reporting remaining times.
     * Such precision loss would make it difficult for programmers to
     * ensure that total waiting times are not systematically shorter
     * than specified when re-waits occur.
     *
     * <p><b>Implementation Considerations</b>
     *
     * <p>The current thread is assumed to hold the lock associated with this
     * {@code Condition} when this method is called.
     * It is up to the implementation to determine if this is
     * the case and if not, how to respond. Typically, an exception will be
     * thrown (such as {@link IllegalMonitorStateException}) and the
     * implementation must document that fact.
     *
     * <p>An implementation can favor responding to an interrupt over normal
     * method return in response to a signal, or over indicating the elapse
     * of the specified waiting time. In either case the implementation
     * must ensure that the signal is redirected to another waiting thread, if
     * there is one.
     *
     * @param nanosTimeout the maximum time to wait, in nanoseconds
     * @return an estimate of the {@code nanosTimeout} value minus
     *         the time spent waiting upon return from this method.
     *         A positive value may be used as the argument to a
     *         subsequent call to this method to finish waiting out
     *         the desired time.  A value less than or equal to zero
     *         indicates that no time remains.
     * @throws InterruptedException if the current thread is interrupted
     *         (and interruption of thread suspension is supported)
     */
    long awaitNanos(long nanosTimeout) throws InterruptedException;

    /**
     * Causes the current thread to wait until it is signalled or interrupted,
     * or the specified waiting time elapses. This method is behaviorally
     * equivalent to:
     *  <pre> {@code awaitNanos(unit.toNanos(time)) > 0}</pre>
     *
     * @param time the maximum time to wait
     * @param unit the time unit of the {@code time} argument
     * @return {@code false} if the waiting time detectably elapsed
     *         before return from the method, else {@code true}
     * @throws InterruptedException if the current thread is interrupted
     *         (and interruption of thread suspension is supported)
     */
    boolean await(long time, TimeUnit unit) throws InterruptedException;

    /**
     * Causes the current thread to wait until it is signalled or interrupted,
     * or the specified deadline elapses.
     *
     * <p>The lock associated with this condition is atomically
     * released and the current thread becomes disabled for thread scheduling
     * purposes and lies dormant until <em>one</em> of five things happens:
     * <ul>
     * <li>Some other thread invokes the {@link #signal} method for this
     * {@code Condition} and the current thread happens to be chosen as the
     * thread to be awakened; or
     * <li>Some other thread invokes the {@link #signalAll} method for this
     * {@code Condition}; or
     * <li>Some other thread {@linkplain Thread#interrupt interrupts} the
     * current thread, and interruption of thread suspension is supported; or
     * <li>The specified deadline elapses; or
     * <li>A &quot;<em>spurious wakeup</em>&quot; occurs.
     * </ul>
     *
     * <p>In all cases, before this method can return the current thread must
     * re-acquire the lock associated with this condition. When the
     * thread returns it is <em>guaranteed</em> to hold this lock.
     *
     *
     * <p>If the current thread:
     * <ul>
     * <li>has its interrupted status set on entry to this method; or
     * <li>is {@linkplain Thread#interrupt interrupted} while waiting
     * and interruption of thread suspension is supported,
     * </ul>
     * then {@link InterruptedException} is thrown and the current thread's
     * interrupted status is cleared. It is not specified, in the first
     * case, whether or not the test for interruption occurs before the lock
     * is released.
     *
     *
     * <p>The return value indicates whether the deadline has elapsed,
     * which can be used as follows:
     *  <pre> {@code
     * boolean aMethod(Date deadline) {
     *   boolean stillWaiting = true;
     *   lock.lock();
     *   try {
     *     while (!conditionBeingWaitedFor()) {
     *       if (!stillWaiting)
     *         return false;
     *       stillWaiting = theCondition.awaitUntil(deadline);
     *     }
     *     // ...
     *   } finally {
     *     lock.unlock();
     *   }
     * }}</pre>
     *
     * <p><b>Implementation Considerations</b>
     *
     * <p>The current thread is assumed to hold the lock associated with this
     * {@code Condition} when this method is called.
     * It is up to the implementation to determine if this is
     * the case and if not, how to respond. Typically, an exception will be
     * thrown (such as {@link IllegalMonitorStateException}) and the
     * implementation must document that fact.
     *
     * <p>An implementation can favor responding to an interrupt over normal
     * method return in response to a signal, or over indicating the passing
     * of the specified deadline. In either case the implementation
     * must ensure that the signal is redirected to another waiting thread, if
     * there is one.
     *
     * @param deadline the absolute time to wait until
     * @return {@code false} if the deadline has elapsed upon return, else
     *         {@code true}
     * @throws InterruptedException if the current thread is interrupted
     *         (and interruption of thread suspension is supported)
     */
    boolean awaitUntil(Date deadline) throws InterruptedException;

    /**
     * Wakes up one waiting thread.
     *
     * <p>If any threads are waiting on this condition then one
     * is selected for waking up. That thread must then re-acquire the
     * lock before returning from {@code await}.
     *
     * <p><b>Implementation Considerations</b>
     *
     * <p>An implementation may (and typically does) require that the
     * current thread hold the lock associated with this {@code
     * Condition} when this method is called. Implementations must
     * document this precondition and any actions taken if the lock is
     * not held. Typically, an exception such as {@link
     * IllegalMonitorStateException} will be thrown.
     */
    void signal();

    /**
     * Wakes up all waiting threads.
     *
     * <p>If any threads are waiting on this condition then they are
     * all woken up. Each thread must re-acquire the lock before it can
     * return from {@code await}.
     *
     * <p><b>Implementation Considerations</b>
     *
     * <p>An implementation may (and typically does) require that the
     * current thread hold the lock associated with this {@code
     * Condition} when this method is called. Implementations must
     * document this precondition and any actions taken if the lock is
     * not held. Typically, an exception such as {@link
     * IllegalMonitorStateException} will be thrown.
     */
    void signalAll();
}

三、线程间的通信

案例:

有两个线程,对初始值为0的变量进行操作。一个线程对变量+1,一个线程对变量-1

public class Communicate {
    public static void main(String[] args) {
        Share share = new Share();
        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    share.increase();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();

        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    share.deincrease();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
    }
}

class Share {
    private int number = 0;

    //+1方法
    public synchronized void increase() throws InterruptedException {
        if (number != 0) {
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName() + ":" + number);
        this.notifyAll();
    }

    //-1方法
    public synchronized void deincrease() throws InterruptedException {
        if (number != 1) {
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName() + ":" + number);
        this.notifyAll();
    }

}

在上述代码中再加入两个线程,一个线程+1,另一个则-1。再次运行程序,会发现除了1和0之外,还有其他值,这种情况被称为虚假唤醒。

wait()特点:在哪里睡,就在哪里醒,醒之后不会再进行if的条件判断,就会出现异常情况。

有4个线程:A,B,C,D。A +1,B -1,C +1,D -1。设初始值为0,若A线程先抢到锁,则对值+1。释放锁,若C抢到锁,发现值为1,执行wait(),唤醒其他线程。若A在此抢到锁,则发现值为1,执行wait(),并唤醒其他线程。若C再次抢到线程,从wait()处被唤醒,继续执行后续的number ++,就会产生虚假唤醒的情况。我们可以通过将判断条件放在while循环中,即使从wait()处被唤醒,也会再次进行判断。

As in the one argument version, interrupts and spurious wakeups are possible, and this method should always be used in a loop:

     synchronized (obj) {
         while (<condition does not hold>)
             obj.wait();
         ... // Perform action appropriate to condition
     }

通过lock方式实现上述案例

public class CommunicateByLock {
    public static void main(String[] args) {
        LShare lShare = new LShare();
        new Thread(()-> {
            for (int i = 1; i <= 10; i ++){
                try {
                    lShare.incr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"AA").start();

        new Thread(() -> {
            for(int i = 1; i <= 10; i ++){
                try {
                    lShare.decr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"BB").start();

        new Thread(()-> {
            for (int i = 1; i <= 10; i ++){
                try {
                    lShare.incr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"CC").start();

        new Thread(() -> {
            for(int i = 1; i <= 10; i ++){
                try {
                    lShare.decr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"DD").start();
    }
}

class LShare {

    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    private int number = 0;

    public void incr() throws InterruptedException {
        //加锁
        lock.lock();
        try {
            while (number != 0){
                condition.await();//线程wait
            }
            number ++;
            System.out.println(Thread.currentThread().getName() + ":" + number);
            condition.signalAll();//唤醒其他线程
        } finally {
            //解锁
            lock.unlock();
        }
    }

    public void decr() throws InterruptedException {
        lock.lock();

        try {
            while (number != 1){
                condition.await();
            }
            number --;
            System.out.println(Thread.currentThread().getName() + ":" + number);
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }
}

四、线程间的定制化通信

案例:

有3个线程,AA,BB,CC。首先AA打印2次,然后BB打印3次,然后CC打印4次,然后循环上述顺序10次。

在这里插入图片描述

package cn.nl.juc.custom_communication;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 有3个线程,AA,BB,CC。首先AA打印2次,然后BB打印3次,然后CC打印4次,然后循环上述顺序10次。
 */
public class CusCommunication {
    public static void main(String[] args) {
        Resource resource = new Resource();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    resource.print2(i + 1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "AA").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    resource.print3(i + 1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "BB").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    resource.print4(i+1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "CC").start();
    }

}

class Resource {
    //定义标志位 AA 1  BB 2  CC 3
    private int flag = 1;
    //重入锁
    private Lock lock = new ReentrantLock();

    //创建三个condition
    private Condition ca = lock.newCondition();
    private Condition cb = lock.newCondition();
    private Condition cc = lock.newCondition();

    /**
     * 打印2次
     *
     * @param count 第几次打印
     */
    public void print2(int count) throws InterruptedException {
        lock.lock();

        try {
            while (flag != 1) {
                ca.await();
            }
            for (int i = 0; i < 2; i++) {
                System.out.println(Thread.currentThread().getName() + ",第" + count + "轮次打印,打印值:" + i);
            }
            flag = 2;//修改标志位
            cb.signal();//唤醒BB线程
        } finally {
            lock.unlock();
        }
    }

    public void print3(int count) throws InterruptedException {
        lock.lock();

        try {
            while (flag != 2) {
                cb.await();
            }
            for (int i = 0; i < 3; i++) {
                System.out.println(Thread.currentThread().getName() + ",第" + count + "轮次打印,打印值:" + i);
            }
            flag = 3;
            cc.signal();
        } finally {
            lock.unlock();
        }
    }

    public void print4(int count) throws InterruptedException {
        lock.lock();
        try {
            while (flag != 3) {
                cc.await();
            }
            for (int i = 0; i < 4; i++) {
                System.out.println(Thread.currentThread().getName() + ",第" + count + "轮次打印,打印值:" + i);
            }
            flag = 1;
            ca.signal();
        } finally {
            lock.unlock();
        }
    }
}

五、集合的线程安全

list线程不安全

1. list线程不安全
List<String> list = new ArrayList<>();
for(int i = 0; i < 90; i ++){
    new Thread(() -> {
        list.add(UUID.randomUUID().toString().substring(0,4));
        System.out.println("线程" + Thread.currentThread().getName() + ",集合:" + list);
    },Integer.toString(i)).start();
}

会出现java.util.ConcurrentModificationException异常,原因是由于add()没有加synchronized关键字。

2. Vector

通过Vector集合解决上述问题

Vector<String> collection = new Vector<>();
for (int i = 0; i < 60; i++) {
    new Thread(() ->{
        collection.add(UUID.randomUUID().toString().substring(0,3));
        System.out.println("线程:" + Thread.currentThread().getName() + ",集合:" + collection);
    },Integer.toString(i)).start();
}
3. Collections
List<String> list = new ArrayList<>();
List<String> collection = Collections.synchronizedList(list);
for (int i = 0; i < 1000; i++) {
    new Thread(() -> {
        collection.add(UUID.randomUUID().toString().substring(0,4));
    }).start();
}
4. CopyOnWriteArrayList

java.util.concurrent.CopyOnWriteArrayList

Collection collection = new CopyOnWriteArrayList<String>();
for (int i = 0; i < 1000; i++) {
    new Thread(() -> {
        collection.add(UUID.randomUUID().toString().substring(0,4));
    }).start();
}

写时复制技术

写之前,复制一份数据,然后往赋值的内容中写入新的数据,最终合并复制数据和原始数据。

HashSet和HashMap线程不安全

HashSet
Set<String> set = new HashSet<>();
for(int i = 0; i < 10000; i ++){
    new Thread(() -> {
        set.add(UUID.randomUUID().toString().substring(0,4));
        System.out.println(Thread.currentThread().getName() + ",集合:" + set);
    }).start();
}

同样也会发生java.util.ConcurrentModificationException的问题。

解决方式

使用CopyOnWriteArraySet.

Set<String> set = new CopyOnWriteArraySet<>();//将上述中set创建改为CopyOnWriteArraySet
HashMap
Map<String, String> map = new HashMap<>();
for (int i = 0; i < 10000; i++) {
    String key = String.valueOf(i);
    new Thread(() -> {
        map.put(key, UUID.randomUUID().toString().substring(0, 4));
        System.out.println(Thread.currentThread().getName() + ",map:" + map);
    }).start();
}

同样的问题:java.util.ConcurrentModificationException

解决方式

Map<String, String> map = new ConcurrentHashMap<>();

六、多线程锁

1. 不同锁的范围

案例:

class Phone {
    public synchronized void sendSMS() throws Exception {
        System.out.println("---sendSMS---");
    }

    public synchronized void sendEmail() throws Exception {

        System.out.println("---sendEmail---");
    }

    public void getHello() throws Exception {
        System.out.println("---getHello---");
    }
}

1.标准访问,先打印短信还是邮件。

Phone phone = new Phone();
new Thread(() -> {
    try {
        phone.sendSMS();
    } catch (Exception e) {
        e.printStackTrace();
    }
},"A").start();

Thread.sleep(100);

new Thread(()->{
    try {
        phone.sendEmail();
    } catch (Exception e) {
        e.printStackTrace();
    }
},"B").start();

//结果
---sendSMS---
---sendEmail---

2.停4秒在短信方法内,先打印短信还是邮件

class Phone {
    public synchronized void sendSMS() throws Exception {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("---sendSMS---");
    }

    public synchronized void sendEmail() throws Exception {

        System.out.println("---sendEmail---");
    }

    public void getHello() throws Exception {
        System.out.println("---getHello---");
    }
}

public class EightLock {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();

        new Thread(() -> {
            try {
                phone.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"A").start();

        Thread.sleep(100);

        new Thread(()->{
            try {
                phone.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"B").start();
    }

}
//结果
---sendSMS---
---sendEmail---

同步方法,锁的是当前对象也,就是this。在上述例子中,this就是phone,当一个同步方法操作时,另一个方法就只能等待。

3.新增普通的hello方法,先打印短信还是hello

class Phone {
    public synchronized void sendSMS() throws Exception {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("---sendSMS---");
    }

    public synchronized void sendEmail() throws Exception {

        System.out.println("---sendEmail---");
    }

    public void getHello() throws Exception {
        System.out.println("---getHello---");
    }
}

public class EightLock {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();

        new Thread(() -> {
            try {
                phone.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"A").start();

        Thread.sleep(100);

        new Thread(()->{
            try {
//                phone.sendEmail();
                phone.getHello();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"B").start();
    }
}

//结果
---getHello---
---sendSMS---`

普通方法没有加锁,而同步方法又等待了4秒,所以普通方法先输出

4.有两部手机,先打印短信还是邮件

class Phone {
    public synchronized void sendSMS() throws Exception {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("---sendSMS---");
    }

    public synchronized void sendEmail() throws Exception {

        System.out.println("---sendEmail---");
    }

    public void getHello() throws Exception {
        System.out.println("---getHello---");
    }
}

public class EightLock {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        Phone phone1 = new Phone();

        new Thread(() -> {
            try {
                phone.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"A").start();

        Thread.sleep(100);

        new Thread(()->{
            try {
//                phone.sendEmail();
//                phone.getHello();
                phone1.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"B").start();
    }

}

//结果
---sendEmail---
---sendSMS---

两个手机即两把锁,两个同步方法执行时,不影响,但由于sendSMS() sleep4秒,所以sendEmail()先执行。

5.两个静态同步方法,1部手机,先打印短信还是邮件

class Phone {
    public static synchronized void sendSMS() throws Exception {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("---sendSMS---");
    }

    public static synchronized void sendEmail() throws Exception {

        System.out.println("---sendEmail---");
    }

    public void getHello() throws Exception {
        System.out.println("---getHello---");
    }
}

public class EightLock {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        Phone phone1 = new Phone();

        new Thread(() -> {
            try {
                phone.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"A").start();

        Thread.sleep(100);

        new Thread(()->{
            try {
                phone.sendEmail();
//                phone.getHello();
//                phone1.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"B").start();
    }

}
//结果
---sendSMS---
---sendEmail---

6.两个静态同步方法,2部手机,先打印短信还是邮件

public class EightLock {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        Phone phone1 = new Phone();

        new Thread(() -> {
            try {
                phone.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"A").start();

        Thread.sleep(100);

        new Thread(()->{
            try {
//                phone.sendEmail();
//                phone.getHello();
                phone1.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"B").start();
    }

}

//结果
---sendSMS---
---sendEmail---

而当加入关键字static时,锁的对象是当前类的Class对象。

7.1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件

class Phone {
    public static synchronized void sendSMS() throws Exception {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("---sendSMS---");
    }

    public /*static*/ synchronized void sendEmail() throws Exception {

        System.out.println("---sendEmail---");
    }

    public void getHello() throws Exception {
        System.out.println("---getHello---");
    }
}

public class EightLock {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        Phone phone1 = new Phone();

        new Thread(() -> {
            try {
                phone.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"A").start();

        Thread.sleep(100);

        new Thread(()->{
            try {
                phone.sendEmail();
//                phone.getHello();
//                phone1.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"B").start();
    }

}

//结果
---sendEmail---
---sendSMS---

8.1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件

class Phone {
    public static synchronized void sendSMS() throws Exception {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("---sendSMS---");
    }

    public /*static*/ synchronized void sendEmail() throws Exception {

        System.out.println("---sendEmail---");
    }

    public void getHello() throws Exception {
        System.out.println("---getHello---");
    }
}

public class EightLock {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        Phone phone1 = new Phone();

        new Thread(() -> {
            try {
                phone.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"A").start();

        Thread.sleep(100);

        new Thread(()->{
            try {
//                phone.sendEmail();
//                phone.getHello();
                phone1.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"B").start();
    }

}

//结果
---sendEmail---
---sendSMS---

静态同步方法和普通同步方法锁的对象分别是当前类的Class对象和当前对象this.

synchronized实现同步,Java中的每一个对象都可以作为锁。

总结:

  • 普通方法,锁的是当前实例对象
  • 静态方法,锁的是当前类的Class对象
  • 同步代码块,锁的是synchronized括号中的对象

2. 公平锁和非公平锁

在之前卖票案例中,创建ReentrantLock时,

若使用无参构造器,则创建非公平锁。

若使用有参构造器,参数boolean fair为false时,为非公平锁;若参数为true时,为公平锁。

使用非公平锁时,我们发现一个线程执行了所有任务。使用公平锁时,每个线程都可以获得执行任务的机会。

private final ReentrantLock lock = new ReentrantLock(true);

非公平锁:线程“饿死”,效率高

公平锁:所有线程都能得到资源,效率相对较低

3. 可重入锁

synchronized关键字和Lock都是可重入锁。synchronized为隐式的可重入锁,Lock为显式的可重入锁。

可重入锁又被称为递归锁。

当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的。

synchronized

public static void main(String[] args) {
    new RecursionLock().add();//StackOverflowError
    Object o = new Object();
    new Thread(() -> {
        synchronized (o){
            System.out.println(Thread.currentThread().getName() + "外层");
            synchronized (o){
                System.out.println(Thread.currentThread().getName() + "中层");
                synchronized(o){
                    System.out.println(Thread.currentThread().getName() + "内层");
                }
            }
        }
    },"x").start();
}
public synchronized void add(){
    add();
}

Lock

Lock lock = new ReentrantLock();

new Thread(() -> {
    try {
        lock.lock();//加锁
        System.out.println(Thread.currentThread().getName() + ",外层");
        try {
            lock.lock();//加锁
            System.out.println(Thread.currentThread().getName() + ",内层");
        } finally {
            lock.unlock();//解锁
        }
    } finally {
        lock.unlock();//解锁
    }
}).start();

上述代码中,如果注释掉第一次解锁,然后再加一个线程,则会发现另外一个线程的任务无法执行。

4. 死锁

两个或两个以上进程在执行过程中,因为争夺资源而造成一种互相等待的现象。

线程A持有锁A,线程B持有锁B,而这时线程A尝试获取锁B,线程B尝试获取锁A,由于两个线程没有释放对应的锁,所以就无法获取到对应锁,导致等待。

原因:

1). 系统资源不足

2). 程序运行中,推进顺序不合适

3). 资源分配不当

验证死锁:

i. 命令jps

ii. 命令jstack(JVM自带的堆栈跟踪工具)

步骤:

使用jps -l命令,找到我们自己的类,并几下前面的进程号

16035 org.jetbrains.jps.cmdline.Launcher
31061 org.jetbrains.kotlin.daemon.KotlinCompileDaemon
16038 cn.nl.juc.multiple_thread_lock.DeadLock
16200 jdk.jcmd/sun.tools.jps.Jps
7180 com.intellij.idea.Main

使用jstack+进程号,查看堆栈信息

# ...
"Thread-1" #12 prio=5 os_prio=0 tid=0x00007fcec42ff000 nid=0x3ed2 waiting for monitor entry [0x00007fcea86c4000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at cn.nl.juc.multiple_thread_lock.DeadLock.lambda$main$1(DeadLock.java:34)
        - waiting to lock <0x00000000dbcff0c8> (a java.lang.Object)
        - locked <0x00000000dbcff0d8> (a java.lang.Object)
        at cn.nl.juc.multiple_thread_lock.DeadLock$$Lambda$2/1747585824.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)

"Thread-0" #11 prio=5 os_prio=0 tid=0x00007fcec42fd800 nid=0x3ed1 waiting for monitor entry [0x00007fcea87c5000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at cn.nl.juc.multiple_thread_lock.DeadLock.lambda$main$0(DeadLock.java:20)
        - waiting to lock <0x00000000dbcff0d8> (a java.lang.Object)
        - locked <0x00000000dbcff0c8> (a java.lang.Object)
        at cn.nl.juc.multiple_thread_lock.DeadLock$$Lambda$1/1096979270.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)
# ...
Found 1 deadlock.

首先我们在最后一行可以看到,发现1个死锁。

然后,在上面可以看到"Thread-1" -waiting to lock <0x00000000dbcff0c8>,locked <0x00000000dbcff0d8>,

“Thread-2” - waiting to lock <0x00000000dbcff0d8>,- locked <0x00000000dbcff0c8>。可以发现,它们持有对方将要获取的锁。

七、Callable接口

线程创建的方式:

1.继承Thread

2.实现Runnable

3.实现Callabe

4.线程池

/**
 * A task that returns a result and may throw an exception.
 * Implementors define a single method with no arguments called
 * {@code call}.
 *
 * <p>The {@code Callable} interface is similar to {@link
 * java.lang.Runnable}, in that both are designed for classes whose
 * instances are potentially executed by another thread.  A
 * {@code Runnable}, however, does not return a result and cannot
 * throw a checked exception.
 *
 * <p>The {@link Executors} class contains utility methods to
 * convert from other common forms to {@code Callable} classes.
 *
 * @see Executor
 * @since 1.5
 * @author Doug Lea
 * @param <V> the result type of method {@code call}
 */
@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

Runnable和Callable接口区别:

RunnableCallable
无返回值有返回值
不能抛出异常能抛出异常
调用方法run调用call()

通过FutureTask来传递Callable接口。futureTask的get()可用来获取执行完成后的结果,isDone()可用来查询任务是否执行完成。

当有多个线程执行任务时,某个线程任务量大,FutureTask会先获取其他线程执行结果,最后再来获取该线程的结果,最终进行汇总。

通过Callable常见线程

FutureTask futureTask1 = new FutureTask(()-> 2009);
new Thread(futureTask1,"f").start();
System.out.println(futureTask1.get());

八、JUC辅助类

1. CountDownLatch

CountDownLatch可以设置一个计数器,然后通过countDown方法来进行减1的操作,使用await()等待计数器不大于0,然后继续执行await()之后的语句。

  • CountDownLatch主要有两个方法,当一个或多个线程调用await()方法时,这些线程会阻塞。
  • 其他线程调用countDown方法会将计数器减1(调用countDown()的线程不会阻塞)
  • 当计数器的值变为0时,因await()阻塞的线程会被唤醒,继续执行。

案例:

CountDownLatch countDownLatch = new CountDownLatch(6);

//6个同学陆续离开教室,班长锁门
for (int i = 1; i <= 6; i++) {
    new Thread(()->{
        System.out.println(Thread.currentThread().getName() + "号同学离开教室");
        countDownLatch.countDown();
    },String.valueOf(i)).start();
}
countDownLatch.await();
System.out.println(Thread.currentThread().getName() + "完成");

2. CyclicBarrier

在使用CyclicBarrier的构造方法第一个参数是目标障碍数,每次执行CyclicBarrier一次障碍数就会加一,如果达到了目标障碍数,才会执行cyclicBarrier.await()之后的语句。

private static final int NUMBER = 7;

public static void main(String[] args) {
    CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER,() -> {
        System.out.println("召唤神龙");
    });

    for(int i =1; i <= 7; i ++){
        new Thread(() -> {
            try {
                System.out.println("龙珠" + Thread.currentThread().getName() + "收集完成。");
                cyclicBarrier.await();
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
        },String.valueOf(i)).start();
    }
}

3. Semaphore

A counting semaphore.  Conceptually, a semaphore maintains a set of
permits.  Each acquire blocks if necessary until a permit is
available, and then takes it.  Each release adds a permit,
potentially releasing a blocking acquirer.
However, no actual permit objects are used; the Semaphore just
keeps a count of the number available and acts accordingly.

semaphore维护着许多许可证,线程想要获取锁,则必须获得许可证。
案例:

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

/**
 * Semaphore辅助类
 *
 * 案例:6辆车,停在3个停车位上。
 */
public class SemaphoreDemo {
    public static void main(String[] args) {
        //模拟3个停车位
        Semaphore semaphore = new Semaphore(3);

        //模拟6辆车
        for (int i = 0; i <= 6; i++) {
            new Thread(() -> {
                try {
                    //抢占,获取许可后,其他线程只能等待
                    semaphore.acquire();
                    System.out.println("==>" + Thread.currentThread().getName() + "号车抢占成功!");

                    //设置随机停车时间
                    TimeUnit.SECONDS.sleep(new Random().nextInt(5));
                    System.out.println("<==" + Thread.currentThread().getName() + "号车离开车位!");

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();//释放
                }
            },String.valueOf(i)).start();
        }
    }
}

九、ReentrantReadWriteLock读写锁

悲观锁与乐观锁

悲观锁:

​ 每次对数据操作时,都认为数据会被修改,所以在获取数据时会对数据进行加锁操作。

​ 共享资源一次只能给一个线程使用,其他线程阻塞,使用结束后,再将资源转给其他线程。

乐观锁:

​ 每次对数据操作时,都认为数据不会被修改,所以不会进行加锁操作。但在更新的时候会判断一下此期间数据有没有被修改。可以使用版本号机制和CAS算法实现。

表锁和行锁

表锁:

​ 对数据表中的记录进行操作时,会对整张表进行加锁操作。

行锁:

​ 对数据表中的记录进行操作时,只对需要更新的记录进行加锁操作。

行锁可能发生死锁。

读锁与写锁

读写锁:一个资源可以被多个读线程访问,或者一个写线程访问。但不能同时存在读写线程,读写互斥,读读共享。

读锁:共享锁 写锁:独占锁

都会发生死锁。

共享锁死锁:

在这里插入图片描述

读写锁案例:模拟缓存

public class ReadWriteLockDemo {
    public static void main(String[] args) {
        CacheDemo cache = new CacheDemo();
        for (int i = 1; i <= 5; i++) {
            final int num = i;
            new Thread(() -> {
                cache.put(num + "", num + "");
            },String.valueOf(i)).start();
        }

        for (int i = 1; i <= 5; i++) {
            final int num = i;
            new Thread(() -> {
                cache.get(num + "");
            },String.valueOf(i)).start();
        }

    }
}

class CacheDemo {
    private Map<String, Object> map = new HashMap<>();

    //创建读写锁
    private ReadWriteLock lock = new ReentrantReadWriteLock();

    //放数据
    public void put(String key, Object value) {
        lock.writeLock().lock();
        System.out.println(Thread.currentThread().getName() + "正在写操作,key:" + key);

        try {
            TimeUnit.MICROSECONDS.sleep(300);
            //放数据
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "线程写入完成。");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.writeLock().unlock();
        }


    }

    //取数据
    public Object get(String key) {
        lock.readLock().lock();
        Object res = null;
        System.out.println(Thread.currentThread().getName() + "线程正在读取数据,key:" + key);

        try {
            TimeUnit.MICROSECONDS.sleep(300);
            res = map.get(key);
            System.out.println(Thread.currentThread().getName() + "线程读取数据完成!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.readLock().unlock();
        }

        return res;
    }
}

读写锁的缺点:

  • 造成锁饥饿,一直读,没有写操作。

  • 读时,不能写;只有读完之后,才可以写,写操作可以读。

读写锁降级

将写入锁降级为读锁。

获取写锁 --> 获取读锁 -> 释放写锁 --> 释放读锁

ReadWriteLock lock = new ReentrantReadWriteLock();
//读锁
Lock readLock = lock.readLock();
//写锁
Lock writeLock = lock.writeLock();

//锁降级
//1.获取到读锁
writeLock.lock();
System.out.println("--写锁--");

//2.获取读锁
readLock.lock();
System.out.println("--读锁--");

//3.释放写锁
writeLock.unlock();

//4.释放读锁
readLock.unlock();

读锁不能升级为写锁

十、BlockingQueue阻塞队列

1. 阻塞队列概述

队列:先进先出

栈:后进先出

阻塞队列:通过一个共享队列,可以使得数据由队列的一端输入,一端输出。

在这里插入图片描述

特点:

​ 当队列是空的,从队列中获取元素的操作将会被阻塞。

​ 当队列是满的,从队列添加元素的操作将会被阻塞

​ 试图从空队列中获取元素的线程将会被阻塞,直到其他线程往空的队列插入新的元素。

​ 试图向已满的队列中添加新元素的线程将会被阻塞,直到其他线程从队列中移除一个或多个或完全情况,使队列变得空闲起来后续新增。

使用BlockingQueue的原因:不用关心阻塞线程和唤醒线程的时机。

2. 阻塞队列的架构

package java.util.concurrent;

import java.util.Collection;
import java.util.Queue;

/**
 * A {@link java.util.Queue} that additionally supports operations
 * that wait for the queue to become non-empty when retrieving an
 * element, and wait for space to become available in the queue when
 * storing an element.
 *
 * <p>{@code BlockingQueue} methods come in four forms, with different ways
 * of handling operations that cannot be satisfied immediately, but may be
 * satisfied at some point in the future:
 * one throws an exception, the second returns a special value (either
 * {@code null} or {@code false}, depending on the operation), the third
 * blocks the current thread indefinitely until the operation can succeed,
 * and the fourth blocks for only a given maximum time limit before giving
 * up.  These methods are summarized in the following table:
 *
 * <table BORDER CELLPADDING=3 CELLSPACING=1>
 * <caption>Summary of BlockingQueue methods</caption>
 *  <tr>
 *    <td></td>
 *    <td ALIGN=CENTER><em>Throws exception</em></td>
 *    <td ALIGN=CENTER><em>Special value</em></td>
 *    <td ALIGN=CENTER><em>Blocks</em></td>
 *    <td ALIGN=CENTER><em>Times out</em></td>
 *  </tr>
 *  <tr>
 *    <td><b>Insert</b></td>
 *    <td>{@link #add add(e)}</td>
 *    <td>{@link #offer offer(e)}</td>
 *    <td>{@link #put put(e)}</td>
 *    <td>{@link #offer(Object, long, TimeUnit) offer(e, time, unit)}</td>
 *  </tr>
 *  <tr>
 *    <td><b>Remove</b></td>
 *    <td>{@link #remove remove()}</td>
 *    <td>{@link #poll poll()}</td>
 *    <td>{@link #take take()}</td>
 *    <td>{@link #poll(long, TimeUnit) poll(time, unit)}</td>
 *  </tr>
 *  <tr>
 *    <td><b>Examine</b></td>
 *    <td>{@link #element element()}</td>
 *    <td>{@link #peek peek()}</td>
 *    <td><em>not applicable</em></td>
 *    <td><em>not applicable</em></td>
 *  </tr>
 * </table>
 *
 * <p>A {@code BlockingQueue} does not accept {@code null} elements.
 * Implementations throw {@code NullPointerException} on attempts
 * to {@code add}, {@code put} or {@code offer} a {@code null}.  A
 * {@code null} is used as a sentinel value to indicate failure of
 * {@code poll} operations.
 *
 * <p>A {@code BlockingQueue} may be capacity bounded. At any given
 * time it may have a {@code remainingCapacity} beyond which no
 * additional elements can be {@code put} without blocking.
 * A {@code BlockingQueue} without any intrinsic capacity constraints always
 * reports a remaining capacity of {@code Integer.MAX_VALUE}.
 *
 * <p>{@code BlockingQueue} implementations are designed to be used
 * primarily for producer-consumer queues, but additionally support
 * the {@link java.util.Collection} interface.  So, for example, it is
 * possible to remove an arbitrary element from a queue using
 * {@code remove(x)}. However, such operations are in general
 * <em>not</em> performed very efficiently, and are intended for only
 * occasional use, such as when a queued message is cancelled.
 *
 * <p>{@code BlockingQueue} implementations are thread-safe.  All
 * queuing methods achieve their effects atomically using internal
 * locks or other forms of concurrency control. However, the
 * <em>bulk</em> Collection operations {@code addAll},
 * {@code containsAll}, {@code retainAll} and {@code removeAll} are
 * <em>not</em> necessarily performed atomically unless specified
 * otherwise in an implementation. So it is possible, for example, for
 * {@code addAll(c)} to fail (throwing an exception) after adding
 * only some of the elements in {@code c}.
 *
 * <p>A {@code BlockingQueue} does <em>not</em> intrinsically support
 * any kind of &quot;close&quot; or &quot;shutdown&quot; operation to
 * indicate that no more items will be added.  The needs and usage of
 * such features tend to be implementation-dependent. For example, a
 * common tactic is for producers to insert special
 * <em>end-of-stream</em> or <em>poison</em> objects, that are
 * interpreted accordingly when taken by consumers.
 *
 * <p>
 * Usage example, based on a typical producer-consumer scenario.
 * Note that a {@code BlockingQueue} can safely be used with multiple
 * producers and multiple consumers.
 *  <pre> {@code
 * class Producer implements Runnable {
 *   private final BlockingQueue queue;
 *   Producer(BlockingQueue q) { queue = q; }
 *   public void run() {
 *     try {
 *       while (true) { queue.put(produce()); }
 *     } catch (InterruptedException ex) { ... handle ...}
 *   }
 *   Object produce() { ... }
 * }
 *
 * class Consumer implements Runnable {
 *   private final BlockingQueue queue;
 *   Consumer(BlockingQueue q) { queue = q; }
 *   public void run() {
 *     try {
 *       while (true) { consume(queue.take()); }
 *     } catch (InterruptedException ex) { ... handle ...}
 *   }
 *   void consume(Object x) { ... }
 * }
 *
 * class Setup {
 *   void main() {
 *     BlockingQueue q = new SomeQueueImplementation();
 *     Producer p = new Producer(q);
 *     Consumer c1 = new Consumer(q);
 *     Consumer c2 = new Consumer(q);
 *     new Thread(p).start();
 *     new Thread(c1).start();
 *     new Thread(c2).start();
 *   }
 * }}</pre>
 *
 * <p>Memory consistency effects: As with other concurrent
 * collections, actions in a thread prior to placing an object into a
 * {@code BlockingQueue}
 * <a href="package-summary.html#MemoryVisibility"><i>happen-before</i></a>
 * actions subsequent to the access or removal of that element from
 * the {@code BlockingQueue} in another thread.
 *
 * <p>This interface is a member of the
 * <a href="{@docRoot}/../technotes/guides/collections/index.html">
 * Java Collections Framework</a>.
 *
 * @since 1.5
 * @author Doug Lea
 * @param <E> the type of elements held in this collection
 */
public interface BlockingQueue<E> extends Queue<E> {
    /**
     * Inserts the specified element into this queue if it is possible to do
     * so immediately without violating capacity restrictions, returning
     * {@code true} upon success and throwing an
     * {@code IllegalStateException} if no space is currently available.
     * When using a capacity-restricted queue, it is generally preferable to
     * use {@link #offer(Object) offer}.
     *
     * @param e the element to add
     * @return {@code true} (as specified by {@link Collection#add})
     * @throws IllegalStateException if the element cannot be added at this
     *         time due to capacity restrictions
     * @throws ClassCastException if the class of the specified element
     *         prevents it from being added to this queue
     * @throws NullPointerException if the specified element is null
     * @throws IllegalArgumentException if some property of the specified
     *         element prevents it from being added to this queue
     */
    boolean add(E e);

    /**
     * Inserts the specified element into this queue if it is possible to do
     * so immediately without violating capacity restrictions, returning
     * {@code true} upon success and {@code false} if no space is currently
     * available.  When using a capacity-restricted queue, this method is
     * generally preferable to {@link #add}, which can fail to insert an
     * element only by throwing an exception.
     *
     * @param e the element to add
     * @return {@code true} if the element was added to this queue, else
     *         {@code false}
     * @throws ClassCastException if the class of the specified element
     *         prevents it from being added to this queue
     * @throws NullPointerException if the specified element is null
     * @throws IllegalArgumentException if some property of the specified
     *         element prevents it from being added to this queue
     */
    boolean offer(E e);

    /**
     * Inserts the specified element into this queue, waiting if necessary
     * for space to become available.
     *
     * @param e the element to add
     * @throws InterruptedException if interrupted while waiting
     * @throws ClassCastException if the class of the specified element
     *         prevents it from being added to this queue
     * @throws NullPointerException if the specified element is null
     * @throws IllegalArgumentException if some property of the specified
     *         element prevents it from being added to this queue
     */
    void put(E e) throws InterruptedException;

    /**
     * Inserts the specified element into this queue, waiting up to the
     * specified wait time if necessary for space to become available.
     *
     * @param e the element to add
     * @param timeout how long to wait before giving up, in units of
     *        {@code unit}
     * @param unit a {@code TimeUnit} determining how to interpret the
     *        {@code timeout} parameter
     * @return {@code true} if successful, or {@code false} if
     *         the specified waiting time elapses before space is available
     * @throws InterruptedException if interrupted while waiting
     * @throws ClassCastException if the class of the specified element
     *         prevents it from being added to this queue
     * @throws NullPointerException if the specified element is null
     * @throws IllegalArgumentException if some property of the specified
     *         element prevents it from being added to this queue
     */
    boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException;

    /**
     * Retrieves and removes the head of this queue, waiting if necessary
     * until an element becomes available.
     *
     * @return the head of this queue
     * @throws InterruptedException if interrupted while waiting
     */
    E take() throws InterruptedException;

    /**
     * Retrieves and removes the head of this queue, waiting up to the
     * specified wait time if necessary for an element to become available.
     *
     * @param timeout how long to wait before giving up, in units of
     *        {@code unit}
     * @param unit a {@code TimeUnit} determining how to interpret the
     *        {@code timeout} parameter
     * @return the head of this queue, or {@code null} if the
     *         specified waiting time elapses before an element is available
     * @throws InterruptedException if interrupted while waiting
     */
    E poll(long timeout, TimeUnit unit)
        throws InterruptedException;

    /**
     * Returns the number of additional elements that this queue can ideally
     * (in the absence of memory or resource constraints) accept without
     * blocking, or {@code Integer.MAX_VALUE} if there is no intrinsic
     * limit.
     *
     * <p>Note that you <em>cannot</em> always tell if an attempt to insert
     * an element will succeed by inspecting {@code remainingCapacity}
     * because it may be the case that another thread is about to
     * insert or remove an element.
     *
     * @return the remaining capacity
     */
    int remainingCapacity();

    /**
     * Removes a single instance of the specified element from this queue,
     * if it is present.  More formally, removes an element {@code e} such
     * that {@code o.equals(e)}, if this queue contains one or more such
     * elements.
     * Returns {@code true} if this queue contained the specified element
     * (or equivalently, if this queue changed as a result of the call).
     *
     * @param o element to be removed from this queue, if present
     * @return {@code true} if this queue changed as a result of the call
     * @throws ClassCastException if the class of the specified element
     *         is incompatible with this queue
     *         (<a href="../Collection.html#optional-restrictions">optional</a>)
     * @throws NullPointerException if the specified element is null
     *         (<a href="../Collection.html#optional-restrictions">optional</a>)
     */
    boolean remove(Object o);

    /**
     * Returns {@code true} if this queue contains the specified element.
     * More formally, returns {@code true} if and only if this queue contains
     * at least one element {@code e} such that {@code o.equals(e)}.
     *
     * @param o object to be checked for containment in this queue
     * @return {@code true} if this queue contains the specified element
     * @throws ClassCastException if the class of the specified element
     *         is incompatible with this queue
     *         (<a href="../Collection.html#optional-restrictions">optional</a>)
     * @throws NullPointerException if the specified element is null
     *         (<a href="../Collection.html#optional-restrictions">optional</a>)
     */
    public boolean contains(Object o);

    /**
     * Removes all available elements from this queue and adds them
     * to the given collection.  This operation may be more
     * efficient than repeatedly polling this queue.  A failure
     * encountered while attempting to add elements to
     * collection {@code c} may result in elements being in neither,
     * either or both collections when the associated exception is
     * thrown.  Attempts to drain a queue to itself result in
     * {@code IllegalArgumentException}. Further, the behavior of
     * this operation is undefined if the specified collection is
     * modified while the operation is in progress.
     *
     * @param c the collection to transfer elements into
     * @return the number of elements transferred
     * @throws UnsupportedOperationException if addition of elements
     *         is not supported by the specified collection
     * @throws ClassCastException if the class of an element of this queue
     *         prevents it from being added to the specified collection
     * @throws NullPointerException if the specified collection is null
     * @throws IllegalArgumentException if the specified collection is this
     *         queue, or some property of an element of this queue prevents
     *         it from being added to the specified collection
     */
    int drainTo(Collection<? super E> c);

    /**
     * Removes at most the given number of available elements from
     * this queue and adds them to the given collection.  A failure
     * encountered while attempting to add elements to
     * collection {@code c} may result in elements being in neither,
     * either or both collections when the associated exception is
     * thrown.  Attempts to drain a queue to itself result in
     * {@code IllegalArgumentException}. Further, the behavior of
     * this operation is undefined if the specified collection is
     * modified while the operation is in progress.
     *
     * @param c the collection to transfer elements into
     * @param maxElements the maximum number of elements to transfer
     * @return the number of elements transferred
     * @throws UnsupportedOperationException if addition of elements
     *         is not supported by the specified collection
     * @throws ClassCastException if the class of an element of this queue
     *         prevents it from being added to the specified collection
     * @throws NullPointerException if the specified collection is null
     * @throws IllegalArgumentException if the specified collection is this
     *         queue, or some property of an element of this queue prevents
     *         it from being added to the specified collection
     */
    int drainTo(Collection<? super E> c, int maxElements);
}

3. 常见的BlockingQueue

1). ArrayBlockingQueue

常用的BlockingQueue,由数组结构组成的有界阻塞队列。

2). LinkedBlockingQueue

常用。由链表结构组成的有界(但大小默认值为Intger.MAX_VALUE)阻塞队列

3). DelayQueue

使用优先级队列实现的延迟无界阻塞队列。

4). PriorityBlockingQueue

支持优先级排序的无界阻塞队列

5). SynchronousQueue

不存储元素的阻塞队列,也即单个元素的队列。

6). LinkedTransferQueue

由链表组成的无界阻塞队列

7). LinkedBlockingDeque

由链表组成的双向阻塞队列

4. 核心方法

方法类型抛出异常特殊值阻塞超时
插入add(e)offer(e)put(e)offer(e,time,unit)
移除remove()poll()take()poll(tiem,unit)
检查element()peek()不可用不可用
抛出异常当阻塞队列满时,再往队列里add插入元素会抛出IllegalStateException:Queue full
当阻塞队列空时,再往队列里remove移除元素会抛出NoSuchElementException
特殊值插入方法,成功true失败false
移除方法,成功返回出队列的元素,队列里没有就返回null
一直阻塞当阻塞队列满时,生产者线程继续往队列里put元素,队列会一直阻塞生产者线程直到put数据或响应中断退出
当阻塞队列空时,消费者线程试图从队列里take元素,队列会一直阻塞消费者线程直到队列可用
超时退出当阻塞队列满时,队列会阻塞生产者线程一定时间,超过限时后生产者线程会退出

add() remove()

BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);

//add(e)
boolean addRes = blockingQueue.add("first");
boolean addRes1 = blockingQueue.add("second");
boolean addRes2 = blockingQueue.add("third");

//element()
String element = blockingQueue.element();
System.out.println(element);

//        blockingQueue.add("fourth"); //java.lang.IllegalStateException: Queue full

blockingQueue.remove();
blockingQueue.remove();
blockingQueue.remove();
//        blockingQueue.remove(); //java.util.NoSuchElementException

offer() poll()

BlockingQueue<String> blockingQueue1 = new ArrayBlockingQueue<>(3);
blockingQueue1.offer("aaa");//添加元素
blockingQueue1.offer("bbb");
blockingQueue1.offer("ccc");
boolean offerRes = blockingQueue1.offer("ddd"); //false

blockingQueue1.poll();//取出元素
blockingQueue1.poll();
blockingQueue1.poll();
String poll = blockingQueue1.poll(); //null

put() take()

blockingQueue2.put("111");
blockingQueue2.put("222");
blockingQueue2.put("333");
//blockingQueue2.put("444"); // 线程一直阻塞

blockingQueue2.take();
blockingQueue2.take();
blockingQueue2.take();
//blockingQueue2.take(); //线程一直阻塞

超时

BlockingQueue<String> blockingQueue3 = new ArrayBlockingQueue<>(3);

blockingQueue3.offer("a");
blockingQueue3.offer("b");
blockingQueue3.offer("c");
blockingQueue3.offer("d", 3L, TimeUnit.SECONDS); //阻塞3秒后,退出

十一、线程池

1. 概述

线程池:维护着多个线程,等待着监督管理者分配可并发执行的任务。

线程池做的工作是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,其他线程执行完毕,再从队列中取出任务来执行。

特点:

降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

提高响应速度:当任务到达时,任务可以不需要等待线程创建就能立即执行。

提高线程的可靠性:线程是稀缺资源,如果无限的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

Java中的线程池通过Executor框架实现,用到了Executor,Executors,ExecutorService,ThreadPoolExecutor

2. 线程池的使用

一池N线程 Executors.newFixedThreadPool(int)

特点:

线程池中的线程处于一定的量,可以很好的控制线程的并发量。

线程可以被重复利用,在显式关闭之前,都将一直存在。

超出一定量的线程被提交时需在队列中等待。

ExecutorService es = Executors.newFixedThreadPool(6);

try {
    for (int i = 1; i <= 10; i++) {
        es.execute(() -> {
            System.out.println("线程" + Thread.currentThread().getName() + "正在处理---");
        });
    }
} catch (Exception e) {
    e.printStackTrace();
} finally {
    es.shutdown();
}

一个任务一个任务执行,一池一线程 Executors.newSingleThreadExecutor()

ExecutorService es1 = Executors.newSingleThreadExecutor();
try {
    for (int i = 1; i <= 10; i++) {
        es1.execute(() -> {
            System.out.println("一池一线程:线程" + Thread.currentThread().getName() + "正在处理---");
        });
    }
} catch (Exception e) {
    e.printStackTrace();
} finally {
    es1.shutdown();
}

线程池根据需求创建线程,可扩容 Executors.newCachedThreadPool()

ExecutorService es2 = Executors.newCachedThreadPool();

try {
    for (int i = 1; i <= 20; i++) {
        es2.execute(() -> {
            System.out.println("可扩容线程池:线程" + Thread.currentThread().getName() + "正在处理---");
        });
    }
} catch (Exception e) {
    e.printStackTrace();
} finally {
    es2.shutdown();
}

3. 线程池参数

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

corePoolSize 核心线程数量(常驻)

maximumPoolSize 最大线程数量

keepAliveTime

unit 线程存活时间

workQueue 阻塞队列。当常驻线程池满了,就会把新的请求放在队列中。

threadFactory 线程工厂,创建线程。

handler 拒绝策略

4. 工作流程和拒绝策略

在这里插入图片描述

当执行execute()方法时,才会创建线程。

当常驻线程池满了,阻塞队列也满了,又有一请求,则会创建一个新线程,优先处理该请求。

拒绝策略:

AbortPolicy(默认):直接抛出RejectedExecutionException异常阻止系统正常运行

CallerRunsPolicy:“调用者运行”,一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量

DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。

DiscardPolicy:该策略默默地丢弃无法处理的任务,不予任何处理也不抛出异常,如果允许任务丢失,这是最好的一种策略。

5. 自定义线程池

ThreadPoolExecutor service = new ThreadPoolExecutor(2, 5, 2L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

try {
    for (int i = 1; i <= 20; i++) {
        service.execute(() -> {
            System.out.println("线程" + Thread.currentThread().getName() + "正在处理---");
        });
    }
} catch (Exception e) {
    e.printStackTrace();
} finally {
    service.shutdown();
}

十二、fork/join分支合并框架

1. 概述

Fork/Join可以将一个大的任务拆分成多个子任务进行并行处理,最后将子任务结果合并成最后的计算结果,并进行输出。

Fork:把复杂任务进行拆分

Join:把拆分任务的结果进行合并

任务分割:首先Fork/Join框架需要把大的任务分割成足够小的子任务,如果子任务比较大的话,还要对子任务进行继续分割。

执行任务并合并结果:分割的子任务分别放到双端队列中,然后几个启动线程分别从双端队列里获取任务执行。子任务执行完的结果都放在另外一个队列中,启动一个线程从队列里取数据,然后合并这些数据。

java.util.concurrent.ForkJoinPool java.util.concurrent.ForkJoinTask java.util.concurrent.RecursiveTask

2. 代码演示

案例:1+2+3…+100

相加两数,差值不超过10

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;

/**
 * 1+2+3...+100
 */
public class ForkJoinDemo {
    public static void main(String[] args) {
        CustomTask task = new CustomTask(1,100);
        ForkJoinPool pool = new ForkJoinPool();

        ForkJoinTask<Integer> forkJoinTask = pool.submit(task);

        Integer res = null;
        try {
            res = forkJoinTask.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println(res);

        pool.shutdown();
    }
}

class CustomTask extends RecursiveTask<Integer> {

    //拆分值不超过10
    private static final Integer DEFAULT_VALUE = 10;
    private int begin;
    private int end;
    private int result;

    public CustomTask(int begin, int end) {
        this.begin = begin;
        this.end = end;
    }

    @Override
    protected Integer compute() {
        if ((end - begin) <= DEFAULT_VALUE) {
            for (int i = begin; i <= end; i++) {
                result = i + result;
            }
        } else {
            //拆分
            //左侧拆分
            int middle = (begin + end) / 2;
            CustomTask task1 = new CustomTask(begin, middle);
            //右侧拆分
            CustomTask task2 = new CustomTask(middle + 1, end);
            task1.fork();
            task2.fork();

            result = task1.join() + task2.join();
        }
        return result;
    }
}

十三、CompletableFuture异步回调

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class CompletableFutureDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //同步
        CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
            System.out.println(Thread.currentThread().getName());  				
            //ForkJoinPool.commonPool-worker-1
        });
        completableFuture.get();
        //异步
        CompletableFuture<Integer> cf = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName());  
            //ForkJoinPool.commonPool-worker-1
            //            int i = 10 / 0;
            return 100;
        });

        cf.whenComplete((T, U) -> {
            System.out.println("T:" + T + ",U:" + U);  //T:100,U:null
        }).get();

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值