并发编程JUC

225 阅读2分钟

Java并发数据结构源码系列

回头再详细整理

公平锁和非公平锁在代码中体现在哪个地方?

static final class NonfairSync extends Sync {
	final void lock() {
    	//相对于公平锁来说,非公平锁可以一上来就执行cas来抢占锁
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }
}

static final class FairSync extends Sync {
	final void lock() {
    	acquire(1);
    }
}

ReentrantLock 原理分析

image.png (图一)

image.png (图二做参考)

没有抢占到锁的线程的Node节点是从尾部插入 acquire(1)分析

if (!tryAcquire(arg) 
		&& acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        	{
           		selfInterrupt();
            }
            
1.  tryAcquire(arg)
1.1 protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
1.2 final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            //继续抢占锁,因为执行到这个时候,可能持有锁的线程刚释放了锁
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
        	//这个表示已经持有锁的线程再次lock(重入)
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
2. addWaiter(Node.EXCLUSIVE) 将未获得锁的线程加入到阻塞队列,
  private Node addWaiter(Node mode) {
      Node node = new Node(Thread.currentThread(), mode);
      // Try the fast path of enq; backup to full enq on failure
      Node pred = tail; //拿到AQS的尾部节点
      if (pred != null) { //当某线程首次进来的时候,pred是null,
          node.prev = pred;
          if (compareAndSetTail(pred, node)) {
              pred.next = node;
              return node;
          }
      }
      enq(node);
      return node;
  }
  //注:对于AQS双向列表,HEAD节点是第一个进队列的节点,TAIL节点是最后一个进队列的节点。
  private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node())) //compareAndSetHead内部只有为空的时候才替换,这时候已经将head更新成当前节点(new Node())了
                    tail = head;
            } else {
                node.prev = t; //node的prev
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

3. acquireQueued 去抢占锁或者阻塞
抢占锁失败之后会一直在这里进行自旋,但是自旋会占用CPU的资源,所以这里对它进行了阻塞【parkAndCheckInterrupt()中的LockSupport.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) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
shouldParkAfterFailedAcquire(..) 把当前节点的前一个节点的waitStatus改成SIGNAL(-1)
parkAndCheckInterrupt() 挂起当前线程
(LockSupport通过许可机制来控制是否堵塞线程,unpark()发放许可,线程不堵塞,park()消费许可,线程堵塞。)
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this); //挂起线程,如果不挂起,会一直执行for (;;) {..} 然后消耗CPU的。 注意这个时候是不执行下面的Thread.interrupted(),直到持有锁的线程释放锁后唤醒这个线程才进行执行;
    return Thread.interrupted();
}
线程执行完 return Thread.interrupted();后, 如果(因为是非公平锁)抢占到了锁,这会执行下面代码:将它设置为head节点,然后return,跳出自旋for(;;){..}
if (p == head && tryAcquire(arg)) {
    setHead(node);
    p.next = null; // help GC
    failed = false;
    return interrupted;
}
一句话概括
ReentrantLock.lock() :如果三个线程同时来竞争锁,如果线程1抢占到锁了,就会把state由0改为1,把exclusiveOwnerThread改成当前线程ID;没有抢占到锁的线程23将会透过AQS的Node节点进入一个FIFO队列中,并且这两个线程会调用LockSupport.park进行阻塞状态中。
ReentrantLock.unlock() :线程1把state由1改成0(这里先忽略重入次数),然后把exclusiveOwnerThread改为null,然后调用LockSupport.unpark来唤醒处在等待队列中的线程1的下一个节点(假设是线程2),被唤醒之后,线程2会继续执行acquireQueued中的自旋代码for(;;),会继续执行parkAndCheckInterrupt()中的return Thread.interrupted();然后会跳出for(;;),跳出循环表示一定抢到锁了,然后就执行业务代码了。

线程Join使用

//当main线程去调用t.join()时,会将自己当前线程阻塞,
    等到t线程执行完成到达完结状态,main线程才可以继续执行
public class T {

    public static void main(String[] args) throws Exception {

        System.out.println("我是主线程...start");
        Thread t = new Thread(new JoinTest());
        t.start();
        t.join();
        System.out.println("我是主线程...end");

    }

    static class JoinTest implements Runnable{

        @Override
        public void run() {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("我是JoinTest线程...");
        }
    }

}

控制台如下: 如果设置join超时时间

