两个线程小程序面试题
实现生产者和消费者
wait()和notify()实现生产者和消费者
/** * 面试题:写一个固定容量同步容器,拥有put和get方法,以及getCount方法, * 能够支持2个生产者线程以及10个消费者线程的阻塞调用 * * 使用wait和notify/notifyAll来实现 * * @author mashibing */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? while会在进行一次判断 try { this.wait(); //effective java } catch (InterruptedException e) { e.printStackTrace(); } } lists.add(t); ++count; this.notifyAll(); //通知消费者线程进行消费 如果用notify()的话 叫醒的可能还是一个生产者 } 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实现生产者与消费者
/** * 面试题:写一个固定容量同步容器,拥有put和get方法,以及getCount方法, * 能够支持2个生产者线程以及10个消费者线程的阻塞调用 * * 使用wait和notify/notifyAll来实现 * * 使用Lock和Condition来实现 * 对比两种方式,Condition的方式可以更加精确的指定哪些线程被唤醒 * * @author mashibing */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(); } } }
对比两种方式,Condition的方式可以更加精确的指定哪些线程被唤醒
写两个线程,线程1添加10个元素到容器中,线程2监控元素的个数,当个数到5个时,线程2给出提示并结束
使用wait()和notify()实现
/** * 曾经的面试题:(淘宝?) * 实现一个容器,提供两个方法,add,size * 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束 * * 给lists添加volatile之后,t2能够接到通知,但是,t2线程的死循环很浪费cpu,如果不用死循环,该怎么做呢? * * 这里使用wait和notify做到,wait会释放锁,而notify不会释放锁 * 需要注意的是,运用这种方法,必须要保证t2先执行,也就是首先让t2监听才可以 * * 阅读下面的程序,并分析输出结果 * 可以读到输出结果并不是size=5时t2退出,而是t1结束时t2才接收到通知而退出 * 想想这是为什么? * * notify之后,t1必须释放锁,t2退出后,也必须notify,通知t1继续执行 * 整个通信过程比较繁琐 * @author mashibing */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(); Thread t2 = 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"); 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 { //t2.join(); lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } }, "t1").start(); } }
使用LockSupport实现
1.和wait() 相比,park()不需要加锁,
2.和notify()相比unPark(t)指定的线程
3.如果先unPark(t),后面的park()不会生效
/** * 曾经的面试题:(淘宝?) * 实现一个容器,提供两个方法,add,size * 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束 * * 给lists添加volatile之后,t2能够接到通知,但是,t2线程的死循环很浪费cpu,如果不用死循环,该怎么做呢? * * 这里使用wait和notify做到,wait会释放锁,而notify不会释放锁 * 需要注意的是,运用这种方法,必须要保证t2先执行,也就是首先让t2监听才可以 * * 阅读下面的程序,并分析输出结果 * 可以读到输出结果并不是size=5时t2退出,而是t1结束时t2才接收到通知而退出 * 想想这是为什么? * * notify之后,t1必须释放锁,t2退出后,也必须notify,通知t1继续执行 * 整个通信过程比较繁琐 * * 使用Latch(门闩)替代wait notify来进行通知 * 好处是通信方式简单,同时也可以指定等待时间 * 使用await和countdown方法替代wait和notify * CountDownLatch不涉及锁定,当count的值为零时当前线程继续运行 * 当不涉及同步,只是涉及线程通信的时候,用synchronized + wait/notify就显得太重了 * 这时应该考虑countdownlatch/cyclicbarrier/semaphore * @author mashibing *///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(); 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 { t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } }, "t1").start(); } }
ReadWriteLock的用法
ReadWriteLock分出了两把锁 readLock 和writeLock
static ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); static Lock readLock = readWriteLock.readLock(); static Lock writeLock = readWriteLock.writeLock();
package com.mashibing.juc.c_020; import java.util.Random; import java.util.concurrent.atomic.LongAdder; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class T10_TestReadWriteLock { static Lock lock = new ReentrantLock(); private static int value; static ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); static Lock readLock = readWriteLock.readLock(); static Lock writeLock = readWriteLock.writeLock(); public static void read(Lock lock) { try { lock.lock(); Thread.sleep(1000); System.out.println("read over!"); //模拟读取操作 } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void write(Lock lock, int v) { try { lock.lock(); Thread.sleep(1000); value = v; System.out.println("write over!"); //模拟写操作 } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main(String[] args) { //Runnable readR = ()-> read(lock); Runnable readR = ()-> read(readLock); //Runnable writeR = ()->write(lock, new Random().nextInt()); Runnable writeR = ()->write(writeLock, new Random().nextInt()); for(int i=0; i<18; i++) new Thread(readR).start(); for(int i=0; i<2; i++) new Thread(writeR).start(); } }
如果读的时候不加锁可能产生脏读(类似事物的脏读)
对一个对象,如果读写都加Synchronized,那么读写都是互斥的 18个线程读的时候 会串行18次,两个线程写的时候 串行两次,需要18+2=20
在读线程里面加读锁 一个线程读的时候 其他线程可以一起读(写锁是排它的,读锁不上排它的),18个线程读的时候 可以一并读
JUC其他的线程类
1、类介绍
一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
用给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。
之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。
这种现象只出现一次——计数无法被重置。 一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行
2、使用场景
在一些应用场合中,需要等待某个条件达到要求后才能做后面的事情;
同时当线程都完成后也会触发事件,以便进行后面的操作。
这个时候就可以使用CountDownLatch。
CountDownLatch最重要的方法是countDown()和await(),前者主要是倒数一次,后者是等待倒数到0,如果没有到达0,就只有阻塞等待了(加个门栓)。
package com.example.thread; import java.util.concurrent.CountDownLatch; /** * countdownlatch 是一个同步类工具,不涉及锁定,当count的值为零时当前线程继续运行, * 不涉及同步,只涉及线程通信的时候,使用它较为合适 * 如果有资源争夺 还是需要加锁 */ public class PrintAB2CDL { private static final int MAX_PRINT_NUM = 100; private static volatile int count = 0; public static void printAB() { // 声明CountDownLatch CountDownLatch countDownLatch = new CountDownLatch(2); new Thread(() -> { while (count < MAX_PRINT_NUM) { if (count % 2 == 0) { System.out.println("num偶数:" + count); count++; } } // 偶数线程执行完则计数器减一 countDownLatch.countDown(); }).start(); new Thread(() -> { while (count < MAX_PRINT_NUM) { if (count % 2 == 1) { System.out.println("num奇数:" + count); count++; } } // 奇数线程执行完则计数器减一 countDownLatch.countDown(); }).start(); try { countDownLatch.await(); } catch (Exception e) { } } public static void main(String[] args) { printAB(); System.out.println("main方法结束"); } }
奇偶数打印完成之后 main方法打印结束
CyclicBarrier
栅栏的意思是 大家伙都在这等着,什么时候等人数够了在执行(类似大家拼车 满人的时候发车)。和上面的CountDownLatch完全不同,一个线程可以CountDown多次
package com.mashibing.juc.c_020; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class T07_TestCyclicBarrier { public static void main(String[] args) { //CyclicBarrier barrier = new CyclicBarrier(20); CyclicBarrier barrier = new CyclicBarrier(20, () -> System.out.println("满人")); /*CyclicBarrier barrier = new CyclicBarrier(20, new Runnable() { @Override public void run() { System.out.println("满人,发车"); } });*/ for(int i=0; i<100; i++) { new Thread(()->{ try { barrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } }).start(); } } }
Semaphore
信号灯,灯亮的时候可以执行:
限流:最多允许多少个线程同时允许
比如卖票,我们最多开五个窗口 semaphore就写5
package com.mashibing.juc.c_020; import java.util.concurrent.Semaphore; public class T11_TestSemaphore { public static void main(String[] args) { //Semaphore s = new Semaphore(2); Semaphore s = new Semaphore(2, true); //允许一个线程同时执行 //Semaphore s = new Semaphore(1); new Thread(()->{ try { s.acquire(); System.out.println("T1 running..."); Thread.sleep(200); System.out.println("T1 running..."); } catch (InterruptedException e) { e.printStackTrace(); } finally { s.release(); } }).start(); new Thread(()->{ try { s.acquire(); System.out.println("T2 running..."); Thread.sleep(200); System.out.println("T2 running..."); s.release(); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); } }