幸いですlog

java基础知识

java基础知识

description
对java基本类型进行分析、及面试必问
Created
Jun 3, 2022 09:29 AM
Updated
Last updated August 31, 2023

volatie

volatile是java虚拟机提供的轻量级的同步机制

三大特性222

1、保证可见性
2、不保证原子性
3、禁止指令重排

JMM(内存模型) 抽象的内存计算模型

抽象概念 是一组规则或规范
JMM关于同步的规定
1、线程解锁前,必须把共享变量的值刷新到主内存
2、线程加锁前,必须读取主内存的最新值到自己的工作内存
3、加锁解锁是同一吧锁
硬盘<内存<cpu jvm中每个线程创建都会创建一个工作空间,作为私有数据区域
notion image

支持三大特性、

1、可见性
<!--第一时间见到的 成为可见性-->
2、原子性---》volatile不保证原子性,synchronized保证原子性
<!--保证数据的一致性-->
3、有序性
4、VolateDemo代码演示可见性+原子性代码
//可见性演示 public class VolateDemo { public static void main(String[] args) { MyData myData=new MyData(); new Thread(()->{ System.out.println(Thread.currentThread().getName()+"\t come in"); try { TimeUnit.SECONDS.sleep(3); }catch (InterruptedException e){ e.printStackTrace(); } myData.addTO60(); System.out.println(Thread.currentThread().getName()+"\t upaded number value"+myData.number); }, "AAA").start(); while (myData.number==0){ } System.out.println(Thread.currentThread().getName()+"\t mission is over"+myData.number); } } class MyData{ volatile int number=0; public void addTO60(){ this.number=60; } } //结果 AAA come in AAA upaded number value60 main mission is over60 //测试原子性 MyData myData=new MyData(); for(int i=1;i<=20;i++){ new Thread(()->{ for (int j=0;j<=1000;j++){ myData.addPlusPlus(); } },String.valueOf(i)).start(); } try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } //一个main 一个gc线程 while (Thread.activeCount()>2){ Thread.yield(); } System.out.println(Thread.currentThread().getName()+"\t finally number value"+myData.number); /** * @deprecated:此前是加了volatile 关键字修饰 volatile * @return: void * @date: 2020/12/3 */ public void addPlusPlus(){ number++; }

为什么volatile不能保证原子性?

notion image
写丢失的情况,在一个写的时候其他会挂起。 线程较多时候,就会导致这个没有写进去的时候 另一个还操作上一个数

如果解决原子性?

1| AtomicInteger ->CAS
2| synchronized (动锁尽量不要用)

指令重排

单线程无所谓

单列模式

