3、高频面试题,AQS源码,以及源码阅读方法论
曾经的面试题:(淘宝?)
实现一个容器,提供两个方法,add,size
写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束
分析下面这个程序,能完成这个功能吗?
import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; public class T01_WithoutVolatile { List lists = new ArrayList(); public void add(Object o) { lists.add(o); } public int size() { return lists.size(); } public static void main(String[] args) { T01_WithoutVolatile c = new T01_WithoutVolatile(); new Thread(() -> { for(int i=0; i<10; i++) { // 一直往lists 添加object c.add(new Object()); System.out.println("add " + i); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } }, "t1").start(); new Thread(() -> { while(true) { // 启动t2 后,如果集合的大小为5 时,跳出循环 if(c.size() == 5) { break; } } System.out.println("t2 结束"); }, "t2").start(); } }
答案是肯定会死循环,不会跳出t2 线程的死循环的,因为多个线程操作一个集合,线程间时没有可见性的。
那就用volatile
给lists添加volatile之后,t2能够接到通知,但是,t2线程的死循环很浪费cpu,如果不用死循环,
而且,如果在if 和 break之间被别的线程打断,得到的结果也不精确,
import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.concurrent.TimeUnit; public class T02_WithVolatile { //添加volatile,使t2能够得到通知 //volatile List lists = new LinkedList(); volatile List lists = Collections.synchronizedList(new LinkedList<>()); public void add(Object o) { lists.add(o); } public int size() { return lists.size(); } public static void main(String[] args) { T02_WithVolatile c = new T02_WithVolatile(); new Thread(() -> { for(int i=0; i<10; i++) { c.add(new Object()); System.out.println("add " + i); /*try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }*/ } }, "t1").start(); new Thread(() -> { while(true) { if(c.size() == 5) { break; } } System.out.println("t2 结束"); }, "t2").start(); } }
volatile 修饰的时引用,里面具体的值还是不可见,如果刚添加了一个对象进去到集合,这时候别的线程调用get 方法,还是错误的。
这里使用wait和notify做到,wait会释放锁,而notify不会释放锁
需要注意的是,运用这种方法,必须要保证t2先执行,也就是首先让t2监听才可以
阅读下面的程序,并分析输出结果
可以读到输出结果并不是size=5时t2退出,而是t1结束时t2才接收到通知而退出
想想这是为什么?
import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; public class T03_NotifyHoldingLock { //wait notify //添加volatile,使t2能够得到通知 volatile List lists = new ArrayList(); public void add(Object o) { lists.add(o); } public int size() { return lists.size(); } public static void main(String[] args) { T03_NotifyHoldingLock c = new T03_NotifyHoldingLock(); final Object lock = new Object(); new Thread(() -> { synchronized(lock) { System.out.println("t2启动"); if(c.size() != 5) { try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("t2 结束"); } }, "t2").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e1) { e1.printStackTrace(); } new Thread(() -> { System.out.println("t1启动"); synchronized(lock) { for(int i=0; i<10; i++) { c.add(new Object()); System.out.println("add " + i); if(c.size() == 5) { // notify 不会释放锁 lock.notify(); } try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } }, "t1").start(); } }
虽然t2 线程wait,然后t1 线程notify,但这个时候t1 线程自己没有释放锁,所以还是个死循环,notify 方法不会释放锁的
所以notify之后,t1必须释放锁,t2退出后,也必须notify,通知t1继续执行, 整个通信过程比较繁琐
import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; public class T04_NotifyFreeLock { //添加volatile,使t2能够得到通知 volatile List lists = new ArrayList(); public void add(Object o) { lists.add(o); } public int size() { return lists.size(); } public static void main(String[] args) { T04_NotifyFreeLock c = new T04_NotifyFreeLock(); final Object lock = new Object(); new Thread(() -> { synchronized(lock) { System.out.println("t2启动"); if(c.size() != 5) { try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("t2 结束"); //通知t1继续执行 lock.notify(); } }, "t2").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e1) { e1.printStackTrace(); } new Thread(() -> { System.out.println("t1启动"); synchronized(lock) { for(int i=0; i<10; i++) { c.add(new Object()); System.out.println("add " + i); if(c.size() == 5) { lock.notify(); //释放锁,让t2得以执行 try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } }, "t1").start(); } }
使用Latch(门闩)替代wait notify来进行通知
好处是通信方式简单,同时也可以指定等待时间
使用await和countdown方法替代wait和notify
CountDownLatch不涉及锁定,当count的值为零时当前线程继续运行
当不涉及同步,只是涉及线程通信的时候,用synchronized + wait/notify就显得太重了
这时应该考虑countdownlatch / cyclicbarrier / semaphore
countdownlatch :
import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; public class T05_CountDownLatch { // 添加volatile,使t2能够得到通知 volatile List lists = new ArrayList(); public void add(Object o) { lists.add(o); } public int size() { return lists.size(); } public static void main(String[] args) { T05_CountDownLatch c = new T05_CountDownLatch(); CountDownLatch latch = new CountDownLatch(1); new Thread(() -> { System.out.println("t2启动"); if (c.size() != 5) { try { latch.await(); //也可以指定等待时间 //latch.await(5000, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("t2 结束"); }, "t2").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e1) { e1.printStackTrace(); } new Thread(() -> { System.out.println("t1启动"); for (int i = 0; i < 10; i++) { c.add(new Object()); System.out.println("add " + i); if (c.size() == 5) { // 打开门闩,让t2得以执行 latch.countDown(); } try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } }, "t1").start(); } }
LockSupport :
import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.LockSupport; //TODO park unpark public class T06_LockSupport { // 添加volatile,使t2能够得到通知 volatile List lists = new ArrayList(); public void add(Object o) { lists.add(o); } public int size() { return lists.size(); } public static void main(String[] args) { T06_LockSupport c = new T06_LockSupport(); CountDownLatch latch = new CountDownLatch(1); Thread t2 = new Thread(() -> { System.out.println("t2启动"); if (c.size() != 5) { LockSupport.park(); } System.out.println("t2 结束"); }, "t2"); t2.start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e1) { e1.printStackTrace(); } new Thread(() -> { System.out.println("t1启动"); for (int i = 0; i < 10; i++) { c.add(new Object()); System.out.println("add " + i); if (c.size() == 5) { LockSupport.unpark(t2); } try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } }, "t1").start(); } }
import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.LockSupport; public class T07_LockSupport_WithoutSleep { // 添加volatile,使t2能够得到通知 volatile List lists = new ArrayList(); public void add(Object o) { lists.add(o); } public int size() { return lists.size(); } static Thread t1 = null, t2 = null; public static void main(String[] args) { T07_LockSupport_WithoutSleep c = new T07_LockSupport_WithoutSleep(); t1 = new Thread(() -> { System.out.println("t1启动"); for (int i = 0; i < 10; i++) { c.add(new Object()); System.out.println("add " + i); if (c.size() == 5) { LockSupport.unpark(t2); LockSupport.park(); } } }, "t1"); t2 = new Thread(() -> { LockSupport.park(); System.out.println("t2 结束"); LockSupport.unpark(t1); }, "t2"); t2.start(); t1.start(); } }
面试题:写一个固定容量同步容器,拥有put和get方法,以及getCount方法,能够支持2个生产者线程以及10个消费者线程的阻塞调用
使用wait和notify/notifyAll来实现
synchronized 写法:
import java.util.LinkedList; import java.util.concurrent.TimeUnit; public class MyContainer1<T> { final private LinkedList<T> lists = new LinkedList<>(); final private int MAX = 10; //最多10个元素 private int count = 0; public synchronized void put(T t) { while(lists.size() == MAX) { //想想为什么用while而不是用if? 如果用if,过后不会继续判断 try { this.wait(); //effective java } catch (InterruptedException e) { e.printStackTrace(); } } lists.add(t); ++count; this.notifyAll(); //通知消费者线程进行消费 } public synchronized T get() { T t = null; while(lists.size() == 0) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } t = lists.removeFirst(); count --; this.notifyAll(); //通知生产者进行生产 return t; } public static void main(String[] args) { MyContainer1<String> c = new MyContainer1<>(); //启动消费者线程 for(int i=0; i<10; i++) { new Thread(()->{ for(int j=0; j<5; j++) System.out.println(c.get()); }, "c" + i).start(); } try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } //启动生产者线程 for(int i=0; i<2; i++) { new Thread(()->{ for(int j=0; j<25; j++) c.put(Thread.currentThread().getName() + " " + j); }, "p" + i).start(); } } }
使用Lock和Condition来实现
对比两种方式,Condition的方式可以更加精确的指定哪些线程被唤醒
import java.util.LinkedList; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class MyContainer2<T> { final private LinkedList<T> lists = new LinkedList<>(); final private int MAX = 10; //最多10个元素 private int count = 0; private Lock lock = new ReentrantLock(); private Condition producer = lock.newCondition(); private Condition consumer = lock.newCondition(); public void put(T t) { try { lock.lock(); while(lists.size() == MAX) { //想想为什么用while而不是用if? producer.await(); } lists.add(t); ++count; consumer.signalAll(); //通知消费者线程进行消费 } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public T get() { T t = null; try { lock.lock(); while(lists.size() == 0) { consumer.await(); } t = lists.removeFirst(); count --; producer.signalAll(); //通知生产者进行生产 } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } return t; } public static void main(String[] args) { MyContainer2<String> c = new MyContainer2<>(); //启动消费者线程 for(int i=0; i<10; i++) { new Thread(()->{ for(int j=0; j<5; j++) System.out.println(c.get()); }, "c" + i).start(); } try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } //启动生产者线程 for(int i=0; i<2; i++) { new Thread(()->{ for(int j=0; j<25; j++) c.put(Thread.currentThread().getName() + " " + j); }, "p" + i).start(); } } }
源码阅读规则
AQS (AbstractQueuedSynchronizer)
通过ReentrantLock 了解AQS
关系图
代码测试
import java.util.concurrent.locks.ReentrantLock; public class TestReentrantLock { private static volatile int i = 0; public static void main(String[] args) { ReentrantLock lock = new ReentrantLock(); lock.lock(); //synchronized (TestReentrantLock.class) { i++; //} lock.unlock(); //synchronized 程序员的丽春院 JUC } }
ReentrantLock 里面的lock() 方法,可以看到是里面的一个内部类NonfairSync 里面的,
NonfairSync 继承了Sync 类,里面用了cas方法
static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; /** * Performs lock. Try immediate barge, backing up to normal * acquire on failure. */ final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } }
而这个compareAndSetState() 方法 在AQS类里面的,
最终NonfairSync 类调用了tryAcquire() 方法,返回的nonfairTryAcquire() 方法,
这个nonfairTryAcquire 方法在Sync里面的,调用非公平锁,节点里装的是线程。
再看acquire() 方法,其实调用的是NonfairSync 里面的tryAcquire 方法,而不是AQS的acquire 方法
可以看到调用tryAcquire 方法并且判断得到队列,
这下通过代码可以知道,AQS就是CAS + volatile
这些就知道了AQS 的结构图
采用的双向链表的队列,每个节点放的是线程
所以AQS 就是链表(多个线程)访问同一个state,那么多个线程访问一个值需要注意什么呢?给这个链表加锁。
加锁影响效率,所以采用CAS 代替加锁操作
acquireQueued() 方法:
通过acquireQueued 方法的代码可以得到结论:队列里的分头部节点,如果新进的线程,是添加到后面的,排队等待执行,优先执行头部节点的线程,
头部节点一直去尝试获取锁,如果获取到锁就说明头节点已经执行完毕,直接干掉,那自己就是头节点了。
看一下addWaiter() 方法:
其实在jdk1.9之后已经不是这个写法的方法了,会有一个VarHandle
VarHandle 就是一个对象的引用的指定。既然定义了一个对象已经有这个对象的引用了,为什么还要VarHandle?
解释:VarHandle 在1.9 之前没有,之前都是通过反射进行操作原来的值,但是VarHandle 比反射快,直接操作二进制码,最终肯定还是通过CPU原语进行实现。
import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; public class T01_HelloVarHandle { int x = 8; private static VarHandle handle; static { try { // 去找handle,条件是:T01_HelloVarHandle.class 名字是 x,int类型的变量 // 通过这个handle可以找到 8,当然通过x 也能找到8 handle = MethodHandles.lookup().findVarHandle(T01_HelloVarHandle.class, "x", int.class); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } public static void main(String[] args) { T01_HelloVarHandle t = new T01_HelloVarHandle(); //plain read / write // 可以通过handle 进行原子性的操作原来的值 System.out.println((int)handle.get(t)); handle.set(t,9); System.out.println(t.x); handle.compareAndSet(t, 9, 10); System.out.println(t.x); handle.getAndAdd(t, 10); System.out.println(t.x); } }