thread.join(1000);

则:

join原理

image.png

1)由于这里出现了synchronized,因此主线程需要拿到子线程对象t的锁。
2)当millis为0的时候,循环判断isAlive(),即判断线程是否存活,如果存活,调用子线程对象的wait方法,传入0也是表示一直等待下去
3)当主线程调用子线程对象的wait方法后,主线程释放掉锁,并进入等待状态。
4)当子线程运行结束,不再是存活状态,那么主线程需要被唤醒,且需要再次获取到锁,才能继续运行join剩余的方法,运行结束后,主线程从join方法中返回,继续运行main剩余的方法。



public final synchronized void join(long millis) throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;

    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (millis == 0) {
        // 判断子线程 (其他线程) 为活跃线程,则一直等待
        while (isAlive()) {
            wait(0);
        }
    } else {
        // 判断子线程 (其他线程) 为活跃线程,则一直等待
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

// ---理解 isAlive()
这是一个本地方法,作用是判断当前的线程是否处于活动状态。什么是活动状态呢?活动状态就是线程已经启动且尚未终止。线程处于正在运行或准备开始运行的状态,就认为线程是“存活”的。
这里有一个点要注意,join为什么阻塞的是主线程,而不是子线程呢? 
不理解的原因是阻塞主线程的方法是放在previousThread这个实例作用,让大家误以为应该阻塞previousThread线程。实际上主线程会持有previousThread这个对象的锁,然后调用wait方法去阻塞,而这个方法的调用者是在主线程中的。所以造成主线程阻塞。
其实join()方法的核心在于wait(),在主线程中调用t.join()相当于在main方法中添加 new JoinDemo().wait();是一样的效果;在这里只不过是wait方法写在了子线程的方法中。 
再次重申一遍,join方法的作用是在主线程阻塞,等在子线程执行完之后,由子线程唤醒主线程,再继续执行主线程调用t.join()方法之后的逻辑。
那么主线程是在什么情况下知道要继续执行呢?就是上面说的,主线程其实是由join的子线程在执行完成之后调用的notifyAll()方法,来唤醒等待的线程。

Condition.await() 和 Condition.signal()方法

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    Node node = addConditionWaiter();
    int savedState = fullyRelease(node); //把status设置为0,释放锁,唤醒在等待中的线程进行获取锁。
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) { //如果不在AQS同步队列(等待队列),就把它挂起。
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}
private Node addConditionWaiter() {
    Node t = lastWaiter; 
    // If lastWaiter is cancelled, clean out.
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}

Condition 测试

生产者

public class Producer implements Runnable{

    private Queue<String> msg;
    private int maxSize;
    Lock lock;
    Condition condition;

    public Producer(Queue<String> msg, int maxSize, Lock lock, Condition condition) {
        this.msg = msg;
        this.maxSize = maxSize;
        this.lock = lock;
        this.condition = condition;
    }

