并发

并发

1 JVM相关

1.1 volatile

volatile的作用:保证内存可见性,禁止指令重排。

  • 保证内存可见性:在JMM层面,主内存的变量由各线程共享,但是各线程并不会直接操作主内存,而是先将主内存中的数据调入工作内存,在工作内存中操作结束后再写回主内存。在CPU层面,CPU不会直接操作内存,而是先将内存中的数据调入缓存,之后有寄存器操作后写入缓存,最后才会写回内存。因此在多线程工作时就有可能发生如下情况:A线程对变量a做了修改但还没有来得及写回主线程就发生了上下文切换到了B线程,此时B线程就会读取到错误的数据。而volatile关键字可以保证单读和单写的原子性。具体解释就是,当线程A对变量a做了修改之后JVM保证被修改的变量会被立刻写回主内存而不能被打断。而当线程B对变量a进行读取时,即便工作内存中有a变量也会在主内存中重新读取a变量。

  • 禁止指令重排:指令重排是JVM为了优化指令、提高程序运行效率,在不影响单线程程序执行结果的前提下对指令顺序进行的修改。但在多线程中可能产生一些问题。在下面的代码中,将writer()方法和reader()方法分别放在A线程和B线程中运行。A线程将a修改为1并将flag置为true。B线程会先检查flag是否为true,是的话将a自乘2并且退出循环。在我们的期望中,两个线程运行结束之后a的值应该为2。但是,对于JMM而言,语句1和语句2没有数据依赖性,因此将语句1和语句2就有可能被颠倒顺序,此时代码将无法按照我们的预期运行。

    class A {
        int a = 0;
        boolean flag = false;
        public void writer() {
            a = 1;					// 1
            flag = true;			// 2
        }
    
        public void reader() {
            while(true) {
                if (flag) {
                    a *= 2;
                    break;
                }
            }
        }
    }
    

1.2 synchronized

1.2.1 修饰目标

修饰代码块:使用程序员指定的对象作为锁。

修饰成员方法:使用当前对象(this)作为锁。

修饰类方法:使用类对象(XXX.class)作为锁。

1.2.2 sleep(),wait()和yield()

sleep():调用方法为Thread.sleep()。调用此方法后,当前线程不会失去锁。

wait():调用方法为object.wait()(object是指锁对象)。调用此方法后,当前线程会失去锁。

yield():调用方法为Thread.sleep()。调用此方法后,当前线程会让出CPU。不常用。

1.2.3 锁

JDK6对锁进行了优化,引入了“偏向锁”和“轻量级锁”。锁的信息存储在对象头的Mark Word中。

偏向锁:偏向锁的获取过程:访问Mark Word中的锁标志位(2bit)是否设置成1。如果不是1,就需要更高级的锁。如果是1,则测试线程ID是否指向当前线程,如果是,获取锁成功。如果线程ID并未指向当前线程,则通过CAS操作竞争锁(偏向锁不会主动释放锁,因此当线程ID指向其他线程时并不意味着有其他线程正在使用该锁)。如果竞争成功,则将Mark Word中线程ID设置为当前线程ID,获取锁成功;如果竞争失败,当到达全局安全点时获得偏向锁的线程被挂起,偏向锁升级为轻量级锁。

轻量级锁:自旋获取锁,响应快但会自旋会无意义的消耗CPU。适合同步代码块执行快的场合。

重量级锁:阻塞,等待唤醒。相应慢。适合同步代码块执行时间长的场合。

1.3 双重检验单例模式

public class Singleton {
    private static volatile Singleton instance = null; // volatile禁止指令重排

    private Singleton() {

    }

