一.你在项目中用过CountDownLanuch,CyclicBarrier,Semaphore吗?
1.CountDownLanuch是一个同步的工具类,它允许一个或多个线程一直等待,直到其他线程执行完毕后才会继续往后执行.
通过内部的计数器实现的,计数器的初始化为线程的数量,每当一个线程执行完毕后,就减1,当计数器达到0时,表示所有的
线程都执行完毕,可以继续执行后面的代码.(类似于火箭发射倒计时)
public static void show01(int n){ CountDownLatch countDownLatch = new CountDownLatch(n);//计数器初始化线程的个数 // 开启6个线程,当countDownLatch减到0时,主线程运行 for (int i = 1; i <= 6; i++) { new Thread(() -> { System.out.println(Thread.currentThread().getName() + " 晚自习,离开教室" ); countDownLatch.countDown();//计数器减1 },String.valueOf(i)).start(); } try { countDownLatch.await();//计数器等待 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 班长最后关门,离开"); }
运行结果
第二个案例:结合枚举来使用
public static void show02(int n){ CountDownLatch countDownLatch = new CountDownLatch(n); for (int i = 1; i <= n; i++) { new Thread(() -> { System.out.println(Thread.currentThread().getName()+"被灭了"); countDownLatch.countDown(); },CountryEnum.list(i).getRetCode()).start(); } try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 秦朝统一"); }
public enum CountryEnum { ONE(1,"齐国"),TWO(2,"楚国"),THREE(3,"燕国"), FOUR(4,"赵国"),FIVE(5,"魏国"),SIX(6,"韩国"); private Integer retId; private String retCode; public Integer getRetId() { return retId; } public String getRetCode() { return retCode; } CountryEnum(Integer retId, String retCode) { this.retId = retId; this.retCode = retCode; } CountryEnum() { } public static CountryEnum list(int index){ //通过线程的编号,获取对应的国家 CountryEnum[] countries = CountryEnum.values(); for (CountryEnum country : countries) { if(country.getRetId() == index){ return country; } } return null; } }
运行结果
2.CyclicBarrier它指的是可循环的使用屏障,它的主要功能是让一组线程达到一个屏障时被阻塞,直到最后一个线程达到屏障
时,屏障才会开门,所有被拦截的线程才会继续工作,线程进入屏障就是调用的是await方法(类比于开会,人到齐了才能够开始)
public static void main(String[] args) { // 当7个线程执行完毕后,CyclicBarrier中的线程才会执行,计数器加1 CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{ System.out.println("人到齐了,会议开始"); }); for (int i = 1; i <= 7; i++) { final int temInt = i; new Thread(()->{ try { System.out.println("第"+temInt+"个人已经进入会议室等待了"); cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } },String.valueOf(i)).start(); } }
运行结果
3.Semaphore信号量,用于多个共享资源的互斥使用,用于并发线程的控制(类比于,停车场抢车位)
public static void main(String[] args) { //有3个停车位,6辆车 Semaphore semaphore = new Semaphore(3);//3个车位 for (int i = 1; i <= 6; i++) { final int tmpInt = i; new Thread(() ->{ try { semaphore.acquire();//获取资源 System.out.println("第"+tmpInt+"号车抢到车位"); TimeUnit.SECONDS.sleep(5); System.out.println("第"+tmpInt+"号车,停了5秒..."); } catch (InterruptedException e) { e.printStackTrace(); }finally { semaphore.release();//释放资源,再来获取资源,直到所有的线程都执行完毕 } },String.valueOf(i)).start(); } }
运行结果
二. 请你谈谈对锁的理解,什么是公平锁/非公平锁,什么是递归锁,什么是读写锁,什么是自旋锁,是否可以写一个自旋锁
1.公平锁/非公平锁
公平锁:在并发环境下,每个线程在获取锁的时候会先查看锁等待的序列,如果为空,或者当前线程是等待队列的第
一个,就占着锁,以后会按照先进先出的顺序从队列中依次的执行(类比于,排队问问题)
非公平锁:线程上来就直接尝试占有锁,如果尝试失败,就采用公平锁的策略(类比于,排队问问题,有人插队,但被喝止,只能排队)
非公平锁的优点在于吞吐量比公平锁大
1.Lock lock = new ReentrantLock(); fair默认为false 是非公平锁,如果fair为true,就是公平锁
2.synchronized 而言,它是非公平锁
2.递归锁(可重用锁)
递归锁:指的是同一个线程,外层的函数获得锁之后,内层的递归函数任然可以获取该锁的递归代码.在同一个线程在外层
方法获取锁的时候,在进入内层方法就会自动的获取锁.也就是说,线程可以进入任何一个它已经拥有锁的同步代码块
(类比于获得了大门的钥匙,在进入厨房,不需要再开锁)
ReentrantLock和 Synchronized 都是可重用锁
优点:这样可以避免死锁的产生
面试题:多个Lock对象会不会产生编译错误/运行错误
多个Lock对象不会产生编译错误,运行错误,如果lock,unlock可以正常匹配,那么代码会正常执行,退出.如果不匹配,线程就不会释放锁
从而,会一直请求释放锁对象,即卡死
class Phone implements Runnable{ // 发短信,成功的话调用法Email的方法 public synchronized void sendSMS() throws Exception{ System.out.println(Thread.currentThread().getName() + " " + "invoke sendSMS"); sendEmail(); } public synchronized void sendEmail() throws Exception{ System.out.println(Thread.currentThread().getName() + " " + "invoke sendEmail"); } Lock lock = new ReentrantLock(); @Override public void run() { get(); } //get方法,里面调用了set方法 public void get(){ lock.lock(); try { System.out.println(Thread.currentThread().getName() + " " + "invoke get"); set(); }catch (RuntimeException e){ e.printStackTrace(); }finally { lock.unlock(); } } public void set(){ lock.lock(); try { System.out.println(Thread.currentThread().getName() + " " + "invoke set"); }catch (RuntimeException e){ e.printStackTrace(); }finally { lock.unlock(); } }
/*使用synchronized证明可重用锁*/ public static void show1(Phone phone){ new Thread(() -> { try { phone.sendSMS(); } catch (Exception e) { e.printStackTrace(); } },"T1").start(); new Thread(() -> { try { phone.sendSMS(); } catch (Exception e) { e.printStackTrace(); } },"T2").start(); }
运行结果:
/*使用ReentrantLock来证明可重用锁*/ public static void show2(Phone phone) { Thread t3 = new Thread(phone,"T3"); Thread t4 = new Thread(phone,"T4"); t3.start(); t4.start(); }
运行结果
3.读写锁
读写锁:多线程共同读一份资源类没有问题,为了满足业务的并发量,多线程读取共享的资源可以同时进行,但是
如果有一个线程需要对资源进行修改,就不应该让其他的线程对该资源进行读写操作
读 - 读 可以共存
读 - 写 不可共存
写 - 写 不可共存
class MyCache{ // 存放数据,操作数据 private volatile Map<String,Object> map = new HashMap<>(); // 读写锁 private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); // 写操作 public void put(String key,Object value){ rwLock.writeLock().lock(); try { System.out.println(Thread.currentThread().getName() + " 写入数据"); TimeUnit.MICROSECONDS.sleep(500); map.put(key,value); System.out.println(Thread.currentThread().getName() + " 写入完成"); } catch (Exception e) { e.printStackTrace(); }finally { rwLock.writeLock().unlock(); } } // 读操作 public void get(String key){ rwLock.readLock().lock(); try { System.out.println(Thread.currentThread().getName() + " 读取数据"); TimeUnit.MICROSECONDS.sleep(500); Object value = map.get(key); System.out.println(Thread.currentThread().getName() + " 读取完成"+value); } catch (Exception e) { e.printStackTrace(); }finally { rwLock.readLock().unlock(); } } public void clear(){ map.clear(); } }
public static void main(String[] args) { MyCache ca = new MyCache(); // 启用5个线程,同时进行写操作 for (int i = 1; i <= 5; i++) { final int temInt = i; new Thread(()->{ ca.put(temInt+"",temInt+""); },String.valueOf(i)).start(); } try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } // 启用5个线程,同时进行读操作 for (int i = 1; i <= 5; i++) { final int temInt = i; new Thread(()->{ ca.get(temInt+""); },String.valueOf(i)).start(); } }
运行结果
多线程下的读/写操作没有插队
4.自旋锁
自旋锁:尝试获取锁的线程不会立刻阻塞,而是采取循环的方式去获取锁,这样可以减少线程上下文切换的消耗,但会
增大CPU的开销
public class SingleLock { /*手写一个自旋锁*/ static AtomicReference<Thread> atomicReference = new AtomicReference<>(); /*加锁*/ public static void lock(){ /*如果当前没有线程,则使用当前线程*/ System.out.println(Thread.currentThread().getName() + " get lock"); // 如果当前的线程不是第一次进来,就会在while这里一直死循环,程序无法进行 while (!atomicReference.compareAndSet(null,Thread.currentThread())){ } } /*解锁*/ public static void unLock(){ /*如果判断是当前的线程,则清空*/ System.out.println(Thread.currentThread().getName() + " invoke lock"); atomicReference.compareAndSet(Thread.currentThread(),null); } /*自旋锁*/ public static void main(String[] args) { new Thread(() ->{ lock(); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } unLock(); },"T1").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ lock(); unLock(); },"T2").start(); } }
运行结果
三.什么是死锁,能否实现一个死锁
死锁:指的是两个或两个以上的进程在执行的过程中,因为争夺资源而造成的一种互相等待的现象,若无外力干涉,那么这些进程将无法推进下去
如果系统资源充足,进程的请求都能得到满足,死锁出现的可能性就会很低,否则就会因为争夺有限的资源而陷入死锁.
原因:
1.系统的资源不足
2.进程推进运行的顺序不合适
3.资源分配不当
排查:
jps -l 查询进程编号
jstack +进程号查看由哪个类导致了死锁
public class DeadLockDemo { public static void main(String[] args) { String lockA= "lockA",lockB = "lockB"; new Thread(new ThreadLock(lockA,lockB),"A").start(); new Thread(new ThreadLock(lockB,lockA),"B").start(); } } class ThreadLock implements Runnable{ private String lockA; private String lockB; public ThreadLock(String lockA, String lockB) { this.lockA = lockA; this.lockB = lockB; } public void run(){ synchronized (lockA){ System.out.println(Thread.currentThread().getName() + " 持有锁A,尝试获取 "+lockB); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lockB){ System.out.println(Thread.currentThread().getName() + " 持有锁B,尝试获取 "+lockA); } } } }
运行结果:
程序无法继续执行
解决方法:
第一步:查进程
第二步:jstack 进程号
第三步,修改代码,避免死锁