之前的列子再高并发下不适合
public class SinletonDemo { //private static SinletonDemo instance=null; private static volatile SinletonDemo instance=null; private SinletonDemo(){ System.out.println(Thread.currentThread().getName()+"\t 我是构造方法"); } public static SinletonDemo getInstance() { if(instance==null){ //双重检测机制 synchronized (SinletonDemo.class){ if (instance==null){ instance=new SinletonDemo(); } } } return instance; } public static void main(String[] args) { //单线程(main线程操作...) for (int i = 0; i < 10; i++) { new Thread(()->{ SinletonDemo.getInstance(); },String.valueOf(i)).start(); } }

CAS是什么?

compareAndSet 是一个cpu并发原语
<!-- JVM 会帮我们实现从CAS的汇编指令 -->
<!--源于 执行必须是连续的,在执行过程中不允许被打断,也就是说CAS是一个条CPU的原子指令,不会造成所谓的数据不一致问题。-->
要有原子性,可见性,有序性
notion image

cas底层原理

var1 AtomicInteger对象本身
var2 该对象值的引用地址
var4 需要变动的数量
var5 是用过 var1 var2找出的内存中真实的值
用该对象当前的值与var5比较
如果相同 更新var5+var4并且返回true 不同 继续取值然后在比较,直到更新完成。
AtomicInteger.getAndIncrement();//源码 private static final Unsafe U = Unsafe.getUnsafe(); private static final long VALUE = U.objectFieldOffset(AtomicInteger.class, "value"); // @parms VALUE 表示该变量值在内存中的偏离地址。 Unsafe类根据偏移地址获取数据 public final int getAndIncrement() { return U.getAndAddInt(this, VALUE, 1); } public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = getIntVolatile(var1, var2);//当前对象上的最新地址值 } while (!weakCompareAndSetInt(var1, var2, var5, var5 + var4)); return v; } //如果 o 当前对象, offset当前对象的内存地址值 expected快照的值 // expected==offset true 修改 public final boolean weakCompareAndSetInt(Object o, long offset, int expected, int x) { return compareAndSetInt(o, offset, expected, x); } //调用底层 public final native boolean compareAndSetInt(Object o, long offset, int expected, int x);

Unsafe类+CAS思想(自旋)

Unsafe

是CAS的核心类,由于java方法无法直接访问底层系统,需要哦通过本地(native)方法来访问,
原子整形之所以在i++多线程中不用加synchronized也能保证线程安全,
因为用的是Unsafe类,

讲一讲AtomicInteger 为什么用CAS不用synchronized?

synchronized 加锁同一时间段只允许一个线程 一致性得到保障,效率下降
CAS 没有加锁反复通过CAS比较,直到比较成功 即保证一致性又保证并发性
CAS缺点:
1、循环时间长开销大
2、只能保证一个共享变量的原子操作
3、引出来ABA问题。。。

原子类AtomicInteger 的ABA问题谈谈?

CAS-->Unsafe->CAS底层思想-->ABA-->原子引用更新-->如何规避ABA问题?
ABA🌩:狸猫换太子
CAS算法实现一个重要的前提需要去除内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差类会导致数据变化。
one线程从内存去除A two也取出 ,two把数值变成B又把B变成了A,这时候one进行CAS操作发现内存中任然是A,然后线程one操作成功。
关于ABA问题:有一个女孩与男孩定下10年之约,从此天各一方,10年内,女孩为了生活,当过老师、空姐、警察...,之后生活得到改善后,女人回到家继续守候约定,最后男孩女人再次见面,女还是那个女,但是人好像更迷人了~~~

如何解决ABA问题? 原子引用 AtomicReference<V>

public static void main(String[] args) { User zs=new User("张三",22); User l4=new User("lisi",25); AtomicReference<User> atomicReference=new AtomicReference<>(); atomicReference.set(zs); System.out.println(atomicReference.compareAndSet(zs, l4)); System.out.println(atomicReference.get().toString()); System.out.println(atomicReference.compareAndSet(zs, l4)); System.out.println(atomicReference.get().toString()); } true User{userName='lisi', age=25} false User{userName='lisi', age=25}
规避ABA问题 ——>原子引用+版本号
static AtomicReference<Integer> atomicReference=new AtomicReference<>(100); //版本号 时间戳 static AtomicStampedReference<Integer> atomicStampedReference=new AtomicStampedReference<>(100,1); public static void main(String[] args) throws InterruptedException{ System.out.println("=================以下是ABA问题的产生==============="); new Thread(()->{ atomicReference.compareAndSet(100,101); atomicReference.compareAndSet(101,100); },"t1").start(); new Thread(()->{ //暂停一秒钟 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(atomicReference.compareAndSet(100, 2019)+"\t int :"+atomicReference.get()); },"t2").start(); TimeUnit.SECONDS.sleep(2); System.out.println("=================以下是ABA问题的解决==============="); new Thread(()->{ int stamp = atomicStampedReference.getStamp(); System.out.println(Thread.currentThread()+"\t 第一次版本号:"+stamp); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1); System.out.println(Thread.currentThread()+"\t 第二次版本号:"+atomicStampedReference.getStamp()); atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1); System.out.println(Thread.currentThread()+"\t 第三次版本号:"+atomicStampedReference.getStamp()); },"t3").start(); new Thread(()->{ int stamp = atomicStampedReference.getStamp(); System.out.println(Thread.currentThread()+"\t 第四次版本号:"+stamp); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } boolean b = atomicStampedReference.compareAndSet(100, 101, stamp, stamp + 1); System.out.println(atomicStampedReference.getStamp()); System.out.println(Thread.currentThread().getName()+"\t "+b); },"t4").start(); } =================以下是ABA问题的产生=============== true int :2019 =================以下是ABA问题的解决=============== Thread[t4,5,main] 第四次版本号:1 Thread[t3,5,main] 第一次版本号:1 Thread[t3,5,main] 第二次版本号:2 Thread[t3,5,main] 第三次版本号:3 3 t4 false

集合类不安全的问题?

new ArrayList<Integer>();

举一个Arraylist线程不安全的列子

List<String> list = new ArrayList<>(); for (int i = 0; i < 3; i++) { new Thread(()->{ list.add(UUID.randomUUID().toString().substring(0,8)); System.out.println(list); },String.valueOf(i)).start(); } [e9335849] [e9335849, c0361717, 05ce30c3] [e9335849, c0361717] 如果再多会出现 java.util.ConcurrentModificationException JUC concurrent 并发 modification 修改
故障现象?

1、使用vector 解决ConcurrentModificationException

Vector 加锁 可以做到一致性 但是并发现下降
如果不许用vector呢?

2、使用Collections.synchronizedList() 变成一个同步list

外面封装一层把list变成安全的

3、juc中 CopyOnwriteArrayList<E> 写时复制

CopyOnwriteArrayList 底层是一个 volatile Object[] array 实现了list
jdk15 里面 synchronized (lock) { Object[] es = getArray(); int len = es.length; es = Arrays.copyOf(es, len + 1); es[len] = e; setArray(es); return true; } jdk8 ReentrantLock:互斥锁,同时只有一个线程可以持有。支持锁重入。 final ReentrantLock lock=this.lock; try{ Object[] elements=getArray(); int len=elements.length; Object[] newElements=Arrays.copyof(elements,len+1); newElements[len]=e; setArray(newElements); return true; }finally{ lock.unlock(); } ReentrantReadWriteLock:读写锁,分为读锁和写锁,支持重入。其中读读共享,写写独占,读写互斥,写读互斥。支持锁降级,线程获取写锁后可以降级为读锁。适合读多写少的场景。
notion image

SET不安全 解决办法同list

CopyOnWriteArraySet <E> extends AbstractSet<E> 内部用的是CopOnWriteArrayList //注意 new HashSet<>(); //底层 //capacity (16) and load factor (0.75). public HashSet() { map = new HashMap<>(); } //为什么参数一个是1,一个是2? private static final Object PRESENT = new Object(); public boolean add(E e) { return map.put(e, PRESENT)==null; //value恒定 } //map第一次添加返回的是null 第二次是1

Map不安全 解决?

1、ConcurrentHashMap

栈管运行,堆管存储

公平锁/非公平锁/可重入锁/递归锁/自旋锁 谈谈你的理解?

//公平锁和非公平锁 new ReentrantLock(); [riːˈɛntrənt] /** * Creates an instance of {@code ReentrantLock}. * This is equivalent to using {@code ReentrantLock(false)}. */ public ReentrantLock() { sync = new NonfairSync(); //不带参数非公平 [feə(r)] } public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
//公平锁 队列先来后到 不允许加塞

可重入锁名递归锁 ReentrantLock/synchronized 就是一个典型的可重入锁

指同一线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码
public class ReentrentLockDemo { public static void main(String[] args) { Phone phone=new Phone(); new Thread(()->{ try { phone.sendSMS(); } catch (Exception e) { e.printStackTrace(); } },"ti").start(); new Thread(()->{ try { phone.sendSMS(); } catch (Exception e) { e.printStackTrace(); } },"t2").start(); } } //资源类 class Phone{ public synchronized void sendSMS() throws Exception{ System.out.println(Thread.currentThread().getName()+"\t invoked sendSMS()"); sendEmail(); } public synchronized void sendEmail() throws Exception{ System.out.println(Thread.currentThread().getName()+"\t invoked sendEmail()"); } } //结果 ti invoked sendSMS() ti invoked sendEmail() t2 invoked sendSMS() t2 invoked sendEmail() //ReetrentLock版本实现 new Thread(phone,"t3").start(); new Thread(phone,"t4").start(); //资源类 class Phone implements Runnable{ Lock lock=new ReentrantLock(); @Override public void run() { get(); } public void get(){ lock.lock(); try { System.out.println(Thread.currentThread().getName()+" \t invoked get()"); set(); }finally { lock.unlock(); } } public void set(){ lock.lock(); try { System.out.println(Thread.currentThread().getName()+" \t invoked set()"); }finally { lock.unlock(); } } } //注意只要锁匹配 lock 和unlock几把都可以

自旋锁

指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处减少线程上下文切换的消耗,缺点是循环会消耗CPU

请手写一个自旋锁。。。

public class SplinLockDemo { AtomicReference<Thread> atomicReference=new AtomicReference<>(); public void mylock(){ Thread thread=Thread.currentThread();//当前进来的线程 System.out.println(Thread.currentThread().getName()+"\t come in "); while (!atomicReference.compareAndSet(null,thread)){} } public void myunlock(){ Thread thread=Thread.currentThread(); atomicReference.compareAndSet(thread,null); System.out.println(Thread.currentThread().getName()+"\t my out"); } public static void main(String[] args) { SplinLockDemo splinLockDemo=new SplinLockDemo(); new Thread(()->{ splinLockDemo.mylock(); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } splinLockDemo.myunlock(); },"AA").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ splinLockDemo.mylock(); splinLockDemo.myunlock(); },"BB").start(); } } //====================结果================= AA come in BB come in AA my out BB my out

独占锁(写锁)/共享锁(读锁)/互斥锁

独占锁:一次只能被一个线程所持有。ReentrantLock和cynchronized都是独占
public class ReadWriteLockDemo { public static void main(String[] args) { MyCache myCache=new MyCache(); for (int i = 0; i <= 5; i++) { final int tempInt=i; new Thread(()->{ myCache.put(tempInt+"",tempInt+""); },String.valueOf(i)).start(); } try { TimeUnit.MILLISECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } for (int i = 0; i <= 5; i++) { final int tempInt=i; new Thread(()->{ myCache.get(tempInt+""); },String.valueOf(i)).start(); } } } class MyCache{ private volatile Map<String,Object> map=new HashMap<>(); private ReentrantReadWriteLock lock=new ReentrantReadWriteLock(); //写操作 :原子+独占 public void put(String key,Object value){ lock.writeLock().lock(); try{ System.out.println(Thread.currentThread().getName()+"\t 正在写入:"+key); try { TimeUnit.MILLISECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } map.put(key,value); System.out.println(Thread.currentThread().getName()+"\t 写入完成"); }catch(Exception e){ e.printStackTrace(); }finally{ lock.writeLock().unlock(); } } public void get(String key){ lock.writeLock().lock(); try{ System.out.println(Thread.currentThread().getName()+"\t 正在读取"); try { TimeUnit.MILLISECONDS.sleep(6); } catch (InterruptedException e) { e.printStackTrace(); } Object reslut=map.get(key); System.out.println(Thread.currentThread().getName()+"\t 读取完成:"+reslut); }catch(Exception e){ e.printStackTrace(); }finally{ lock.writeLock().unlock(); } } } /* 1 正在写入:1 1 写入完成 0 正在写入:0 0 写入完成 2 正在写入:2 2 写入完成 3 正在写入:3 3 写入完成 4 正在写入:4 4 写入完成 5 正在写入:5 5 写入完成 0 正在读取 0 读取完成:0 1 正在读取 1 读取完成:1 2 正在读取 2 读取完成:2 3 正在读取 3 读取完成:3 4 正在读取 4 读取完成:4 5 正在读取 5 读取完成:5 不加读写锁会造成加加塞 */
线程在高内聚低耦合的,线程操纵资源类

CountDownLatch/CyclicBarrier/Semaphore使用过吗?

CountDownLatch [daʊn] [lætʃ]