    public static Singleton getInstance() {
        if (instance == null) {					// 第一次检验,当对象已经被初始化就没有必要获取锁
            synchronized (Singleton.class) {
                if (instance == null) {			// 第二次检验,避免在初始化时被阻塞的线程在获取锁之后再次进行初始化
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

2. LOCK

2.1 读写锁

  • 读锁:可以由多个线程同时获得。当读锁已被获得则可以再获得读锁,但如果已获得写锁则不可以再获得读锁。
  • 写锁:只能由一个线程获得。不论读锁还是写锁已被获得都将无法再获得写锁。

2.2 悲观锁和乐观锁

  • 悲观锁总是假设最坏的情况,因此每次获取数据时都要保证其他线程无法操作数据即加锁。
  • 乐观锁总是假设最好的情况,读取数据时不会加锁,只有在写数据时才会加锁,可以使用CAS实现。

2.3 可重入锁和不可重入锁

  • 可重入锁:该锁可以被同一线程多次获取。可以简化编程。
  • 不可重入锁:当持有该锁的线程再次获得该锁时,会死锁。

3. 源码分析

3.1 AbstractQueuedSynchronizer

可重写的方法

protected boolean tryAcquire(int arg);			// 这是独占式获取同步状态的方法,将会被acquire(int arg)方法调用
protected boolean tryRelease(int arg);			// 这是独占式释放同步状态的方法,将会被release(int arg)方法调用
protected int tryAcquireShared(int arg);		// 这是共享式获取同步状态的方法,将会被acquireShared(int arg)方法调用
protected boolean tryReleaseShared(int arg);	// 这是共享式释放同步状态的方法,将会被releaseShared(int arg)方法调用
protected boolean isHeldExclusively();			// 这个方法用来判断当前同步器是否在独占模式下被线程占用

同步器提供的模板方法

public final void acquire(int arg);									// 独占式获取同步状态
public final void acquireInterruptibly(int arg);					// 与acquire(int arg)相同,但在同步队列中可响应中断
public final boolean tryAcquireNanos(int arg, long nanosTimeout);		// 在acquireInterruptibly(int arg)添加了超时限制
public final void acquireShared(int arg);							// 共享式获取同步状态
public final void acquireSharedInterruptibly(int arg);					// acquireShared(int arg)相同,可响应中断
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout);	// 在acquireSharedInterruptibly(int arg)添加了超时限制
public final boolean release(int arg);								// 独占式释放同步状态
public final boolean releaseShared(int arg);						// 共享式获取释放状态
public final Collection getQueuedThreads();							// 获取同步队列上的线程集合

获取同步状态:调用acquire(int arg)获取同步状态,在acquire(int arg)中会调用tryAcquire(int arg)方法尝试获取同步状态,若获取成功结束方法,执行同步方法。若获取失败首先将当前线程添加到同步队列中。之后在acquireQueued(final Node node, int arg)中尝试获取同步状态。

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&								// 若返回true直接退出方法获取到同步状态
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))		// 先将当前线程添加到同步队列,之后再自旋尝试获取同步状态
        selfInterrupt();
}

将线程添加至同步队列:首先将当前线程封装为Node节点,将该节点的pred节点指向尾节点,之后将尾节点的next节点指向该节点(此时可能有多个线程都在尝试将尾节点的next节点指向自己所在的节点,因此需要cas,而之前将该节点的pred节点指向尾节点的操作只会影响到自己,即便失败,只需要将该节点指向新的尾节点即可因此不需要cas)。若尾节点为空(同步队列为空)或插入失败。将会进入 enq(final Node node)方法自旋地插入。

private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);			// 将当前线程封装为Node节点
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if (pred != null) {										// 如果同步队列为空,交给enq(final Node node)方法处理
        node.prev = pred;									// 若插入失败,即其他线程插入并修改尾节点,将prev节点更新即可
        if (compareAndSetTail(pred, node)) {				// 若不使用cas,可能导致节点丢失
            pred.next = node;
            return node;
        }
    }
    enq(node);											// 当同步队列为空或插入失败时调用
    return node;
}
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))				// 向同步队列中插入首节点,此节点中没有线程
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