    @Override
    public void run() {
        int i=0;
        while(true){
            i++;
            lock.lock();
                while(msg.size()==maxSize){
                    System.out.println("生产者队列满了,先等待");
                    try {
                    //condition.await()阻塞线程并释放锁(背后相当于执行了lock.unlock()),会一直阻塞着,什么时候不阻塞了呢,是等消费者线程调用condition.signal()之后,
                    //这里才会继续往下进行,往下进行的时候已经表明已经又重新获取锁了(背后相当于又重新lock.lock()了)
                        condition.await(); 
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("生产消息:"+i);
                msg.add("生产者的消息内容"+i);
                condition.signal(); //唤醒阻塞状态下的线程
            lock.unlock();
        }
    }

}

消费者

public class Consumer implements Runnable{

    private Queue<String> msg;
    private int maxSize;
    Lock lock;
    Condition condition;

    public Consumer(Queue<String> msg, int maxSize, Lock lock, Condition condition) {
        this.msg = msg;
        this.maxSize = maxSize;
        this.lock = lock;
        this.condition = condition;
    }

    @Override
    public void run() {
        int i=0;
        while(true){
            i++;
            lock.lock(); //synchronized
            while(msg.isEmpty()){
                System.out.println("消费者队列空了,先等待");
                try {
                    condition.await(); //阻塞线程并释放锁   wait
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("消费消息:"+msg.remove());
            condition.signal(); //唤醒阻塞状态下的线程
            lock.unlock();
        }
    }

}

测试类

public class Test
{
    public static void main( String[] args )
    {
        Queue<String> queue=new LinkedList<>();
        Lock lock=new ReentrantLock(); //重入锁
        Condition condition=lock.newCondition();
        int maxSize=5;

        Producer producer=new Producer(queue,maxSize,lock,condition);
        Consumer consumer=new Consumer(queue,maxSize,lock,condition);

        Thread t1=new Thread(producer);
        Thread t2=new Thread(consumer);
        t1.start();
        t2.start();
    }

}

控制台:

Semaphore测试

/**
 * semaphore 也就是我们常说的信号灯,semaphore 可以控 制同时访问的线程个数,
 * 通过 acquire 获取一个许可,如 果没有就等待,通过 release 释放一个许可。
 * 有点类似限流 的作用。
 * 场景:有5个停车位,同时来了10量车,然后等待进停车位(例子归例子,实际不现实的哈哈哈)
 */
public class SemaphoreTest {

    public static void main(String[] args) {

        Semaphore semaphore=new Semaphore(5);
        for(int i = 0; i<10; i++){
            new Car(i,semaphore).start();
        }

    }
    static class Car extends Thread{

        private int num;
        private Semaphore semaphore;

        public Car(int num, Semaphore semaphore) {
            this.num = num;
            this.semaphore = semaphore;
        }

        public void run(){
            try {
                semaphore.acquire();//获取一个许可(或令牌),拿到了就往下执行,拿不到就阻塞
                System.out.println("线程:"+Thread.currentThread().getName()+"代表的第"+num+"俩车来啦.......");
                TimeUnit.SECONDS.sleep((long)(Math.random() * 10) + 2);
                System.out.println("线程:"+Thread.currentThread().getName()+"代表的第"+num+"俩车走喽-------");
                semaphore.release();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

}

CountDownLatch测试

//CountDownLatch试用于在多线程场景中需要等待所有子线程都执行完毕之后再做操作~
public class CountDownLatchTest {
    
    public static void main(String[] args) {
        // 5 表示其他线程的数量
        CountDownLatch latch = new CountDownLatch(5);
        LatchDemo ld = new LatchDemo(latch);
        long start = System.currentTimeMillis();
        for (int i = 0; i < 5; i++) {
            new Thread(ld).start();
        }
        try {
            // 此处要一直等到 latch的值为0 ,就能往下执行了
            latch.await();

            System.out.println("最终全部执行完毕!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("消耗时间为:" + (end - start));
    }

    static class LatchDemo implements Runnable {
        private CountDownLatch latch;
        public LatchDemo(CountDownLatch latch) {
            this.latch = latch;
        }
        @Override
        public void run() {
            synchronized(this) {
                try {
                    long ss = (long)(Math.random() * 5000);
                    Thread.sleep(ss);
                    System.out.println("线程:"+Thread.currentThread()+"执行完毕!执行了:"+ss+"毫秒");
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    latch.countDown();
                }
            }
        }
    }

}

CyclicBarrier测试

//CyclicBarrier叫做 回环屏障,作用是 让一组线程全部达到一个状态之后再全部同时执行
public class TestJava5 {

    public static void main(String[] args) {
        
        //等张三,李四,王五三人都上厕所完成后 一起开会~
        CyclicBarrier cycliBarrier=new CyclicBarrier(3,()->{
            System.out.println("---------开始进行开会-----------");
            System.out.println("------------------------------");
        });
        new Thread(new DataImportThread(cycliBarrier,"张三")).start();
        new Thread(new DataImportThread(cycliBarrier,"李四")).start();
        new Thread(new DataImportThread(cycliBarrier,"王五")).start();

    }


    public static class DataImportThread extends Thread {

        private CyclicBarrier cyclicBarrier;
        private String name;

        DataImportThread(CyclicBarrier cyclicBarrier, String name) {
            this.cyclicBarrier = cyclicBarrier;
            this.name = name;
        }
        @Override
        public void run() {
            System.out.println(name + "在上厕所-----");
            try {
                long ss = (long)(Math.random() * 10000);
                Thread.sleep(ss);
                System.out.println(name + "上完了~~~~~");
                cyclicBarrier.await();//阻塞
                System.out.println("cyclicBarrier.await()后三个线程同时执行-----" + name + "坐在板凳上-----当前时间----"
                        + System.currentTimeMillis());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }
}

控制台:

image.png