  • CountDownLatch 有两个主要的方法,当一个或多个线程调用awit方法时,使用线程会被阻塞。
  • 其他线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞),
  • 当计数器的值变为零时,因调用await方法被阻塞的线程会被唤醒,继续执行。
CountDownLatch countDownLatch=new CountDownLatch(6); for (int i = 1; i <= 6; i++) { new Thread(()->{ System.out.println(Thread.currentThread().getName()+"\t 上完自习离开教室"); countDownLatch.countDown(); //-- },String.valueOf(i)).start(); } countDownLatch.await();// System.out.println(Thread.currentThread().getName()+"\t ********关门走人"); //第二种巧用枚举 ublic enum ConutryEnum { ONE(1,"齐"), TWO(2,"楚"), THREE(3,"燕"), FOUR(4,"赵"), FIVE(5,"魏"), SIX(6,"韩"); private Integer retCode; private String retMessage; public Integer getRetCode() { return retCode; } public String getRetMessage() { return retMessage; } ConutryEnum(Integer retCode, String retMessage) { this.retCode = retCode; this.retMessage = retMessage; } public static ConutryEnum foEach_ConutryEnum(int index){ ConutryEnum [] conutryEnums=ConutryEnum.values(); for(ConutryEnum conutryEnum:conutryEnums){ if(index==conutryEnum.getRetCode()){ return conutryEnum; } } return null ; } } CountDownLatch countDownLatch=new CountDownLatch(6); for (int i = 1; i <= 6; i++) { new Thread(()->{ System.out.println(Thread.currentThread().getName()+"\t 国,被灭"); countDownLatch.countDown(); },ConutryEnum.foEach_ConutryEnum(i).getRetMessage()).start(); } countDownLatch.await(); System.out.println(Thread.currentThread().getName()+"\t ********秦帝国,一统华夏"); //========================= 楚 国,被灭 燕 国,被灭 赵 国,被灭 齐 国,被灭 魏 国,被灭 韩 国,被灭 main ********秦帝国,一统华夏

CyclicBarrier [ˈsaɪklɪk] [ˈbæriə(r)] 这个和CounDownLatch相反做加法

CyclicBarrier cyclicBarrier=new CyclicBarrier(7,()->{ System.out.println("******召唤神龙"); }); for (int i = 1; i <= 7; i++) { final int tempInt=i; new Thread(()->{ System.out.println(Thread.currentThread().getName()+"\t 收集到第:"+tempInt+"龙珠"); try { cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } },String.valueOf(i)).start(); } //========== 3 收集到第:3龙珠 7 收集到第:7龙珠 1 收集到第:1龙珠 5 收集到第:5龙珠 2 收集到第:2龙珠 4 收集到第:4龙珠 6 收集到第:6龙珠 ******召唤神龙

Semaphore 信号量 增车位 [ˈseməfɔː(r)]

可以代替cynchrinozed
Semaphore semaphore=new Semaphore(3);//模拟3个停车位 for (int i = 1; i <= 6; i++) {//模拟6部汽车 new Thread(()->{ try { semaphore.acquire(); System.out.println(Thread.currentThread().getName()+"\t 抢到车位。"); TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName()+"\t 停车3秒后离开车位。"); } catch (InterruptedException e) { e.printStackTrace(); }finally { semaphore.release(); } },String.valueOf(i)).start(); }

阻塞队列?

当阻塞队列为空时,从队列中 获取 元素的操作将会被阻塞。
  • 所谓阻塞,在某些情况下会挂起线程,一旦条件满足,被挂起的线程又会自动被唤醒

为什么需要BlockingQueue?

  • 好处是我们不需要关心什么时候需要阻塞线程
  • 什么时候需要唤醒线程,因为这一切BlockingQueue都一手帮你包办
好处:手动挡换成了自动挡
Collection->Queue->BlockingQueue
<!--Integer.MAX_VALUE 2147483647-->
  • ArrayBlockingQueue 有数组结构组成的有界阻塞队列
  • LinkedBlockingQueue 由链表结构组成的有界(大小默认值Integer.MAX_VALUE)阻塞队列
  • PriorityBlockingQueue 支持优先级排序的无界阻塞队列
  • DelayQueue 优先级队列实现的延迟无界阻塞队列
  • SynchronousQueue 不储存元素的阻塞队列,也即单个元素的队列
  • LinkedTransferQueue 由链表结构组成的无界阻塞队列
  • LinkedBlockingDeque 由链表结构组成的双向阻塞队列

第一组抛出异常

//3个 BlockingQueue<String> blockingQueue=new ArrayBlockingQueue<>(3); System.out.println(blockingQueue.add("A")); System.out.println(blockingQueue.add("B")); System.out.println(blockingQueue.add("C")); System.out.println(blockingQueue.add("X")); //超多大小抛出一个异常 java.lang.IllegalStateException: Queue full [ɪˈliːɡl] System.out.println(blockingQueue.remove()); System.out.println(blockingQueue.remove()); System.out.println(blockingQueue.remove()); System.out.println(blockingQueue.remove()); java.util.NoSuchElementException [sʌtʃ]

第二组返回boolean

BlockingQueue<String> blockingQueue=new ArrayBlockingQueue<>(3); System.out.println(blockingQueue.offer("A")); System.out.println(blockingQueue.offer("B")); System.out.println(blockingQueue.offer("C")); System.out.println(blockingQueue.offer("D")); System.out.println(blockingQueue.peek());//对手的顶端元素 System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); //================= true true true false A A B C null

第三组 put,take 死战不退, 过时不候。

blockingQueue.put("a"); blockingQueue.put("a"); blockingQueue.put("a"); System.out.println("==============================="); //blockingQueue.put("x"); blockingQueue.take(); blockingQueue.take(); blockingQueue.take(); blockingQueue.take();

第四组温柔点的

System.out.println(blockingQueue.offer("a", 2L, TimeUnit.SECONDS)); System.out.println(blockingQueue.offer("a", 2L, TimeUnit.SECONDS)); System.out.println(blockingQueue.offer("a", 2L, TimeUnit.SECONDS)); System.out.println(blockingQueue.offer("a", 2L, TimeUnit.SECONDS));
notion image

SynchronousQueue 同步队列

与BlockingQueue不同,SynchronousQueue是一个不存储元素的BlockingQueue
//同步队列 BlockingQueue<String> blockingQueue=new SynchronousQueue<>(); new Thread(()->{ try { System.out.println(Thread.currentThread().getName()+"\t put 1"); blockingQueue.put("1"); System.out.println(Thread.currentThread().getName()+"\t put 2"); blockingQueue.put("2"); System.out.println(Thread.currentThread().getName()+"\t put 3"); blockingQueue.put("3"); } catch (InterruptedException e) { e.printStackTrace(); } },"AAA").start(); new Thread(()->{ try { TimeUnit.SECONDS.sleep(5); System.out.println(Thread.currentThread().getName()+"\t"+blockingQueue.take()); TimeUnit.SECONDS.sleep(5); System.out.println(Thread.currentThread().getName()+"\t"+blockingQueue.take()); TimeUnit.SECONDS.sleep(5); System.out.println(Thread.currentThread().getName()+"\t"+blockingQueue.take()); } catch (InterruptedException e) { e.printStackTrace(); } },"BBB").start(); //====================== AAA put 1 AAA put 2 BBB 1 AAA put 3 BBB 2 BBB 3
notion image

Synchronized 和lock的区别

1、两者的构成

  • syschroized 属于jvm层面java的关键字
    • 底层主要是monitorenter(底层是通过moniter对象来完成,其实wait/notify的等方法也依赖于monitor对象,只有在同步块或方法才能调用wait/notify的等方法)
      monitorexit
  • lock 是jdk5有的属于api层面 (java.util.concurrent,locks.Lock)
9: monitorenter 10: aload_1 11: monitorexit 12: goto 20 15: astore_2 16: aload_1 17: monitorexit 18: aload_2 19: athrow 20: return

2、使用方法

  • synchronized 不需要手动去释放。
  • ReentrantLock需要lock() unlock()

3、等待是否可中断

  • synchronized 不可中断,除非抛出异常,或者正常允许完成
  • ReentrantLock 可中断
    • 1、设置超时方法tryLock(long timeout,TimeUnit unit)
    • 2、lockInterruptibly() 放代码块中,调用interrupt()方法可中断

4、加锁是公平

  • synchronized 非公平锁
  • ReentrantLock 按传值 默认false 非公平 true为公平

5、锁绑定多个条件Condition

  • synchronized 没有
  • ReentrantLock 用来实现分组唤醒需要唤醒的线程们,可以精准唤醒,而不是像synchronized要么随机唤醒一个线程,要么唤醒全部线程。

题目

1、一个初始值为0的变量,两个线程对其交替操作,一个加1一个减一,来5轮

1、线程 操作 资源类
//资源类 class ShareData{ private int number=0; //变量 private Lock lock=new ReentrantLock(); private Condition condition=lock.newCondition(); public void incremet() throws Exception{ lock.lock(); try{ //1、判断 while (number!=0){ //等待,不能生产 condition.await();//阻塞 } //2、干活 number++; System.out.println(Thread.currentThread().getName()+"\t"+number); //3、通知唤醒 condition.signalAll(); }catch(Exception e){ e.printStackTrace(); }finally{ lock.unlock(); } } public void decremet() throws Exception{ lock.lock(); try{ //1、判断 while (number==0){ //等待,不能生产 condition.await(); } //2、干活 number--; System.out.println(Thread.currentThread().getName()+"\t"+number); //3、通知唤醒 condition.signalAll(); }catch(Exception e){ e.printStackTrace(); }finally{ lock.unlock(); } } } public class ProdConsumer_TraditionDemo { public static void main(String[] args) { ShareData shareData=new ShareData(); new Thread(()->{ for (int i = 0; i < 5; i++) { try { shareData.incremet(); } catch (Exception e) { e.printStackTrace(); } } },"AA").start(); new Thread(()->{ for (int i = 0; i < 5; i++) { try { shareData.decremet(); } catch (Exception e) { e.printStackTrace(); } } },"BB").start(); } } //=========================== AA 1 BB 0 AA 1 BB 0 AA 1 BB 0 AA 1 BB 0 AA 1 BB 0

多线程之间按顺序调用,实现A->B->C三个线程启动,要求如下

AA打印5次,BB打印10次,CC打印15次
class ShareResource{ private int number =1;//A:1 B:2 C:3 private Lock lock=new ReentrantLock(); private Condition condition1=lock.newCondition(); private Condition condition2=lock.newCondition(); private Condition condition3=lock.newCondition(); public void print5(){ lock.lock(); try{ //1、判断 while (number!=1){ condition1.await(); } // 2、干活 for(int i=1;i<=5;i++){ System.out.println(Thread.currentThread().getName()+"\t"+i); } //3、通知 number=2; condition2.signal(); }catch(Exception e){ e.printStackTrace(); }finally{ lock.unlock(); } } public void print10(){ lock.lock(); try{ //1、判断 while (number!=2){ condition2.await(); } // 2、干活 for(int i=1;i<=10;i++){ System.out.println(Thread.currentThread().getName()+"\t"+i); } //3、通知 number=3; condition3.signal(); }catch(Exception e){ e.printStackTrace(); }finally{ lock.unlock(); } } public void print15(){ lock.lock(); try{ //1、判断 while (number!=3){ condition3.await(); } // 2、干活 for(int i=1;i<=15;i++){ System.out.println(Thread.currentThread().getName()+"\t"+i); } //3、通知 number=1; condition1.signal(); }catch(Exception e){ e.printStackTrace(); }finally{ lock.unlock(); } } } public class Synchronized { public static void main(String[] args) { ShareResource shareResource=new ShareResource(); new Thread(()->{ for (int i = 1; i <= 10; i++) { shareResource.print5(); } },"AA").start(); new Thread(()->{ for (int i = 1; i <= 10; i++) { shareResource.print10(); } },"BB").start(); new Thread(()->{ for (int i = 1; i <= 10; i++) { shareResource.print15(); } },"CC").start(); } }

线程通信之生产者消费者队列。

volatile/cas/atomicInteger/BlockQueue/线程交互
//资源类 class MyResource{ private volatile boolean flag=true;//默认开启,进行生产+消费 可见性 private AtomicInteger atomicInteger=new AtomicInteger(); // cas 原子性 BlockingQueue<String> blockingQueue=null; //阻塞队列 public MyResource(BlockingQueue<String> blockingQueue) { this.blockingQueue = blockingQueue; System.out.println(blockingQueue.getClass().getName());//通过反射获取类名 } //加入队列 public void myProd() throws Exception{ String data=null; boolean retValue; while (flag){ data=atomicInteger.incrementAndGet()+"";//++i retValue=blockingQueue.offer(data,2L, TimeUnit.SECONDS); if(retValue){ System.out.println(Thread.currentThread().getName()+"\t 插入队列"+data+"成功"); }else{ System.out.println(Thread.currentThread().getName()+"\t 插入队列"+data+"失败"); } try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) {e.printStackTrace();} } System.out.println(Thread.currentThread().getName()+"\t 大老板叫停了,表示FLAG=FALSE,产生多做结束。"); } //消费队列 public void myConsumer() throws Exception{ String result=null; while (flag) { result=blockingQueue.poll(2L,TimeUnit.SECONDS); if(result==null||result.equalsIgnoreCase("")){ flag=false; System.out.println(Thread.currentThread().getName()+"\t超过2秒钟没有取出消费退出"); System.out.println(); System.out.println(); return; } System.out.println(Thread.currentThread().getName()+"\t消费队列"+result+"成功"); } } //停止操作 public void stop()throws Exception{ this.flag=false; } } //main方法 public class ProdConsumer_BlockQueueDemo { public static void main(String[] args) { MyResource myResource=new MyResource(new ArrayBlockingQueue<>(10)); new Thread(()->{ System.out.println(Thread.currentThread().getName()+"\t生产线程启动"); try { myResource.myProd(); } catch (Exception e) { e.printStackTrace(); } },"Prod").start(); new Thread(()->{ System.out.println(Thread.currentThread().getName()+"\t消费线程启动"); try { myResource.myConsumer(); } catch (Exception e) { e.printStackTrace(); } },"Consumer").start(); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) {e.printStackTrace();} System.out.println(); System.out.println(); System.out.println("5秒钟时间到,大老板main线程叫停,活动结束!"); try { myResource.stop(); } catch (Exception e) { e.printStackTrace(); } } } //=====================================结果 java.util.concurrent.ArrayBlockingQueue Prod 生产线程启动 Consumer 消费线程启动 Consumer 消费队列1成功 Prod 插入队列1成功 Consumer 消费队列2成功 Prod 插入队列2成功 Consumer 消费队列3成功 Prod 插入队列3成功 5秒钟时间到,大老板main线程叫停,活动结束! Prod 大老板叫停了,表示FLAG=FALSE,产生多做结束。 Consumer 超过2秒钟没有取出消费退出 Process finished with exit code 0

Callable

为什么有Runnable接口还要用callable接口?

带有返回值, 高并发的时候可以查看多线程的状态。
public class CallableDemo { public static void main(String[] args) throws Exception{ //FutureTask(Callable<V> ) FutureTask<Integer> futureTask=new FutureTask<>(new MyThread2()); new Thread(futureTask,"AA").start();//给这个线程更多的时间去计算。 new Thread(futureTask,"BB").start(); System.out.println(Thread.currentThread().getName()+"*******"); int result=100; // while (!futureTask.isDone()){//任务完成返回true // // } int result2=futureTask.get(); System.out.println("********reulst:"+(result+result2)); } } class MyThread implements Runnable{ @Override public void run() { } } class MyThread2 implements Callable<Integer>{ @Override public Integer call() throws Exception { System.out.println(Thread.currentThread().getName()+"******"); TimeUnit.SECONDS.sleep(3); return 1024; } } //============== main******* AA****** ********reulst:1124

线程池使用的优势

线程池的工作主要时控制允许的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量超出的的线程排队等候 ,等其他线程执行完毕,再从队列中取出任务来执行。
[ɪɡˈzekjətə(r)] Executor
notion image

3种线程池

一般写的时候用ExecutorService ExecutorService extends Executor
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); } //7大参数意义 线程池的底层东西 1、创建线程池后,当有请求任务之后,就会安排池中的线程去执行请求任务,近似理解为今日当值线程 2、当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。 //corePoolSize :线程池中的常驻核心线程数 //maximumPoolSize:线程池能够容纳同时执行的最大线程数,此值必须大于等于1 //keepAliveTime:多余的空闲线程的存活时间当前线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime时,多余空闲线程会被销毁直到剩下corePoolSize个线程位置。 //unit:keepAliveTime的单位 //workQueue:任务队列,被提交但尚未被执行的任务 //threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程一般用默认的即可 //handler:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数时如何来 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
  • 创建了线程池后,等待提交过来的任务请求
  • 当调用execute()方法添加一个请求任务时,线程池会做如下判断
    • 如果正在运许的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
    • 如果正在运行的线程大于或等等于corePoolSize,那么将这个任务放入队列
    • 如果这时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是创建非核心线程立即运行这个任务
    • 如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
  • 当一个线程完成任务时,它会从队列中取下一个任务来执行
  • 当一个线程无事可做超过一定时间(KeepAlivettTime)时,线程池会判断
    • 如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉
    • 所以线程池的所有任务完成后它最终会收缩到corePoolSize的大小。
public class T1 { public static void main(String[] args) { ExecutorService executorService=new ThreadPoolExecutor(2, 5,100L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy() ); try{ for (int i = 1; i <=8 ; i++) { final int tmpl=i; executorService.execute(()->{ System.out.println(Thread.currentThread().getName()+"号窗口,服务顾客:"+tmpl); try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) {e.printStackTrace();} }); } }catch(Exception e){ e.printStackTrace(); }finally{ executorService.shutdown(); } } }

说说线程池的底层工作原理?

notion image

线程池的拒绝策略?

第七个参数
  1. AbortProlicy(默认):[ˈpɒləsi] 直接抛出异常RejectedExecutionException异常阻止系统正常运行。
  1. CallerRunnsPolicy:“调用者运行”一种调价机制,该策略既不会抛其任务,也不会抛出异常,而是将某些任务退回到调用者,从而降低新的流量。
  1. DiscardOldestPolicy:抛弃队列中等待最久的任务,然后当前任务加入队列中尝试再次提交当前任务。
  1. DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案。

那么多方法?你用那个多?超级大坑

答案一个都不用,我们生产上只能使用自定义的

Executor中JDK已经给你提供了,为什么不用?

newFixedThreadPool()和newSingleThreadExecutor实现的都是LinkedBlockingQueue<Runnable>());

你在工作中时如何使用线程池的,是否自定义过线程池使用

public class T1 { public static void main(String[] args) { ExecutorService executorService=new ThreadPoolExecutor( 2, 5, 1L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy() ); try{ for (int i = 1; i <=9; i++) { final int tmpl=i; executorService.execute(()->{ System.out.println(Thread.currentThread().getName()+"号窗口,服务顾客:"+tmpl); }); } }catch(Exception e){ e.printStackTrace(); }finally{ executorService.shutdown(); } } } //最大值5+3==8

线程池配置合理线程数 ?你是如何考虑

要知道服务器是多少核

死锁编码及定位分析

是什么?
两个多个争抢持有线程的一种互相等待的现象,
notion image
  • 系统资源不足
  • 进程运行推进顺序不合适
  • 资源分配不当
//使用jps 查看进程。 linux ps -ef|grep xxxx //命令 ls -l jsp -l
notion image
//jstack 把上面的编号放下面 //jstack 9636
notion image

GC相关

notion image
JVM体系结构概述
  • 类的加载器分为
    • 类的加载器是什么?
      • Java 源程序(.java 文件)编译成 Java 字节代码(.class 文件)
      • 类加载器负责读取 Java 字节代码,并转换成 java.lang.Class 类的一个实例。通过此实例的 newInstance() 方法就可以创建出该类的一个对象。实际的情况可能更加复杂,比如 Java 字节代码可能是通过工具动态生成的,也可能是通过网络下载的。
      • 基本上所有的类加载器都是 java.lang.ClassLoader 类的一个实例根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个 Java 类,除此之外, ClassLoader 还负责加载 Java 应用所需的资源,如图像文件和配置文件等
      根类加载器(bootstrap class loader):它用来加载 Java 的核心类,并不继承自 java.lang.ClassLoader 由C++实现 开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。
      notion image
    • 双亲委派机制是什么
    • 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式,即每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己才想办法去完成。
    • 沙箱安全机制?
      • 字节码校验器(bytecode verifier):确保java类文件遵循java语言规范,可以帮助java程序实现内存保护。但不是所有的类文件都会经过字节码校验,比如核心类。
      • 类装载器(class loader):类装载器在三个方面对java沙箱起作用
          1. 防止恶意代码干涉善意代码---->双亲委派机制
          1. 守护了被信任的类库边界
          1. 将代码归入保护域,确定了代码可以进行哪些操作。
          · 从最内层JVM自带类加载器开始加载,外层恶意同名类得不到加载。 · 严格通过包来区分访问与,外层恶意的类通过内置代码无法获得权限访问内层类。
      • 存取控制器(access controller):可以控制核心API对操作系统的存取权限,而这个控制的策略设定,可以由用户指定。
      • 安全管理器(security manager):是核心API和操作系统之间的主接口。实现权限控制,比存取控制优先级高。
      • 安全软件包(security package):java.security下的类和扩展包下的类,允许用户为自己的应用增加信的安全。包括:安全提供者、信息摘要、数字签名、加密、鉴别

GC的作用域

notion image
notion image

2、复制算法 年轻代

复制之后有交换,谁空谁是true 不会产生内存碎片,会浪费空间
notion image

后两者用于

标记清楚

notion image

标记复制 既不浪费空间又不浪费时间耗时比较多

notion image

问题?

JVM垃圾回收的时候如何确定垃圾?是否知道什么是GC Roots

什么是垃圾?简单的说就是内存中已经不再被使用到的空间就是垃圾
  • 引用计数器:判断引用+1,很难判断循环互相引用问题
  • 枚举根节点做可达性分析(根搜索路径算法)
    • notion image
    • 从“GC Roots”的对象作为起始点,这个被称为GC Roots的对象开始向下搜索,如果一个对象到GC Roots没有任何引用链相连时,则说明此对象不可用。也即给定一个集合的引用作为根出发,通过引用关系遍历对象图,能被遍历到(可到达)对象就判定为存活,没有被遍历到就自然被判定为死亡。
    • 什么可以作为GCRoot?

JVM参数类型分为的标配参数和X参数、xx参数(重点)

-Xms 初始的堆空间 等价于-XX:+initialHeepSize
  • 标配参数
    • version
    • help
    • java -showversion
  • X参数(了解)
    • Xint 解释执行
    • Xcomp 编译执行
    • Xmixed 混合模式(先编译后执行)
  • XX参数
    • Boolean类型
      • XX:+或者-某个熟悉 +表示开启 -表示关闭
    • KV设值类型
      • XX:属性key==属性值value
    • jinfo
      • notion image

第一种:如何查看一个正在运行中的java程序,他的某个jvm参数是否开启?具体值是多少?

常用jps(查看后台进程)和jinfo(正在运行的java程序)

Boolean类型

PrintGCDetails (打印GC)细节
jinfo -flag PrintGCDetails 号码
D:\data\jdk\bin>jinfo -flag PrintGCDetails 3664 -XX:-PrintGCDetails #表示这一次根本没有添加 PrintGCDetails #开启了 D:\data\jdk\bin>jinfo -flag PrintGCDetails 600 -XX:+PrintGCDetails
UserSerialGc(串行垃圾回收器)
UserParallelGC(并行垃圾回收器)

KV类型设置

MetaspaceSize(元空间大小)
D:\data\jdk\bin>jinfo -flag MetaspaceSize 9444 -XX:MetaspaceSize=21807104 #修改参数 D:\data\jdk\bin>jinfo -flag MetaspaceSize 11028 -XX:MetaspaceSize=1262485504
MaxTenuringThreshold() [ˈθreʃhəʊld] 设置伊甸园存活次数

第二种查看初始默认值

查看JVM参数盘点家底

运行java的同时打印信息

//运行时并修改
notion image

常用参数

新生代:老年代 1:2 伊甸园:form:to 8:1:1
//内存被撑爆 java.lang.OutOfMemoryError:
notion image
full gc分析规律
【名称:gc当前内存占用->GC后内存占用(该区内存总大小)】

强引用、软引用、弱引用、虚引用分别是什么?java.lang.ref.*

强引用

内存不足是,JVM开始进行垃圾回收,就算出现了OOM也不会对该对象进行回收,死都不收。
public class StrongReferenceDemo { public static void main(String[] args) { Object obj=new Object(); Object obj2=obj; obj=null; System.gc(); System.out.println(obj2); } } //================ java.lang.Object@16b98e56

软引用 SoftReference [sɒft]

内存足够的前提下我不收,内存不够的前提我必收
public class SoftReferenceDemo { public static void main(String[] args) { Object o1=new Object(); SoftReference<Object> softReference=new SoftReference<>(o1); System.out.println(o1); System.out.println(softReference.get()); o1=null; System.gc(); try{ //故意配置大对象 出现OOM 查看弱引用 byte[] bt=new byte[30*1024*1024]; }catch(Exception e){ e.printStackTrace(); }finally{ System.out.println(o1); System.out.println(softReference.get()); } } private static void softRef_Memory_NotEnouth() { Object o1=new Object(); SoftReference<Object> softReference=new SoftReference<>(o1); System.out.println(o1); System.out.println(softReference.get()); o1=null; System.gc(); System.out.println(o1); System.out.println(softReference.get()); } } //-Xms5m -Xmx5m -XX:+PrintGCDetails

弱引用

垃圾回收一律回收不管够不够用
public class WeakReferenceDemo { public static void main(String[] args) { Object o1=new Object(); WeakReference<Object> weakReference=new WeakReference<>(o1); System.out.println(o1); System.out.println(weakReference.get()); o1=null; System.gc(); System.out.println("============="); System.out.println(o1); System.out.println(weakReference.get()); } } // java.lang.Object@16b98e56 java.lang.Object@16b98e56 ============= null null

软引用和弱引用的场景

假如有一个应用需要读取大量的本地图片:

WeakHashMap认识嘛?

public class WeakHashMapDemo { public static void main(String[] args) { myHashMap(); System.out.println("============"); myWeakHashmap(); } private static void myWeakHashmap() { WeakHashMap<Integer,String> map=new WeakHashMap<>(); Integer key=new Integer(2); String value="WeakHashMap"; map.put(key,value); System.out.println(map); key=null; System.out.println(map); System.gc(); System.out.println(map); } private static void myHashMap() { HashMap<Integer,String> map=new HashMap<>(); Integer key=new Integer(1); String value="Hashmap"; map.put(key,value); System.out.println(map); key=null; System.out.println(map); System.gc(); System.out.println(map); } } {1=Hashmap} {1=Hashmap} {1=Hashmap} ============ {2=WeakHashMap} {2=WeakHashMap} {}

虚引用——>幽灵引用 PhantomReference

如果一个对象仅持有虚引用,那么他就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,
public class ReferenceQueueDemo { public static void main(String[] args) throws InterruptedException { Object o1=new Object(); ReferenceQueue<Object> referenceQueue=new ReferenceQueue<>(); WeakReference<Object> weakReference=new WeakReference<>(o1,referenceQueue); System.out.println(o1); System.out.println(weakReference.get()); System.out.println(referenceQueue.poll()); System.out.println("==================="); o1=null; System.gc(); Thread.sleep(5); System.out.println(o1); System.out.println(weakReference.get()); System.out.println(referenceQueue.poll()); } } //================= java.lang.Object@16b98e56 java.lang.Object@16b98e56 null =================== null null java.lang.ref.WeakReference@7ef20235 ///========================================== public class PhatomReferenceDemo { public static void main(String[] args) { Object o1=new Object(); ReferenceQueue<Object> referenceQueue=new ReferenceQueue<>(); PhantomReference<Object> phantomReference=new PhantomReference<>(o1,referenceQueue); System.out.println(o1); System.out.println(referenceQueue.poll()); System.out.println(phantomReference.get()); System.out.println("================"); o1=null; System.gc(); System.out.println(o1); System.out.println(referenceQueue.poll()); System.out.println(phantomReference.get()); } } java.lang.Object@16b98e56 null null ================ null java.lang.ref.PhantomReference@7ef20235 null Process finished with exit code 0

GCroot 和四大引用小总结

notion image

OOM的认识

java.lang.StackoverFlowError 栈撑爆 无限递归
notion image

java.lang.OutofMemoryError:GC overhead limit exceeded

大量的资源被用作来及回收有用的很少

java.lang.OutofMemoryError:Direct buffer memory 内存挂掉

参数配置

java.lang.OutofMemoryError:unable to create new native thread

原因:

java.lang.OutofMemoryError:Metaspace 元空间

-XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=8m

垃圾回收器

4种:

gc回收的类型主要有分几种 7

UseSerialGC 串行
notion image
notion image
参数说明

Server和Client模式

32位Window操作系统,无论如何都默认使用Client的JVM模式

新生代

串行GC(Serial)/(Serial Copying) DefNew+Tehured
老年代
配置一个后对应的新生代变成对应的。
notion image
notion image

G1

-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:UseG1GC
  • 年轻代和老年代是各自独立且连续的内存块。
  • 年轻代收集使用eden+s0+s1进行复制算法。
  • 老年代收集必须扫描整个老年代区域
  • 都是以尽可能少而快速地执行GC为设计原则。
特性:
  • 跟CMS收集器一样,能与应用程序线程并发执行
  • 整理空闲空间更块。
  • 需要更多的时间来预测GC停顿时间。
  • 不希望牺牲大量的吞吐性能
  • 不需要更大的JAVA heep
目的:取代CMS收集器
与CMS相比以下更出色
  • 有整理内存过程的垃圾收集器,不会产生很多内存碎片
  • Stop The world更可控,G1在停顿时间上添加了预测机制,用户可以指定期望停顿时间。
为了取出内存碎片又要保留CMS垃圾收集器低暂停时间的优点。
Eden,Surivior和Tenured等内存区域变成了一个个大小一样的region,region从1-32M不等。一个region有可能属于Eden,Surivior或者Tenured内存区域。
notion image
notion image
notion image
notion image
notion image
notion image

参数配置

  • XX:+UseG1GC
  • XX:G1HeapRegionSize=n 设置G1区域的大小 值2的幂
  • XX:MaxGCPauseMillis=n 最大GC停顿时间。这是个软目标,JVM将尽可能(但不保证)停顿小于这个时间
  • XX:lnitiatingHeapOccupancyPercent=n 堆占用了多少的时候就出发GC,默认为45
  • XXConcGCThreads=n 并发GC使用的线程数
  • XX:G1ReservePercent=n 设置作为空闲空间的预留内存百分比,以降低目标空间溢出的风险,默认值10%
  • XX:+UseG1GC -Xmx32g -XX:MaxGCPauseMillis=100 毫秒数

JVMGC结合Springboot微服务优化简介

  1. IDEA开发微服务工程
  1. maven进行clean packege
  1. 要求微服务启动的时候,同时配置我们的JVM/GC的调优参数
    1. 内 run里面配置
    2. 外===》重点
  1. 公式 java -server jvm的各种参数 -jar 第一步上面的jar/war的名字
undertow

Linux

  • top 查看性能 uptime 系统性能精简版 整机
  • vmstat 查看CPU vmstat -n 2 3 每两秒 捕捉3次
    • Proce r:允许和等待cpu时间片的进程数,原则上1核cpu运行队列不超过2 最大为小于cpu核数*2 否则代表系统压力过大。 b:等待资源的进程数
    • cpu us:用户进程消耗cpu时间百分比,us值高,用户进程消耗cpu时间多,如果长期大于50%,优化程序。 sy:内核进程消耗的cpu时间百分比 id:cpu空闲率
    • 查看所有cpu核的信息 mpstat -P ALL 2 每两秒采样一次
    • 每个进程使用cpu的用量分解信息 pidstat -u 1(多久采样一次) -p 进程编号
  • free 内存 free -m free -g(单位) 20%《可用内存《70%
    • 查看额外 pidstat -p 进程编号 -r 时间
  • 硬盘查看 df -h
  • 磁盘io:iostat
    • 磁盘系能评估 iostat -xdk 2 3 rkB/s
      • notion image
    • 查看额外 pidstat -d 时间 -p 进程编号
  • 网络:ifstat 1 各个网卡的in, out 观察网络的负载情况,程序网络读写是否正常
    • 程序网络I/O优化
    • 增加网络I/O宽带
ps -ef|grep java 查看指定进程信息

LINUx调优

notion image
ps -ef|grep java|grep -v grep
ps -mp 进程 -o THREAD,tid,time //查看具体线程 -m显示所有的线程 -p进程cpu的时间
printf "%x\n"

对于JDK 自带的JVM监控和性能分析工具公国那些?一般你怎么用的?

字符串常量

方法区和运行时常量池溢出

由于运行时常量池是方法区的一部分,所以两个区域的溢出测试可以放到一起进行。
public class StringPool58Demo {    public static void main(String[] args) {        String str1=new StringBuilder("58").append("tongcheng").toString();        System.out.println(str1);        System.out.println(str1.intern());        System.out.println(str1==str1.intern());        System.out.println();        String str2=new StringBuilder("open").append("jdk").toString();        System.out.println(str2);        System.out.println(str2.intern());        System.out.println(str2==str2.intern());   } } //============================== 58tongcheng 58tongcheng true openjdk openjdk false  //jdk1.8这里是false 因为jdk自己带了一个java

为什么?

有一个初始化的java字符串(JDK出娘胎自带的),在加载sun.misc.version这个类的时候进入常量池。
System类初始化,有一个vesrion类
jdk 15变成了 java.lang.VersionProps

类加载器和rt.jar

根加载器提前部署加载rt.jar

源码分析openjdk

openjdk\jdk\src\share\classes\sun\misc 去找相关的

JUC

隐式锁(synchronized关键字使用的锁)默认是可重入锁

synchronized

每个锁对象拥有一个锁计数器和一个指向持有该锁线程的指针。
当执行monitorenter时,如果目标锁对象的计数器为0,那么说明它没有被其他线程所持有,jvm需要会将该锁对象的持有线程设置为当前线程,并且将计数器+1;
在目标对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么JVM可以将计数器+1,否则需要等待,直至有线程释放该锁。
当执行monitorexit是,JVM则需要将锁对象的计数器-1。计数器为0代表释放

LockSupport

前置知识:可重入锁 LockSupport

锁的阻塞唤醒方式

1、使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程
2、使用JUC包中的Codition的await()方法让线程等待,使用signal()方法唤醒线程
3、LookSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程
优点:unpark可以在park之前执行,不会行成阻塞。
LockSopprt是一个线程的阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。
  • 每个使用它的线程都一个许可(permit)关联。permit相当于1,0的开关,默认0
  • 调用一次unpark变为1
  • 调用一次park会消费permit,也就是1变成0,同时park立即返回
  • 如再次调用park会变成阻塞(因为permit为0会阻塞在这里,一直到permit变为1).这是调用unpark会把permit置为1;
  • 每个线程都一个相关的permit,permit最多只有一个,重复调用unpark也不会积累凭证。

调用park时

有凭证,直接消耗这个凭证然后正常退出。
没有凭证,就必须阻塞等待凭证可用。

而unpark相反

增加一个凭证,但凭证最多只能有一个,累加无效。

AQS

初步了解

AbstractQueuedSynchronizer
抽象队列容器。
用来构建锁,或者其它同步器组件的重量级基础框架及整个JUC体系的基石,
notion image
如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的时CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中,这个队列就是AQS的抽象表现。它将请求共享资源的线程封装成队列的节点(Node),通过CAS,自旋以及LockSupport.park()的方式,维护state变量的状态,使并发达到同步的效果。
提供一个框架,用于实现依赖于先进先出(FIFO)等待队列的阻塞锁和相关同步器(信号量、事件等)。这个类被设计成是大多数依赖单个原子{@codeint}值来表示状态的同步器的有用基础。子类必须定义更改此状态的受保护方法,并定义该状态对于获取或释放此对象意味着什么 AQS使用一个volatile的int类型的成员变量来表示同步状态,通过内置的FIFO队列来完成资源获取的排队工作将每条要去抢占资源的线程封装成一个Node节点来实现锁的分配,通过CAS完成对State值的修改 AQS=state+CLH队列 /**     * The synchronization state.     */    private volatile int state; /*     * Overview.     *     * The wait queue is a variant of a "CLH" (Craig, Landin, and     * Hagersten) lock queue. CLH locks are normally used for     * spinlocks. We instead use them for blocking synchronizers by     * including explicit ("prev" and "next") links plus a "status"     * field that allow nodes to signal successors when releasing     * locks, and handle cancellation due to interrupts and timeouts.     * The status field includes bits that track whether a thread     * needs a signal (using LockSupport.unpark). Despite these     * additions, we maintain most CLH locality properties.     *     * To enqueue into a CLH lock, you atomically splice it in as new     * tail. To dequeue, you set the head field, so the next eligible     * waiter becomes first.     *     * +------+ prev +-------+       +------+     * | head | <---- | first | <---- | tail |     * +------+       +-------+       +------+  */ abstract static class Node {        volatile Node prev;       // initially attached via casTail        volatile Node next;       // visibly nonnull when signallable        Thread waiter;            // visibly nonnull when enqueued        volatile int status;      // 等待状态 }
  • 0就是没人,自由状态可以办理
  • 大于等于1,有人占用窗口,等着去

面向锁的使用者 定义了程序员和锁交互的API

同步器

面向锁的实现者。
notion image
notion image
notion image

ReentrantLock 加锁过程

  1. 尝试枷锁
  1. 加锁失败,线程进入队列
  1. 线程进入队列后,进入阻塞状态

AOP相关

Spring=IOC+AOP+TX

Aop常用注解

  • @Before 前置通知:目标方法之前执行
  • @After 后置通知:目标方法之后执行(始终执行)[ˈɑːftə(r)]
  • @AfterReturning 返回后通知:执行方法结束前执行(异常不执行)
  • @AfterThrowing 异常通知:出现异常时执行
  • @Around 环绕通知:环绕目标方法执行

面试题

你肯定知道Spring,那说说aop的全部通知顺序springboot或springboot2对aop的执行顺序影响?

说说你使用aop中碰到的坑?

spring 4版本 System.out.println("spring版本"+ SpringVersion.getVersion()+                "\tspringboot版本"+ SpringBootVersion.getVersion()); System.out.println(); calcSerice.div(10,2);
异常
类似
try{ @Before()     method.invoke();     @AfterReturning }catch(Exception e){     @AfterThrowing }finally{      @After() } spring5 @Test void testAop5() {   System.out.println("spring版本"+ SpringVersion.getVersion()+                "\tspringboot版本"+ SpringBootVersion.getVersion());   System.out.println();   calcSerice.div(10,2); } //=====================正常执行 spring版本5.3.2 springboot版本2.4.1 ******我是环绕通知AAA ******before 前置通知 ======CalcServiceImpl==被调用,结果:5 ******afterReturning 返回通知 ******after 后置通知 ******我是环绕通知BBB //======================异常模式 spring版本5.3.2 springboot版本2.4.1 ******我是环绕通知AAA ******before 前置通知 ******afterThrowing 异常通知 ******after 后置通知 java.lang.ArithmeticException: / by zero

循环依赖

多个bean之间相互依赖,行成一个闭环。
构造器循环依赖是无法解决的
你想让构造器注入支持循环依赖,是不存在的
public class ClientConstructor {    public static void main(String[] args) {        new ServiceA(new ServiceB(new ServiceC(还需要再加入new ServiceA)));   } }
使用set就不没事。
  • 默认的单例singlenton的场景是支持循环依赖的,不报错
  • 原型prototype的场景是不支持循环依赖的,会报错

DefaultSingletonBeanRegistry

map解决的三级缓存
/** 1级 Cache of singleton objects: bean name to bean instance. */ /** 1级缓存也叫单列池。singletonObjects:存放已经经历了完整生命周期的Bean对象 放成品*/ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); /**3级 Cache of singleton factories: bean name to ObjectFactory. */ /**3级缓存 存放可以生产Bean的工厂. */吧 private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); /**2级 Cache of early singleton objects: bean name to bean instance. */ /**2级缓存 earlySingletonObjects:存放早期暴露出来的Bean对象,Bean的生命周期未结束(属性还未填充 ) 放半成品 */ private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
只有单列的Bean会通过三级缓存提前暴露来解决循环依赖的问题,而非单例的bean,每此从容器中获取都是一个新的对象,都会重新创建所以非单列的bean是没有缓存的,不会将其放到三级缓存中。

循环依赖Debug

  • 实列化 new一个内存空间
  • 初始化 属性赋值
3个Map4个方法
notion image
  • getSingleton 从spring容器得到bean没有去下一个doCreateBean
  • doCreateBean 创建的时候 对属性填充populateBean 填充玩执行addsingleton 放回到容器
#第一层 singletonObjects 存放已经初始化好了的Bean #第二层 earlySingletonObjects 存放的是实列化,但是未初始化的Bean #第三层 singletonFactories 存放的是FacoryBean。 假如A类实现了FactoryBean,那么依赖注入的时候不是A类,而是A类产生的Bean 1、A创建过程中需要B,于是A将自己放到三级缓存里面,去实例化B 2、B实列化的时候发现需要A,于是B先查一级缓存,没有在查二级缓存,还是没有在查三级缓存,找到了A然后把三级缓存里面的的这个A放到二级缓存里面,并删除三级缓存里面的A 3、B顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A任然是创建中状态)然后回来接着创建A,此时B以及创建结束,直接从一级缓存里面拿到B,然后完成创建,并将A自己放到一级缓存里面
notion image
bean会被高度抽象为RootBeanDefinition
notion image

如何解决的巡回依赖?

Spring创建bean主要分为两个部分,创建原始bean对象,接着去填充对象属性和初始化
每次创建bean之前,我们都会从缓存中查下有没有该bean,因为是单列,只能有一个
当我们创建beanA的原始对象后,并把它放到三级缓存中,接下来就该填充对象属性了,这时候发现了依赖beanB,同样的流程,创建完beanB填充属性时,又发现它依赖了beanA又是同样的流程。
不同的是:
这个时候可以在三级缓存中查到刚放进去的原始对象beanA,所以不需要继续创建,用它注入beanB,完成beanB的创建
既然bean创建好了,所以beanA就可以完成填充属性的步骤了,接着执行剩下的逻辑,闭环完成。
Spring解决循环依赖依靠的是Bean的“中间态”这个概念,而这个中间态指的是已经实列化还没初始化的状态——》半成品
A,B循环引用,实列化A的时候将其放入三级缓存中,接着填充属性的时候,发现依赖B,同样的流程也是实列化后放入三级缓存,接着去填充属性时又发现自己依赖A,这时候从缓存中查找到早期暴露的A,没有AOP代理的话,直接将A的原始对象注入B,完成B的初始化后,进行属性填充和初始化,这时候B完成后,就去完成剩下的A的步骤,如果Aop代理,就进行Aop处理获取代理后的对象A,注入B,走剩下的流程。
notion image
 
 

曾经也有 个和你一样的人来过这里。

「姿や形は問題ではなく、「魂」が問題です。」

Vercel Logo

「健全なる魂は健全なる精神と健全なる肉体に宿る。」

「あなたの魂、受け取りました。」

VercelVercel

Clover © 2022 幸いです 全著作権所有.