队列中的线程获取同步状态:首先判断当前线程所在的节点的prev节点是否是首节点。若是,尝试获取同步状态并将此节点设置为首节点,否则,检查前驱节点是否已经singal。若是停放(park)。

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {						// 若当前线程的前驱节点是首节点并且获取同步状态成功,并将自己设置为首节点
                setHead(node);										// 因为已经获取同步状态,因此直接设置即可
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&				// 若前节点等待状态为singal(当前线程已就绪),
                													// 若前节点已取消,跳过所有已取消的节点
                parkAndCheckInterrupt())								// 进入停放状态(阻塞)使用LockSupport.park()
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         */
        return true;
    if (ws > 0) {
        /*
         * Predecessor was cancelled. Skip over predecessors and
         * indicate retry.
         */
        do {
            node.prev = pred = pred.prev;								// 跳过被取消的节点
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        /*
         * waitStatus must be 0 or PROPAGATE.  Indicate that we
         * need a signal, but don't park yet.  Caller will need to
         * retry to make sure it cannot acquire before parking.
         */
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

释放同步状态:调用release(int arg)方法会在内部调用tryRelease(int arg)方法,当释放同步状态后若首节点为就绪状态唤醒首节点(其实唤醒的并不是首节点中的线程,具体见“唤醒线程”)。

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

唤醒线程:若首节点的下一个节点未被取消,则唤醒首节点的下一个节点,否则唤醒离首节点最近的未被取消的节点。该节点被唤醒之后会在acquireQueued(final Node node, int arg)方法中跳过该节点之前被取消的节点。

private void unparkSuccessor(Node node) {
    /*
     * If status is negative (i.e., possibly needing signal) try
     * to clear in anticipation of signalling.  It is OK if this
     * fails or if status is changed by waiting thread.
     */
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    /*
     * Thread to unpark is held in successor, which is normally
     * just the next node.  But if cancelled or apparently null,
     * traverse backwards from tail to find the actual
     * non-cancelled successor.
     */
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)						// 从尾节点找离首节点最近的未被取消的节点
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}

3.2 ReentrantLock

ReentrantLock底层使用AQS:在公平锁实现中,当状态为0时查看同步队列是否为空,若满足通过cas设置状态值,若成功,获取同步状态并将当前锁的拥有线程指向当前线程。若状态不为0查看当前线程是否是该锁的拥有线程,若是设置状态值,否则获取失败。

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {													// 该所未被获取
        if (!hasQueuedPredecessors() &&								// 若等待队列为空,非公平锁无线此判断
            compareAndSetState(0, acquires)) {						// 尝试获取同步状态
            setExclusiveOwnerThread(current);						// 获取成功后将该锁的拥有线程指向当前线程
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {				// 若该锁的拥有线程指为当前线程
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);										// 当前线程已获取同步状态,直接设置状态值即可
        return true;
    }
    return false;
}

释放锁:修改状态值若状态值为0,则将锁的拥有线程置为空。

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

3.3 ReentrantReadWriteLock

以后再写。。。

4. 阻塞队列

  • ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。

  • LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。

  • PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。

  • DelayQueue:一个使用优先级队列实现的无界阻塞队列。

  • SynchronousQueue:一个不存储元素的阻塞队列。

  • LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。

  • LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。

    boolean add(E e);															// 添加,当队列满时抛出异常
    boolean remove(Object o);													// 移除,当队列为空时抛出异常
    boolean offer(E e);															// 添加,当队列满时返回false
    E poll() throws InterruptedException;										// 移除,当队列为空时返回false
    void put(E e) throws InterruptedException;									// 添加,当队列满时一直阻塞直到添加成功
    E take() throws InterruptedException;										// 移除,当队列为一直阻塞直到移除成功
    boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException;	// 添加,当队列满时一直阻塞直到添加成功或超时
    E poll(long timeout, TimeUnit unit) throws InterruptedException;				// 移除,当队列为空时一直阻塞直到移除成功或超时
    

5. 线程池

private volatile ThreadFactory threadFactory;					// 用于创建线程的线程工厂,线程池中的工作线程都由此工厂创建
private volatile RejectedExecutionHandler handler;				// 拒绝策略
private final BlockingQueue<Runnable> workQueue;				// 工作队列
private volatile long keepAliveTime;							// 空闲存活时间 单位为枚举类型TimeUnit
private volatile int corePoolSize;								// 基本(核心)线程数
private volatile int maximumPoolSize;							// 最大线程数
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); // integer高三位存储线程池运行状态,其余位用于记录工作线程数
private final HashSet<Worker> workers = new HashSet<Worker>();			// 用于存放工作线程
// 线程池的运行状态
private static final int RUNNING    = -1 << COUNT_BITS;			// 运行
private static final int SHUTDOWN   =  0 << COUNT_BITS;			// 关闭
private static final int STOP       =  1 << COUNT_BITS;			// 停止
private static final int TIDYING    =  2 << COUNT_BITS;			// 整理
private static final int TERMINATED =  3 << COUNT_BITS;			// 结束

线程池的工作过程:

执行(提交)任务:当提交任务时,首先检查当前线程数若小于基本线程数则直接添加一个核心任务。若核心线程池已满,则尝试将任务放入工作队列中。若工作队列已满而工作线程数小于最大线程数则创建非核心任务()。若工作线程数已等于最大线程数则采取拒绝策略(handler)。

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    else if (!addWorker(command, false))
        reject(command);
}

任务结束:当线程中正在执行的任务结束之后,当前线程会调用getTask()从工作队列中获取任务。以此来保证线程复用。并且在完成所有任务(getTask()方法无法取出任务)后结束当前线程。

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock();
    boolean completedAbruptly = true;
    try {
        while (task != null || (task = getTask()) != null) {
            w.lock();
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    task.run();
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    afterExecute(task, thrown);
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}

线程空闲死亡:当允许核心线程死亡,或者工作线程数已经大于核心线程数时,从工作队列中取任务就会从take()方法(一直阻塞直到取出为止)变为poll(long timeout, TimeUnit unit)(阻塞直到取出或超时),未取出任务会进入下一轮循环,并在下一轮循环中返回unll。

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

    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // Are workers subject to culling?
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

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

        try {
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

6. 并发工具类

6.1 等待多线程完成的 CountDownLatch

CountDownLatch中有一个计数器(在构造时传入),调用countDown()方法会使计数器减一,调用await()方法的线程会被阻塞,直至CountDownLatch中有一个计数器为0。

6.2 同步屏障CyclicBarrier

CyclicBarrier中有也有一个计数器(在构造时传入),调用await()方法的线程会被阻塞并且计数器会减一,当计数器为0时,唤醒所有被此CyclicBarrier阻塞的线程。

6.3 控制并发线程数的Semaphore(信号量)

内部使用AQS,当参数会传给state。在获取锁时,若state大于1,可以将state减一并获取锁。可以控制同时访问指定资源的线程数量。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值