java常用类
String类
String 是一个 final 类,代表不可变的字符序列 char[]
String str = new String();//char[] value = new char[0];
String str = new String("abc");//char[] value = new char[]{'a','b','c'};
**String 常用方法1
int length() :返回字符串的长度: return value.length
char charAt(int index): : 返回某索引处的字符return value[index]
boolean isEmpty() :判断是否是空字符串:return value.length == 0
String toLowerCase() :使用默认语言环境,将 String 中的所有字符转换为小写
String toUpperCase() :使用默认语言环境,将 String 中的所有字符转换为大写
String trim(): :返回字符串的副本,忽略前导空白和尾部空白
boolean equals(Object obj): :比较字符串的内容是否相同
boolean equalsIgnoreCase(String anotherString) :与equals方法类似,忽略大小写
String concat(String str) :将指定字符串连接到此字符串的结尾。 等价于用“+”
int compareTo(String anotherString): :比较两个字符串的大小
String substring(int beginIndex): :返回一个新的字符串,它是此字符串的从
beginIndex开始截取到最后的一个子字符串。
String substring(int beginIndex, int endIndex) : :返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。
**String 常用方法2
boolean endsWith(String suffix): :测试此字符串是否以指定的后缀结束
boolean startsWith(String prefix): :测试此字符串是否以指定的前缀开始
boolean startsWith(String prefix, int toffset): :测试此字符串从指定索引开始的子字符串是否以指定前缀开始
boolean contains(CharSequence s) :当且仅当此字符串包含指定的 char 值序列时,返回 true
int indexOf(String str): :返回指定子字符串在此字符串中第一次出现处的索引
int indexOf(String str, int fromIndex): :返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
int lastIndexOf(String str): :返回指定子字符串在此字符串中最右边出现处的索引
int lastIndexOf(String str, int fromIndex): :返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索 注:indexOf和lastIndexOf方法如果未找到都是返回-1
**String 常用方法3
String replace(char oldChar, char newChar): :返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
String replace(CharSequence target, CharSequence replacement): :使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。
String replaceAll(String regex, String replacement) : : 使 用 给 定 的replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
String replaceFirst(String regex, String replacement) : : 使 用 给 定 的replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
StringBuffer 类
线程安全的,char[] super(str.length()+16);
StringBuffer sb1=new StringBuffer();//char[] value=new char[16];底层创建了一个长度16的数组
sb1.append('a');
sb1.append('b');
StringBuffer sb2=new StringBuffer("abc");//char[] value=new char["abc".length()+16];
扩容问题:如果要添加的数据底层数组盛不下了,那就需要扩容底层的数组。
默认情况下,扩容为原来容量的2倍+2,同时将原有数组中的元素复制到新的数组中。
StringBuffer常用方法
StringBuffer append(xxx):提供了很多的append()方法,用于进行字符串拼接
StringBuffer delete(int start,int end):删除指定位置的内容
StringBuffer replace(int start, int end, String str):把[start,end)位置替换为str
StringBuffer insert(int offset, xxx):在指定位置插入xxx
StringBuffer reverse() :把当前字符序列逆转
此外,还定义了如下的方法:
public int indexOf(String str)
public String substring(int start,int end)
public int length()
public char charAt(int n )
public void setCharAt(int n ,char ch)
StringBuilder 类
线程不安全的 char[]
集合
1、单列集合框架结构
Collection 接口:单列集合,用来存储一个一个的对象
List接口:存储有序的 、可重复的数据 --》“动态”数组
ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[]存储
源码分析:jdk7:ArrayList list= new ArrayList();底层创建了一个长度10的数组。扩容原来容量1.5倍
list.add(123);//elementData[0]=new Integer(123);
...
list.add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容。
默认扩容为原来的容量1.5倍,同时需要将原有数组中的数据复制到新数组中。
jdk8:
LinkedList:对于频繁插入、删除操作,使用此类效率比ArrayList高,底层使用双向链表存储
Vector:古老实现类,线程安全的,底层使用Object[]存储
Set接口:存储无序的、不可重复的数据
HashSet、LinkedHashSet、TreeSet
Map接口:双列集合,用来存储一对(key-value)一对的数据
HashMap、LinkedHashMap、TreeMap、Hashtable、Properties
Collection接口方法:
1、 添加
add(Object obj)
addAll(Collection coll)
2、获取有效元素的个数
int size()
3、清空集合
void clear()
4、是否是空集合
boolean isEmpty()
5、是否包含某个元素
boolean contains(Object obj):是通过元素的equals方法来判断是否是同一个对象
boolean containsAll(Collection c):也是调用元素的equals方法来比较的。拿两个集合的元素挨个比较。
6、删除
boolean remove(Object obj) :通过元素的equals方法判断是否是要删除的那个元素。只会删除找到的第一个元素
boolean removeAll(Collection coll):取当前集合的差集
7、取两个集合的交集
boolean retainAll(Collection c):把交集的结果存在当前集合中,不影响c
8、集合是否相等
boolean equals(Object obj)
9、转成对象数组
Object[] toArray()
10、获取集合对象的哈希值
hashCode()
11、遍历
iterator():返回迭代器对象,用于集合遍历
1、volatile 是Java虚拟机提供的轻量级的同步机制:
1.1保证可见性
1.2不保证原子性
1.3禁止指令重排
1.验证volatile的可见性
1.1 假如 int number=0;,number变量之前根本没有添加volatile关键字修饰,没有可见性
1.2 添加了volatile,可以解决可见性问题
2.验证volatile不保证原子性
2.1 原子性指的是什么意思?
不可分割,完整性,也即某个线程正在做某个具体业务时,中间不可以被加塞或者被分割。需要整体完整要么同时成功,要么同时失败。
2.2 volatile 不保证原子性的案例
2.3 why
2.4 如何解决原子性
*加sync
*使用我们的juc 下AtomicInteger
注:AtomicInteger 低层原理CAS
JUC
2、JMM你谈谈
JMM三大特性:1 可见性
2 原子性
3 有序性
JMM(java内存模型Java Memory Model)本身是一种抽象的概念并不真实的存在,它描述的是一组规则或规范,通过这组规范义了程序中各个变量的访问方式。
JMM关于同步的规定:
1、线程解锁前,必须把共享变量的值刷新回主内存
2、线程加锁前,必须读取主内存的最新值到自己的工作内存
3、加锁解锁是同一把锁
注:笔试:
你在哪些地方用到过volatile?
答:3.1 单例模式DCL代码
3.2 单例模式volatile分析
//
//多机
//DCL 双端检锁机制不一定线程安全,原因是有指令重排序的存在,加入volatile可以禁止指令重排
//原因在于某一个线程执行到第一次检测,读取到的instance不为null时,instance的引用对象可能没有完成初始化。
====================
====================CAS
====================
CAS是什么?
1.比较并交换
2.CAS底层原理?如果知道,谈谈你对UnSafe的理解
3.CAS缺点
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
CAS,它是一条CPU并发原语
它的功能是判断内存某个位置是否为预期值,如果是则更改为新的值,这个过程是原子的。
CAS是什么? ===>compareAndSet
1.比较并交换
饿涛没K INT这
AtomicInteger atomicInteger = new AtomicInteger(initialValue:5);
System.out.println(atomicInteger.compareAndSet(expect:5,update:2019))
2.CAS底层原理?
2.1 Unsafe 安谁付
自旋锁
是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe类存在于sun.misc包中,其
内部方法操作可以像C的指针一样直接操作内存,因为Java中CAS操作的执行依赖于Unsafe类的方法。
注意Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务。
2.2 atomiclnteger.getAndlncrement
3.CAS缺点
3.1 循环时间长开销很大
3.2 只能保证一个共享变量的原子操作。
3.3 引出来ABA问题
====================
====================ABA
====================
CAS--->UnSafe--->CAS底层思想--->ABA--->原子引用更新--->如何规避ABA问题
* ABA:狸猫换太子
* 解决ABA问题??? 理解原子引用 + 新增一种机制,那就是修改版本号(类似时间戳)
CAS不够???AtomicInteger
时间戳原子引用:AtomicStampedReference
ABADemo
Class AtomicReference<V>原子引用 软分次
====================
====================集合类
====================
List
线程不安权的例子:
public static void main(String[] args){
//List<String> list = new ArrayList<>();//线程不安全的
//List<String> list = Collections.synchronizedList(new ArrayList<>());//线程安全的
List<String> list = new CopyOnWriteArrayList<>();//写时复制
for (int i=1;i<=3;i++){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
/* 摸滴
*1 故障现象 肯克恩特 马特飞K森
* java.util.ConcurrentModificationException 并发修改异常(错误)
*
*2 导致原因
* 并发争抢修改导致,参考我们的花名册签名情况
* 一个人正在写入,另外一个同学过来抢夺,导致数据不一致异常。并发修改异常
*
*3 解决方案
* 3.1 new Vector<>(); // 蛙k的
* 3.2 Collections.synchronizedList(new ArrayList<>());//线程安全的
* 3.3 Class CopyOnWriteArrayList<E> (写时复制)
*
*4优化建议(同样的错误不犯第2次)
*
**/
}
Set
public static void main(String[] args){
Set<String> set = new HashSet<>();
Set<String> set = Collections.synchronizedSet(new HashSet<>());
Set<String> set = new CopyOnWriteArraySet<>();//写时复制
for (int i=1;i<=30;i++){
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(set);
},String.valueOf(i)).start();
}
}
Map
public static void main(String[] args){
Map<String,String> map = new HashMap<>();
Map<String,String> map = Collections.synchronizedMap(new HashMap<>());
Map<String,String> map = new ConcurrentHashMap<>(); //肯克恩特
for (int i=1;i<=30;i++){
new Thread(()->{
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,8));
System.out.println(set);
},String.valueOf(i)).start();
}
}
====================
====================Java锁
====================
公平锁、非公平锁
ReentrantLock()要是空代表的是非公平,true是公平
Java锁:公平锁、非公平锁、可重入锁、递归锁、自旋锁谈谈?请手写一个自旋锁
公平锁和非公平锁
公平锁是指多个线程按照申请锁的顺序来获取锁。
非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。
可重入锁(又名递归锁)
指的是同一线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码,在同一个线程在外层方法取锁的时候,在进入内层方法会自动获取锁。
也即是说,线程可以进入任何一个它已经拥有的锁的同步着的代码块。
独占锁/共享锁/互斥锁
自旋锁
是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。
面试题:写个生产者或阻塞对列
====================
====================CountDownLatch/CyclicBarrier/Semaphore使用过吗?
====================
JUC包
CountDownLatch 康当辣吃
CountDownLatch(i int)
让一些线程阻塞直到另一些线程完成一系列操作后才被唤醒
CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,调用线程会被阻塞。其它线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞),
当计数器的值变为零时,因调用了await方法被阻塞的线程会被唤醒,继续执行。
public static void main(String[] args) throws Exception{
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 ************班长最 后关门走人");
}
CyclicBarrier 塞克来克 拜尔优
作加法
public static void main(String[] args){
CyclicBarrier cyclicBarrier=new CyclicBarrier(parties:7,()->{System.out.println("*************召唤神龙");});
for (int i=1;i<=7;i++){
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t 收集到第:"+i+"龙珠");
try{
cyclicBarrier.await();
}catch(InterruptedException e){
e.printStackTrace();
}catch(BrokenBarrierException e){
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
Semaphore 三么付
信号量主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制。
Semaphore semaphore = new Semaphore(permits:3);
for(int i=1;i<=6;i++){
new Thread(()->{
try{
semaphore.acquire();//进
System.out.println(Thread.currentThread().getName()+"\t 抢到车位");
try{TimeUnit.SECONDS.sleep(timeout:3);}catch(InterruptedException e){e.printStackTrace();}
System.out.println(Thread.currentThread().getName()+"\t 停3秒离开车位");
}catch(InterruptedException e){
e.printStackTrace();
}finally{
semaphore.release();//释放
}
},String.valueOf(i)).start();
}
====================
====================阻塞队列知道吗?
====================
阻塞队列
1.阻塞队列有没有好的一面
2.不得不阻塞,你如何管理
阻塞队列
当阻塞队列是空时,从队列中获取元素的操作将会被阻塞。
当阻塞队列是满时,往队列里添加元素的操作将会被阻塞。
在多线程领域:所谓阻塞,在某些情况下会挂起线程(阻塞),一旦条件满足,被挂起的线程又会自动被唤醒。
为什么需要BlockingQueue
好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQuere都给你手包为了
Iterable
Collection
BlockingQueue//阻塞队列 辣K印
List
Collection
Queue
BlockingQueue
*ArrayBlockingQueue:由数组结构组成的有界阻塞队列。
方法类型 抛出异常 特殊值 阻塞 超时
插入 add(e) offer(e) put(e) offer(e,time,unit)
移除 remove() poll() take() poll(time,unit)
检查 element() peek() 不可用 不可用
*LinkdBlockingQueue:由链表结构组成的有界(但大小默认值为integer.MAX_VALUE)阻塞队列
PriorityBlockingQueue:支持优先级排序的无界阻塞队列。
DelayQueue使用优先级队列实现的延迟无界阻塞队列。
*SynchronousQueue:不存储元素的阻塞队列,也即单个元素的队列。
SynchronousQueue没有容量。与其他BlockingQueue不同,SynchronousQueue是一个不存储元素的BlockingQueue。
每一个put操作必须要等待一个take操作,否则不能继续添加元素,反之亦是然。
LinkedTransferQueue:由链表结构组成的无界阻塞队列。
LinkedBlockingDeque:由链表结构组成的双向阻塞队列。
生产者消费者模式:传统 ProdConsumer_TraditionDemo
线程池
消息中间件
锁
题目 :synchronized和Lock有什么区别?用新的Lock有什么好处?
1、原始构成
synchronized是关键字属于JVM层面,
monitorenter(底层是通过monitor对象来完成,其实wait/notify等方法也依赖于 monitor 对象只有在同步块或方法中才能调wait/notify等方法)
monitorexit
Lock是具体类(java.util.concurrent.locks.Lock)是api层面的锁
2、使用方法
synchronized 不需要用户去手动释放锁,当synchronized 代码执行完后系统会自动让线程释放对锁的占用
ReentrantLock 则需要用户去手动释放锁若没有主动释放锁,就有可能导致出现死锁现象。
需要Lock()和unLock()方法配合try/finally语句块来完成。
3、等待是否可中断
synchronized 不可中断,除非抛出异常或者正常运行完成
ReentrantLock 可中断,1设置超时方法 tryLock(Long timeout,TimeUnit unit)
2 LockInterruptibly()放代码块中,调用interrupt()方法可中断
4、加锁是否公平
synchronized 非公平锁
ReentrantLock 两者都可以,默认非公平锁,构造方法可以传入booLean值,true为公平锁,false为非公平锁
5、锁绑定多个条件Condition
synchronized 没有
ReentrantLock 用来实现分组唤配需要唤醒的线程们,可以精确唤醒,而不是像synchronized是么随机唤醒一个线程要么唤醒全部线程。
====================
====================CallableDemo 线程
====================
class MyThread implements Callable<Integer>{
pubic Integer call() throws Exception{
System.out.println("8888888888888888");
return 1024;
}
}
public static void main(){
FutureTask<Integer> futureTask = new FutureTask<>(new MyThread());
Thread t1 = new Thread(futureTask,name:"AAA");
t1.start();
System.out.println("**************result:"+futureTask.get());
}
为什么用线程池,优势?
答:线程池做的工作主要是控制的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大
数量超出数量的线程排队等候,等其它线程执行完毕,再从队列中取出任务来执行。
他的主要特点为:线程复用;控制最大并发数;管理线程。
1、降低资源消耗,通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
2、提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
3、提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的
分配,调优和监控。
线程池如何使用?
架构说明:
Java中的线程池是通过Executor框架实现的,该框架中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor这几个类。
一克塞K的
编码实现:
Executors.newScheduledThreadPool()//执行调度的带时间的
Executors.newWorkStealingPool(int)//java新增,使用目前机器上可用的处理器作为它的并行级别
重点:
ThreadPoolExecutor //线程池的底层就是这个类
//创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。LinkedBlockingQueue
Executors.newFixedThreadPool(int)//执行长期的任务,性能好很多 Fixed /fɪkst/ 费克思的
//创建一个单线程代的线程池,它只会用唯一的工作线程来执行任务,保证的有任务按照指定顺序执行。LinkedBlockingQueue
Executors.newSingleThreadExecutor()//一个任务一个任务执行的场景 Single /ˈsɪŋɡl/ 星狗 Executor /ɪɡˈzekjətə(r)/ 一克塞K的
//创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程。
Executors.newCachedThreadPool()//适用:执行很多短期异步的小程序或者负载较轻的服务器 Cached 卡死t
例:
public static void main(String[] args){
ExecutorService threadPool = Executors.newFixedThreadPool(5);//一池5个处理线程
ExecutorService threadPool = Executors.newSingleThreadExecutor();//一池1个处理线程
ExecutorService threadPool = Executors.newCachedThreadPool();//一池N个处理线程
try{
for (int i=1;i<=10;i++){
threadPool.Execute(()->{
System.out.println(Thread.currentThread().getName()+"\t办理业务");
});
}
}catch(Exception e){
e.printStackTrace();
}finally{
threadPool.shutdown();
}
}
例:2
public static void main(){
ExecutorService service= Executors.newFixedThreadPool(10);
ThreadPoolExecutor service2 = (ThreadPoolExecutor)service;
service2.setCorePoolSize(15);
service2.setKeepAliveTime();
service.execute(new NumberThread);
service.shutdown();
}
ThreadPoolExecutor:线程池的低层就是这个类
线程池的几个重要参数介绍?
说说线程池的底层工作原理?
线程池的工作原理,几个重要参数,然后给了具体几个参数分析线程池会怎么做,最后问阻塞队列的作用是什么
答:线程池的七大参数
1、corePoolSize:线程池中的常驻核心 线程数 酷
在创建了线程池后,当有请求任务来之后,就会安排池中的线程去执行请求任务,近似理解为今日当值线程
当线程池中的线程数目达到corePoolSize后,就会把到达任务放到缓存队列当中
2、maximumPoolSize:线程池能够容纳同进执行的最大线程数,此值必须大于等于1
3、keepAliveTime:多余的空闲线程的存活时间。 Kp 饿赖夫 太么
当前线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime值时,
多余空闲线程会被销毁直到只剩下 corePoolSize个线程为止。
4、unit:keepAliveTime的单位。
5、workQueue:任务队列,被提交但尚未被执行的任务。
6、threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程一般用默认的即可。
7、handler:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数(maximumPoolSize)时如何来
线程池用过吗?生产上你如何设置合于是理的参数
|----线程池的拒绝策略你谈炎
|---------等待队列也已经排满了,再也塞不下新任务了同时线程池中的max线程也达到了,无法继续为新任务服务。这时候我们就需要拒绝策略机制合理的处理这个问题。
|---------线程池的4种拒绝策略理论(JDK内置的拒绝策略)
|---------AbortPolicy(默认):直接抛出RejectedExecutionException异常阻止系统正常运行。饿不特
|---------CallerRunsPolicy:“调用者运行”一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。酷了 万思 趴了s
|---------DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。
|---------DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案。
以上内置拒绝策略均实现了RejectedExecutionHandler接口
|----你在工作中单量的、固定数的、可变的三种创建线程池的方法,你用那个多?超级大坑
答:三种线程池一个都不用,我们生产上只能用自定义的Executors中JDK已经给你提供了,为什么不用?
线程池不允使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors返回的线程池对象的弊端如下:
1、FixedThreadPool和SingleThreadPool:
允许的请求队列长度为Integer.MAX_value,可能会堆积大量的请求,从而导致OOM。
2、CachedThreadPool和ScheduledThreadPool:
允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
|----你在工作中是如何使用线程池的,是否自定义过线程池使用
public static void main(String[] args){
ExecutorService threadPool = new ThreadPoolExecutor(2,5,1L,....)
}
|----合理配置线程池你是如何考虑的?
CPU密集型
公式:CPU核数+1个线程的线程池
IO密集型
1、由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如CPU*2
2、IO密集型,即该任务需要大量的IO,即大量的阻塞。
公式:CPU/1-阻塞系数
如:8核:8/1-0.9=80个线程数
class MyThread implements Runnable{
public void run(){
}
}
class MyThread2 implements Callable<Integer>{ //Callable 卡了薄
public Integer Call() throws Exception{
//return null;
System.out.println("*****************come in Callable");
return 1024;
}
}
public class CallableDemo{
public static void main(String[] args) throws InteruptedException,ExceptionException{
FutureTask<Integer> futureTask = new FutureTask<>(new MyThread2());
Thread t1 = new Thread(futureTask,name:"AA");
t1.start();
//System.out.println("**********result:"+futureTask.get());
int result01 = 100;
//要求获得Callable线程的计算结果,如果没有计算完成就要去强求,会导致堵塞,值得计算完成
int result02 = futureTask.get();//建议放在量后
System.out.println("**********result:"+(result01+result02));
}
}
线程池
Callable
面试线程池:
线程池的工作原理,几个重要参数,然后给了具体几个参数分析线程池会息么做,最后问阻塞队列的作用是什么?
====================
====================死锁
====================
死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相靠等待的现象,若无外力干涉那它们都将无法推进下去,
如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。
class HoldLockThread implements Runnable{
private String lockA;
private String lockB;
public HoldLockThread(String lockA,String lockB){
this.lockA=lockA;
this.lockB=lockB;
}
public void run(){
synchronized(lockA){
System.out.println(Thread.currentThread().getName()+"\t 自己持有:"+lockA+"\t尝试 获得:"+lockB)
try{}catch(){}
synchronized(lockB){
System.out.println(Thread.currentThread().getName()+"\t 自己持有:"+lockB+"\t尝试 获得:"+lockA)
}
}
}
}
public static void main(){
String lockA;
String lockB;
new Thread(new HoldLockThread(lockA,lockB),name:"ThreadAAA").start();
new Thread(new HoldLockThread(lockB,lockA),name:"ThreadBBB").start();
}
jps定位进进号
jstack找到死锁查看
====================
====================JVM
====================
JVM是运行在操作系统之上的,它与硬件没有直接的交互。
JVM体系结构图:
类加载器:根启动加载器、拓展加载器、应用加载器
类装载器ClassLoader:
负责加载class文件,class文件在文件开头有特定的文件标示,并且ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定。
类加载器是什么?举例说明?
public static void main(){
Object obj=new Object();
Demo01 test =new Demo01();
System.out.pringln(obj.getClass().getClassLoader());//根加载器。 null
System.out.pringln("=========================");
System.out.pringln(test.getClass().getClassLoader().getParent().getParent());//爷爷 null
System.out.pringln(test.getClass().getClassLoader().getParent());//爸爸 sun.misc.Launcher$ExtClassLoader
System.out.pringln(test.getClass().getClassLoader());//孩子 sun.misc.Launcher$AppClassLoader
}
类加载器是什么?双清委派机制、沙箱安权机制
方法区:
方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法如构造函数,接口代码也在此定义。简单说,所有定义的方法的信息都保存在该区域。此区属于共享区间。
静态变量+常量+类信息(构造方法、接口定义)+运行时常量池存在方法区中。
class Book{
private Integer id;//方法区 类信息
private String bookName;//方法区 类信息
} -->Book.class //方法区 类信息
public class Demo{
private static final String Num=7;//常量 方法区
}
实例变量存在堆内存中,和方法区无关
栈:
栈也叫栈内存,主管java程序的运行,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束该栈就Over,生命
周期和线程一致,是线程私有的。8种基本类型变量+对象的引用变量+实例方法都是在函数的栈内存中分配。
栈存储什么?
栈帧中主要保存3类数据:
本地变量(Local Variables):输入参数和输出参数以及方法内的变量;
栈操作(Operand Stack):记录出栈、入栈的操作;
栈帧数据(Frame Data):包括类文件、方法等等。
java.lang.StackOverflowError
永久区:
永久存储区是一个常驻内存区域,用于存放JDK自身所携带的Class,Interface的元数据,也就是说它存的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收掉的,关闭JVM
才会释放此区域所占用的内存。
java.lang.OutOfMemoryError:PermGen space,说明是java虚拟机对永久代Perm内存不够。一般出现这种情况 ,都是程序启动需要加载大量的第三方jar包。
-XX:PermSize -XX:MaxPermSize
枚举根节点做可达性分析 GC Roots 的对象:虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。
方法区中的类静态属性引用的对象。
方法区中常量引用对象。
本地方法栈中JNI(Native方法)引用的对象。
常见的垃圾回收算法:1、引用计数
2、复制
复制->清空->互换(优点:没有产生碎片)
(1):eden、SurvivorFrom复制到SurvivorTo,年龄+1
首先,当Eden区满的时候会触发第一次GC,把还活着的对象拷贝到SurvivorFrom区,当Eden区再次触发GC的时候会扫描Eden区和From区域,对这两个区载进行垃圾回收,
经过这次回收后还存活的对象,则直接复制到To区域(如果有对象的年龄已经达到了老年的标准,则赋值到老年代区),同时把这些对象的年龄+1
(2):清空eden、SurvivorFrom
然后,清空Eden和SurvivorFrom中的对象,也即复制之后有交换,谁空谁是to
(3):SurvivorTo和SurvivorFrom互换
最后,SurvivorTo和SurvivorFrom互换,原 SurvivorTo成为下一次GC时的SurvivorFrom区。部分对象会在From和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold
决定,这个参数默认是15),最终如果还是存活,就存入到老年代。
3、标记清除
算法分成标记和清除两个阶段,先标记出要回收的对象,然后统一回收这些对象。
4、标记整理
1)标记
2)压缩
再次扫描,并往一端滑动存活对象
对,没有内存碎片,可以利用bump X,需要移动对象的成本
GC是什么(分代收集算法)
次数上频繁收集Young区
次数上较少收集Old区
基本不动Perm区
GC4种算法:
引用计数法
复制算法(Copying)
年轻代中使用的是Minor GC,这种GC算法采用的是复制算法
what
优点:
没有标记和清除的过程,效率高
没有内存碎片,可以利用bump-the-pointer实现快速内存分配。但是需要双倍空间。
缺点:
它浪费了一半的内存,
如果对象的存活率很高,我们可以极端一点,假设是100%存活,那么我们需要将所有对象都复制一遍,并将所有引用地址重置一遍,复制这一工作花费的时间,在对象存
活率达到一定程度时,将会变的不可忽视。所以从以上描述不难看出,复制算法想使用,最起码对象的存活率要非常低才行,而且最重要的是,我们必须要克服50%内存的
浪费。
标记清除(Mark-Sweep)
标记压缩(Mark-Compact)
标记清除压缩(Mark-Sweep-Compact)
====================
====================G1
====================
G1算法将堆划分为若干个区域(Region),它仍然属于分代收集器
这些Region的一部分包含新生代,新生代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者Survivor空间。
这些Region的一部分包含老年代,G1收集器通过将对象从一个区或复制到别外一个区域,完成了清理工作。这就意味着,在正常的处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就
不会有CMS内存碎片问题的存在了。
在G1中,还有一种特殊的区域,叫Humongous(区大的)区域,如果一个对象占用空间超过了分区容量50%以上,G1收集器就认为这是一个区型对象。这些区型对象默认直接会被分配在年老代,但是如果
它是一个短期存在的区型对象,就会对垃圾收集器造成负面影响。为了解决这个问题,G1划分了一个Humongous区,它用来专门存放区型对象。如果一个H区装不下一个巨型对象,那么G1会录找连续的H分区来
存储。为了能找到连续的H区,有时候不得不启动Full GC。
G1收集器下的Young GC
针对Eden区进行收集,Eden区耗尽后会被触发,主要是小区域收集+形成连续的内存块,避免内存碎片
*Eden区的数据移动到Survivor区,假如出现Survivor区空间不够,Eden区数据会部晋升到Old区
*Survivor区的数据移动到新的Survivor区,部会数据晋升到Old区
*最后Eden区收拾干净了,GC结束,用户的应用程序继续执行
初始标记:只标记GC Roots能直接关联到的对象
并发标记:进行GC Roots Tracing的过程
最终标记:修正并发标记期间,因程序运行导致标记发生变化的那一部分对象
筛选回收:根据时间来进行价值最大化的回收
-XX:+UseG1GC
绿 E Eden
黄 S Survivor 幸存区
蓝 O Old
红 H Humongous
JVMGC + SpringBoot微服务的生产部署和调参优化
JVMGC -> 调优 -> SpringBoot微服务的生产部署和调参优化
1、使用mvn celan package 打包
2、在有包的路径下,运行jar命令,公式如下:
java -server jvm的各种参数 -jar 第一步上面的jar/war包名字
Undertow
==============
==============JUC java.util.concurrent 并发
==============
***Lock 8锁
1、标准访问,请问先打印短信还是email === sendSMS、sendEmail
2、sendSMS()睡觉4秒钟,请问先打印短信还是email === sendSMS、sendEmail
3、新增普通方法openPhone,请问先打印短信还是openPhone === openPhone、sendSMS
4、有两部手机,请问先打印短信还是email === sendEmail、sendSMS
5、两个静态同步方法,同一部手机,请问先打印短信还是email === sendSMS、sendEmail
6、两个静态同步方法,2部手机,请问先打印短信还是email === sendSMS、sendEmail
7、1个静态同步方法,1个普通同步方法,同一部手机,请问先打印短信还是email === sendEmail、sendSMS
8、1个静态同步方法,1个普通同步方法,2部手机,请问先打印短信还是email === sendEmail、sendSMS
class Phone{
public static synchronized void sendSMS() throws Exception {
TimeUnit.SECONDS.sleep(4);
System.out.println("-------------sendSMS");
}
public static synchronized void sendEmail() throws Exception{
System.out.println("-------------sendEmail");
}
public void openPhone(){
System.out.println("-------------openPhone");
}
}
public static void main() throws Exception {
Phone phone = new Phone();
Phone phone2 = new Phone();
new Thread(()->{
try{
phone.sendSMS();
}catch(){
e.printStackTrace();
}
},"A").start();
new Thread(()->{
try{
phone.sendEmail();
//phone.openPhone();
//phone2.sendEmail();
}catch(){
e.printStackTrace();
}
},"B").start();
}
***seqLoad
class Father{
public Father(){
System.out.println("11111111");
}
{
System.out.println("22222222");
}
static{
System.out.println("333333333");
}
}
class Son extends Father{
public Son(){
super();
System.out.println("44444444444");
}
{
System.out.println("5555555555");
}
static{
System.out.println("66666666");
}
}
public static void main(String[] args){
new Son();
System.out.println("=========");
new Son();
System.out.println("=========");
new Father();
}
3333333
6666666
2222222
1111111
5555555
4444444
========
2222222
1111111
5555555
4444444
========
2222222
1111111
***ThreadAccess
class ShareResource
{
private int number = 1;//1 - A 2 - B 3 - C.....
private Lock lock = new ReentrantLock();
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
public void print5(int totalLoop)
{
lock.lock();
try
{
//1 判断
while(number != 1)
{
c1.await();
}
//2 干活
for (int i = 1; i <=5; i++)
{
System.out.println(Thread.currentThread().getName()+"\t"+i+"\t totalLoop:"+totalLoop);
}
//3 通知
number = 2;
c2.signal();//
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print10(int totalLoop)
{
lock.lock();
try
{
//1 判断
while(number != 2)
{
c2.await();
}
//2 干活
for (int i = 1; i <=10; i++)
{
System.out.println(Thread.currentThread().getName()+"\t"+i+"\t totalLoop:"+totalLoop);
}
//3 通知
number = 3;
c3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print15(int totalLoop)
{
lock.lock();
try
{
//1 判断
while(number != 3)
{
c3.await();
}
//2 干活
for (int i = 1; i <=15; i++)
{
System.out.println(Thread.currentThread().getName()+"\t"+i+"\t totalLoop:"+totalLoop);
}
//3 通知
number = 1;
c1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
/**
* @Description:
* 多线程之间按顺序调用,实现A->B->C
* 三个线程启动,要求如下:
*
* AA打印5次,BB打印10次,CC打印15次
* 接着
* AA打印5次,BB打印10次,CC打印15次
* ......来10轮
*/
public class ThreadOrderAccess
{
public static void main(String[] args)
{
ShareResource sr = new ShareResource();
new Thread(() -> {
for (int i = 1; i <=10; i++)
{
sr.print5(i);
}
}, "AA").start();
new Thread(() -> {
for (int i = 1; i <=10; i++)
{
sr.print10(i);
}
}, "BB").start();
new Thread(() -> {
for (int i = 1; i <=10; i++)
{
sr.print15(i);
}
}, "CC").start();
}
}
***ReadWriteLockDemo 读写锁
class MyQueue
{
private Object obj;
private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
public void writeObj(Object obj)
{
rwLock.writeLock().lock();
try
{
this.obj = obj;
System.out.println(Thread.currentThread().getName()+"\t"+obj);
} catch (Exception e) {
e.printStackTrace();
} finally {
rwLock.writeLock().unlock();
}
}
public void readObj()
{
rwLock.readLock().lock();
try
{
System.out.println(Thread.currentThread().getName()+"\t"+obj);
} catch (Exception e) {
e.printStackTrace();
} finally {
rwLock.readLock().unlock();
}
}
}
/**
*
* @Description: 一个线程写入,100个线程读取
* @author zzyy
*/
public class ReadWriteLockDemo
{
public static void main(String[] args) throws InterruptedException
{
MyQueue q = new MyQueue();
new Thread(() -> {
q.writeObj("ClassName1018");
}, "writeThread").start();
Thread.sleep(100);
for (int i = 1; i <=100; i++)
{
new Thread(() -> {
q.readObj();
},String.valueOf(i)).start();
}
}
}
***CallableDemo
class MyThread implements Callable<Integer>
{
@Override
public Integer call() throws Exception
{
System.out.println("**********call() ****");
//Thread.sleep(4000);
return 1018;
}
}
/**
* @Description: Callable接口获得多线程
*/
public class CallableDemo{
public static void main(String[] args) throws InterruptedException, ExecutionException
{
FutureTask<Integer> ft = new FutureTask<Integer>(new MyThread());
new Thread(ft, "AA").start();
new Thread(ft, "BB").start();
System.out.println(Thread.currentThread().getName()+"***********我是上课主线程");
Integer result01 = ft.get();
System.out.println("******result01: "+result01);
}
}
***CountDownLatchDemo 闭锁
java 5.0在java.util.concurrent包中提供了多种并发容器类来改进同步容器的性能。
CountDownLatch一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
/*
* CountDownLatch :闭锁,在完成某些运算是,只有其他所有线程的运算全部完成,当前运算才继续执行
*/
public class TestCountDownLatch {
public static void main(String[] args) {
final CountDownLatch latch = new CountDownLatch(50);
LatchDemo ld = new LatchDemo(latch);
long start = System.currentTimeMillis();
for (int i = 0; i < 50; i++) {
new Thread(ld).start();
}
try {
latch.await();
} catch (InterruptedException e) {
}
long end = System.currentTimeMillis();
System.out.println("耗费时间为:" + (end - start));
}
}
class LatchDemo implements Runnable {
private CountDownLatch latch;
public LatchDemo(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
try {
for (int i = 0; i < 50000; i++) {
if (i % 2 == 0) {
System.out.println(i);
}
}
} finally {
latch.countDown();
}
}
}
/**
* Talk is cheap,show me your code
*
*让一些线程阻塞直到另一些线程完成一系列操作后才被唤醒。
*
* CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,这些线程会阻塞。
* 其它线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞),
* 当计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行。
*
* 解释:5个同学陆续离开教室后值班同学才可以关门。
* 也即 秦灭6国,一统华夏
* main主线程必须要等前面5个线程完成全部工作后,自己才能开干
*/
public class CountDownLatchDemo
{
public static void main(String[] args) throws InterruptedException
{
CountDownLatch cdl = new CountDownLatch(6);
for (int i = 1; i <=6; i++)
{
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"\t 国被灭");
cdl.countDown();
},CountryEnums.forEachCountryEnums(i).getRetMsg()).start();
}
cdl.await();
System.out.println(Thread.currentThread().getName()+"*********秦灭6国,一统华夏");
System.out.println();
System.out.println();
System.out.println(CountryEnums.ONE);
System.out.println(CountryEnums.ONE.getRetCode());
System.out.println(CountryEnums.ONE.getRetMsg());
}
public static void testCloseDoor() throws InterruptedException
{
CountDownLatch cdl = new CountDownLatch(6);
for (int i = 1; i <=6; i++)
{
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"\t 下自习离开教室");
cdl.countDown();
}, String.valueOf(i)).start();
}
cdl.await();
System.out.println(Thread.currentThread().getName()+"*********班长走人");
}
}
***CyclicBarrierDemo
/**
* CyclicBarrier
* 的字面意思是可循环(Cyclic)使用的屏障(Barrier)。它要做的事情是,
* 让一组线程到达一个屏障(也可以叫同步点)时被阻塞,
* 直到最后一个线程到达屏障时,屏障才会开门,所有
* 被屏障拦截的线程才会继续干活。
* 线程进入屏障通过 CyclicBarrier 的await()方法。
*
* 集齐7颗龙珠就可以召唤神龙
*/
public class CyclicBarrierDemo
{
private static final int NUMBER = 7;
public static void main(String[] args)
{
//CyclicBarrier(int parties, Runnable barrierAction)
CyclicBarrier cb = new CyclicBarrier(NUMBER, () -> { System.out.println("******召唤神龙");} ) ;
for (int i = 1; i <=NUMBER; i++)
{
int tempInt = i;
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName()+"\t 收集到第:"+tempInt+"\t 龙珠");
cb.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
}
}
***SemaphoreDemo
/**
* 在信号量上我们定义两种操作:
* acquire(获取) 当一个线程调用acquire操作时,它要么通过成功获取信号量(信号量减1),
* 要么一直等下去,直到有线程释放信号量,或超时。
* release(释放)实际上会将信号量的值加1,然后唤醒等待的线程。
* 信号量主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制。
*/
public class SemaphoreDemo{
public static void main(String[] args){
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(new Random().nextInt(8));
System.out.println(Thread.currentThread().getName()+"\t 离开了车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}
***ConcurrentHashMap 采用“锁分断”机制
/*
* CopyOnWriteArrayList/CopyOnWriteArraySet : “写入并复制”
* 注意:添加操作多时,效率低,因为每次添加时都会进行复制,开销非常的大。并发迭代操作多时可以选择。
*/
public class TestCopyOnWriteArrayList {
public static void main(String[] args) {
HelloThread ht = new HelloThread();
for (int i = 0; i < 10; i++) {
new Thread(ht).start();
}
}
}
class HelloThread implements Runnable{
// private static List<String> list = Collections.synchronizedList(new ArrayList<String>());
private static CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
static{
list.add("AA");
list.add("BB");
list.add("CC");
}
@Override
public void run() {
Iterator<String> it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
list.add("AA");
}
}
}
面试题:
1.JVM垃圾回收的时候如何确定垃圾?是否知道什么是GC Roots
答:什么是垃圾:简单的说就是内存中已经不再被使用到的空间就是垃圾
要进行垃圾回收,如何判断一个对象是否可以被回收?
1引用计数法
2枚举根节点做可达性分析(根搜索路径)
基本思路就是通过一系列名为“GC Roots”的对象作为起点,从这个被称为GC Roots的对象开始向下搜索,如果一个对象到GC Roots没有任何引用链相连时,则说明此对象不可用。也即单个元素的队列。
给定一个集合的引用作为根出发,通过引用关系遍历对象图,能被遍历到的(可到达的)对象就被判定为存活;没有被遍历到的就自然被判定为死亡。
java中可以作为GC Roots的对象:
虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。
方法区中的类静态属性引用的对象。
方法区中常量引用对象。
本地方法栈中JNI(Native方法)引用的对象。
2.你说你做过JVM调优和参数配置,请问如何盘点查看JVM系统默认值。
答:Xms Xms和Xmx最好要调成一致 避免GC平凡的收集忽高忽低 -Xms 等价于 -XX:InitialHeapSize 初始化的堆内存
-Xmx 等价于 -XX:MaxHeapSize 初始化的堆内存
Xmx:堆空间
Xss:初始栈空间
JVM的参数类型:标配参数
-version 微森、-help 耗破、java-showversion
X参数(了解)
-Xint 解释执行
-Xcomp 第一次使用就编译成本地代码
-Xmixed 混合模式
*XX参数
Boolean类型
公试: -XX:+或者 - 某个属性值
+表示开启
-表示关闭
Case:是否打印GC收集细节
-XX:-PrintGCDetails
-XX:+PrintGCDetails
是否使用串行垃圾回收器
-XX:-UseSerialGC
-XX:+UseSerialGC
KV设值类型
公试:-XX:属性key=属性值value
Case: -XX:MetaspaceSize=128m 21M 元空间
-XX:MaxTenuringThreshold=15
jps:查看进程编号 -l
jinfo举例,如何查看当前运行程序的配置
jinfo -flag MetaspaceSize 4300
题外话(坑题):两个经典参数:-Xms和-Xmx
这个你如何 解释:-Xms 等价于 -xx:InitialHeapSize 初始化的堆内存
-Xmx 等价于 -XX:MaxHeapSize 初始化的堆内存
第一种,查看参数盘点家底
jps -l
jinfo -flag 具体参数 java进程编号
jinfo -flags java进程编号
jinfo -flags PrintGCDetails 13344
第二种,查看参数盘点家底 佛拉歌s 因尼踢奥
查看JVM默认值:-XX:+PrintFlagsInitial
主持要查看初始默认
公式 java -XX:+PrintFlagsInitial -version
java -XX:+PrintFlagsInitial
主要查看修改更新
-XX:+PrintFlagsFinal no
java -XX:+PrintFlagsFinal -version
java -XX:+PrintFlagsFinal
打印命令行参数
克梦按的 赖恩
-XX:+PrintCommandLineFlags
java -XX:+PrintCommandLineFlags -version
方法区
可通过-XX:PermSize和-XX:MaxPermSize来指定最小值和最大值。
常用参数配置:
-Xms128m -Xmx4096m -Xss1024k -XX:MetaspaceSize=512m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseSerialGC
3.你平时工作用过的JVM常用基本配置参数有哪些?
-XX:+HeapDumpOnOutOfMemoryError OOM时导出堆到文件
-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
答:常用参数 -Xms:初始大小内存,默认为物理内存1/64
等价于 -XX:InitialHeapSize
-Xmx:最大分配内存,默认为物理内存1/4
等价于 -XX:MaxHeapSize
-Xss:设置单个线程栈的大小,一般默认为512K - 1024K
等价于:-XX:ThreadStackSize ThreadStackSize=0如果是0用的就是系统出厂默认值。
-Xmn:设置年轻代
-XX:MetaspaceSize :设置元空间大小 默认20多M 美特思贝思
元空间的本质和永久代类似,都是对JVM规范中方法区的实现。
不过元空间与永久代之间最大的区别在于:
元空间并不在虚拟机中,而是使用本地内存
因此,默认情况下,元空间的大小仅受本地内存限制
-Mms10m -Xmx10m -XX:MetaspaceSize=1024m -XX:+PrintFlagsFinal
-Mms128m -Xmx4096m -Xss1024m -XX:MetaspaceSize=512m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseSerialGC
*-XX:+PrintGCDetails 低特奥思
输出详细GC收集日志信息
规律:
GC
【GC类型 GC前新生代内存点用->GC后内存占用 新生代总大小 GC前JVM堆内存占用->GC后JVM堆内存使用 JVM堆总大小 GC耗时 用户耗时 系统耗时 实际耗时】
FullGC
-XX:SurvivorRatio 威肖
设置新生代中eden和S0/S1空间的比列
默认
-XX:SurvivorRatio=8,Eden:S0:S1=8:1:1
假如
-XX:SurvivorRatio=4,Eden:S0:S1=4:1:1
SurvivorRatio值就是设置eden区的比例占多少,S0/S1相同
-XX:NewRatio
配置年轻代与老年代在堆结构的占比
默认
-XX:NewRatio=2新生代占1,老年代2,年轻代占整个堆的1/3
假如
-XX:NewRatio=4新生代占1,老年代4,年轻代占整个堆的1/5
NewRatio值就是设置老年代的占比,剩下的1给新生代
-XX:MaxTenuringThreshold
设置垃圾最大年龄
查看默认进入老年代年龄:
jinfo -flag MaxTenuringThreshold 17344(进程编号)
-XX:MaxTenuringThreshold=15
-XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。
如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。
4.强引用、软引用、弱引用、虚引用分别是什么?
java.lang.ref 包下面
Object
Reference 软引用
SoftReference
WeakReference
PhantomReference
ReferenceQueue
强引用:95%用的都是强引用。
当内存不足,JVM开始垃圾回收,对于强引用的对象,就算是出现了OOM也不会对该对象进行回收,死心都不收。
强引用是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还“活着”,垃圾收集器不会碰这种对象。在java
中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达
状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到JVM也不会回收。因此强引用是造成Java内存泄漏的主要原因之一。
对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为NULL,一般认为就是可以被垃圾收集的了.
软引用:是一种相对强引用弱化了一些的引用,需要用java.lang.ref.SoftReference类来实现,可以让对象豁免一些垃圾收集。
对于只有软引用的对象来说,当系统内存充足时它不会被回收,当系统内存不足时它会被回收。软引用通常用在对内存敏感的程序中,比如高速缓存就有用到软引用,内存够用的时候就保留,
不够用就回收。
弱引用:弱引用需要用java.lang.ref.WeakReference类来实现,它比软引用的生存期更短,对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,
都会回收该对象占用的内存
虚引用:虚引用需要用java.lang.ref.WeakReference类来实现,
就 是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,
它不能单独使用也不能通过它访问对象,虚引用必须和引用队列联合使用。
5.请谈谈你对OOM的认识? 思大克 哦威夫捞 挨饿
答:生产故涨 java.lang.StackOverflowError(栈溢出)错误
递归调用方法特别多把占空间给撑了
函数调用栈太深了,注意代码中是否有了循环调用方法而无法退出的情况
java.lang.OutOfMemoryError:java heap space(堆溢出)heap 细破 space 私被思
堆内存不够用了
使用Java程序从数据库中查询大量的数据时出现异常:
在JVM中如果98%的时间是用于GC且可用的 Heap size 不足2%的时候将抛出此异常信息。
java.lang.OutOfMemoryError:GC overhead limit exceeded
GC回收时间过长会抛出OutOfMemroyError。过长的定义是,超过98%的时间用来作GC并且回收了不到2%的堆内存。连续多次GC都只回收了不到2%的极端情况下才会抛出。
假如不抛出GC overhead limit 错误会发生什么情况呢?
那就是GC清理的这么点内存很快会再次埴满,迫使GC再次执行,这样就形成恶性循环,CPU使用率一直是100%,而GC却没有任何成果。
*java.lang.OutOfMemoryError:Direct buffer memory 脉摸蕊
写NIO程序经常使用ByteBuffer来读取或者写入数据,这是一种基于通道与缓冲区的I/O方式,
它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。
这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中回复制数据。
安内爆 亏A特
*java.lang.OutOfMemoryError:unable to create new native thread
高并发请求服务器时,以常出现如下异常:java.Lang.OutOfMemoryError:unable to create new native thread
准确的讲该native thread异常与对应的平台有关
导致原因:
1、你的应用创建了太多线程了,一个应该进程创建多个线程,超过系统承载极限。
2、你的服务器并不允许你的应用程序创建这么多线程,linux系统默认允许单个进程可 以创建的线程数是1024个,
你的应用创建超过这个数量,就会报java.lang.OutOfMemoryError:unable to create new native thread
解决办法:
1、想办法降低你应用程序创建线程数量,分析应用是否真的需要创建这么多线程,如果不是,改代码将线程数降到最低。
2、对于有的应用,确实需要创建很多线程,远超过linux系统的默认1024个线程的限制,可以通过修改linux服务器配置,扩大linux默认限制
java.lang.OutOfMemoryError:Metaspace(加载静态类,给撑暴了)
使用java -XX:+PrintFlagslintial命令查看本机的初始化参数,-xx:Metaspacesize为21810376B(大约20.8M)
Metaspace是方法区在HotSpot中的实现,它与持久代最大的区别在于: Metaspace并不在虚拟机内存中而使用本地内存
java.lang.OutOfMemoryError:PermGen space
说明是java虚拟机对永久代Perm内存不够。一般出现这种情况 ,都是程序启动需要加载大量的第三方jar包。
6.GC垃圾回收算法和垃圾收集器的关系?分别是什么请你谈谈
答:GC算法(引用计数/复制/标清/标整)是内存回收的方法论,垃圾收集器就是算法落地实现。
因为目前为止还没有完美的收集器出现,更加没有万能的收集器,只是针对具体应用最合适的收集器,进行分代收集。
4种主要垃圾收集器:Serial串行 Parallel并行 CMS并发 G1
串行垃圾回收器(Serial):它为单线程环境设计且只使用一个线程进行垃圾回收,会暂停所有的用户线程。所以不适合服务器环境。
并行(ParNew):多个垃圾收集线程并行工作,此时用户线程是暂停的,适用于科学计算/大数据处理首台处理等弱交互场景
并行垃圾回收器(Parallel):
并发垃圾回收器(CMS):用户线程和垃圾收集线程同时执行(不一定是并行,可能交替执行),不需要停顿用户线程,互联网公司多用它,
适用对响应时间有要求的场景。
上述3个小总结,G1特殊后面说
G1垃圾回收器:G1垃圾回收器将堆内存分割成不同的区域然后并发的对其进行垃圾回收
7.怎么查看服务器默认的垃圾收集器是那个?生产上如何配置垃圾收集器的?谈谈你对垃圾收集器的理解?
答:怎么查看默认的垃圾收集器是那个:java -xx:+PrintCommandLineFlags -version -XX:+UseParallelGC
java的gc回收的类型主要有几种?
又思 C尔又GC 又思 拍尔捞GC 又思 炕K 妈K 思蕊破 又思 爬尔 又思 哦的
UseSerialGC(串行)、UseParallelGC(并行)、UseConcMarkSweepGC(并发标记清除)、UseParNewGC()、UseParallelOldGC、UseG1GC
垃圾收集器:
Young Gen -> Serial Copying \ Parallel Scavenge \ ParNew
----------------------------------------------------------------------------------G1
Old Gen -> Serial MSC(Serial Old)\ Parallel Compacting(Parallel Old)\ CMS
部分参数预先说明:
DefNew ---> Default New Generation
Tenured ---> Old
ParNew ---> Parallel New Generation
PSYoungGen ---> Parallel Scavenge
ParOldGen ---> Parallel Old Generation
Server/Client模式分别是什么意思?
新生代
串行GC(Serial)/(Serial Copying)
一个单线程的收集器,在进行垃圾收集时候,必须暂停其他所有的工作线程直到它收集结束。
对应JVM参数是:-XX:+UseSerialGC
开启后会使用:Serial(Young区用)+Serial Old(Old区用)的收集器组合。
表示:新生代、老年代都会使用串行回收收集器,新生代使用复制算法、老年代使用标记-整理算法
并行GC(ParNew)
使用多线程进行垃圾回收,在垃圾收集时,会Stop-the-World暂停其他所有的工作线程直到它收集结束。
ParNew收集器其实就是Serial收集器新生代的并行多线程版本,最常见的应用场景是配合老年代的CMS GC工作,其余的行为和Serial收集器完全一样,ParNew垃圾收集器在垃圾收集过程
中同样也要暂停所有其他的工作线程。它是很多Java虚拟机运行在Server模式下新生代的默认垃圾收集器。
常用对应JVM参数: -xx:+UseParNewGC 启用ParNew收集器,只影响新生代的收集,不影响老年代
开启上述参数后,会使用:ParNew(Young区用)+Serial Old的收集器组合,新生代使用复制算法,老年代采用标记-整理算法
并行回收GC(Paralle)/(Parallel Scavenge)
Parallel Scavenge收集器类似ParNew也是一个新生代垃圾收集器,使用复制算法,也是一个并行的多线程的垃圾收集器,俗称吞吐量优先收集器。串行收集器在新生代和老年代的并行化
老年代
串行GC(Serial Old)/(Serial MSC)
并行GC(Parallel)/(Parallel MSC)
Parallel Old收集器是Parallel Scavenge的老年代版本,使用多线程的标记-整理算法,Paralle Old收集器在JDK1.6才开始提供。
Parallel Old正是为了在老年代同样提供吞吐量优先的垃圾收集器,如果系统对吞吐量要求比较高,JDK1.8后可以优先考虑新生代Parallel Scavenge和老年代Parallel Old收集器的搭配
策略。在JDK1.8及后(Parallel Scavenge + Parallel Old)
JVM常用参数:
-XX:+UseParallelOldGC 使用Parallel Old收集器,设置该参数后,新生代Parallel+老年代Parallel Old
并发标记清除GC(CMS)
CMS收集器(Concurrent Mark Sweep:并发标记清除)是一种以获取最短回收停顿时间为目标的收集器。
适合应用在互联网站或者B/S系统的服务器上,这类应用尤其重视服务器的响应速度,希望系统停顿时间最短。
CMS非常适合堆内存大、CPU核数多的服务器端应用,也是G1出现之前大型应用的首选收集器。
Concurrent Mark Sweep并发标记清除,并发收集低停顿,并发指的是与用户线程一起执行。
开启该收集器的JVM参数:-XX:+UseConcMarkSweepGC开启该参数后会自动将 -XX:+UseParNewGC打开
开启该参数后,使用ParNew(Young区用)+CMS(Old区用)+Serial Old的收集器组合,Serial Old将作为CMS出错的后备收集器
如何选择垃圾收集器:
组合的选择
单CPU或小内存,单机程序
-XX:+UseSerialGC
多CPU,需要最大吞吐量,如后台计算型应用
-XX:+UseParallelGC 或者 -XX:+useParallelOldGC
多CPU,追求低停顿时间,需快速响应如互联网应用。
-XX:+UseConcMarkSweepGC 或 -XX:+ParNewGC
8.G1垃圾收集器
以前收集器特点
年轻代和老年代是各自独立且连续的内存块;
年轻代收集使用单eden+SO+s进行复制法;
老年代收集必须扫描整个老年代区域;
都是以尽可能少而快速地执行GC为设计原则。
G1是什么
G1是一种服务器端的垃圾收集器,应用在多处理器和大容量内存环境中,在实现高吞吐量的同进,尽可能的满足垃圾收集暂停时间的要求。另外,它还具有以下特性:
像CMS收集器一样,能与应用程序线程并发执行。
整理空闲空间更快。
需要更多的时间来预测GC停顿时间。
不希望牺牲大量的右吐性能。
不需要更大的Java Heap.
G1收集器的设计目标是取代CMS收集器,它同CMS相比,在以下方面表现的更出色
G1是一个有整理内存过程的垃圾收集器,不会产生很多内存碎片。
G1的Stop The World(STW)更可控,G1在停顿时间上添加了预测机制,用户可以指定期望停顿时间。
底层原理
Region 区域化垃圾收集器
最大好处是化整为零,避免全内存扫描,只需要按照区域来进行扫描即可。
区域化内存划片Region,整体编为了一些列不连续的内存区域,避免了全内存区的GC操作。
核心思想是将整个堆内存区域分成大小想 同的子区域(Region),在JVM启动时会自动设置这些子区域的大小。
在堆的使用上,G1并不要求对象的存储一定是物理上连续的只要逻辑上连续即可,每个分区也不会固定地为某个代服务,可以按需在年轻代和老年代之间切换。启动时可 通过参数
-XX:G1HeapRegionSize=n可指定分区大小(1MB 32MB,且必须是2的幂),默认将整堆划分为2048个分区。
回收步骤
4步过程
case案例
常用配置参数(了解)
和CMS相比的优势
小总结
9.生产环境服务器变慢,诊断思路和性能评估谈谈?
整机:top uptime, 系统性能命令的精简版 load average:1 5 15 系统的平均负载值 3个值相加除3在乘100%如高于60%系统高
CPU:vmstat 思得t vmstat -n 2 3
每2秒采样一次 共记采样3次 procs(总进程) memory(内存) swap(交换空间) io system(系统) cpu
r(运行) b(阻塞)
procs
1、r:运行和等待CPU时间片的进程数,原则上1核的CPU的运行队列不要超过2,整个系统的运行队列不能超过总核数的2倍。否则代表系统压过大
2、b:等待资源的进程数,比如正在等待磁盘I/O、网络I/O等。
CPU
us:用户进程消耗CPU时间百分比,us值高,用户进程消耗CPU时间多,如果长期大于50%,优化程序;
sy:内核进程消耗的cpu时间百分比;
us: + sy 参考值为80%,如果us + sy 大于80%,说明可能存在CPU不足。
id:处于空闲的CPU百分比
wa:系统等待IO的CPU时间百分比
st:来自于一个虚拟机偷取的CPU时间的百分比
内存:free -m 服蕊
应用程序可用内存/系统物理内存>70%内存充足
应用程序可用内存/系统物理内存<20%内存不足,需要增加内存
20%<应用程序可用内存/系统物理内存<70%内存基本够用
pidstat -p 进程号 -r 采样间隔秒数
硬盘:df -h
磁盘IO:iostat -xdk 2 3
网络IO:ifstat
10.假如生产环境出现CPU占用过高,请谈谈你的分析思路和定位
1、先用top命令找出CPU占比最高的
2、ps -ef 或者 jps 进一步定位,得知是一个怎么样的一个后台程序
3、定位到具体线程或者代码 ps -mp 进程 -o THREAD,tid,time
-m 显示所有线程
-p pid进程使用cpu的时间
-o 该参数后是用户自定义格式
4、将需要的线程ID转换为16进制格式(英文小写格式)
5、jstack进程ID|grep tid(16进制线程ID小写英文)-A60
11.对于JDK自带的JVM监控和性能分析工具用过哪些?一搬你是怎么用的?
12、请谈谈你对JVM的理解?java8的虚拟机有什么更新?
13、什么是OOM?什么是StackOverflowError?有哪些方法分析?
14、JVM的常用参数调优你知道哪些?
15、内存快照抓取和MAT分析DUMP文件知道吗?
16、谈谈JVM中,对类加载器你的认识?
17、类加载器是什么?举例说明?
public static void main(){
Object obj=new Object();
Demo01 test =new Demo01();
System.out.pringln(obj.getClass().getClassLoader());//根加载器。 null
System.out.pringln("=========================");
System.out.pringln(test.getClass().getClassLoader().getParent().getParent());//爷爷 null
System.out.pringln(test.getClass().getClassLoader().getParent());//爸爸 sun.misc.Launcher$ExtClassLoader
System.out.pringln(test.getClass().getClassLoader());//孩子 sun.misc.Launcher$AppClassLoader
}
18、双亲委派机制+沙箱安全机制
19、用户自定义
pulic abstract class ClassLoader{
}
面试题:
public static void Main(){
int i=1;
i=i++;
int j = i++;
int k= i+ ++i * i++;
System.out.println("i="+i);//i=4
System.out.println("j="+j);//j=1
System.out.println("k="+k);//k=11
}
(1)把i的值压入操作数栈
(2)i变量自增1
(3)把i的值压入操作数栈
(4)把i的值压入操作数栈
(5)i变量自增1
(6)把操作数栈中前两个弹出求乘积结果再压入栈
(7)把操作数栈中的值弹出求和再赋值给k
单例设计模式 Singleton:
单例设计模式,即某个类在整个系统中只能有一个实例对象可被获取和使用的代码模式。
几种常见形式
1 饿汉式:直接创建对象,不存在线程安全问题
直接实例化饿汉式(简洁直观)
枚举式(最简洁)
静态代码块饿汉式(适全复杂实例化)
2 懒汉式:延迟创建对象
线程不安全(适用于单线程)
线程安全(适用于多线程)
静态内部类形式(适用于多线程)
饿汉式:
/*
*饿汉式:直接实例化饿汉式(简洁直观)
*1、构造器私有化
*2、自行创建,并且用静态变量保存
*3、向外提共这个实例
*4、强调一个单例,可以用final修改
*/
public class Singleton1(){
public static final Singleton1 INSTANCE = new Singleton1();
private Singleton1(){
}
}
public static void main(String[] args){
Singleton1 s= Singleton1.INSTANCE;
System.out.println(s);
}
/*
*枚举式,表示该类型的对象是有限的几个 1.5之后
*可以限定为一个成了单例
*/
public enum Singleton1(){
INSTANCE
}
public static void main(String[] args){
Singleton1 s= Singleton1.INSTANCE;
System.out.println(s);
}
/*
*静态代码块饿汉式
*
*/
public class Singleton1{
public static final Singleton1 INSTANCE;
static{
INSTANCE = new Singleton1();
}
private Singleton1(){
}
}
懒汉式:
/*
*线程不安全(适用于单线程)
*可以加 synchronized 同步代码块来解决线安全问题
*/
public class Singleton1{
private static Singleton1 instance;
private Singleton1(){
}
public static Singleton1 getInstance(){
if(instance == null){
try{
Thread.sleep(100);
}catch(InterruptedException e){
e.printStackTrace();
}
instance = new Singleton1();
}
return instance;
}
}
public static void main(){
Singleton1 s1= Singleton1.getInstance();
}
/*
*线程安全(适用于多线程)
*/
public class Singleton1{
private static Singleton1 instance;
private Singleton1(){
}
public static Singleton1 getInstance(){
if(instance == null){
synchronized(Singleton1.class){
if(instance == null){
try{
Thread.sleep(100);
}catch(InterruptedException e){
e.printStackTrace();
}
instance = new Singleton1();
}
}
}
return instance;
}
}
/*
*静态内部类形式(适用于多线程)
*内部类加载和初始代时,创建INSTANCE对象
*是单独加载和初始化getInstance,是线程安全的
*/
public class Singleton1{
private Singleton1(){
}
private static calss Inner{
private static final Singleton1 INSTANCE = new Singleton1();
}
public static Singleton1 getInstance(){
return Inner.INSTANCE;
}
}
面试题:类初始化和实例初始化等?
/*
*父类的初始化<clinit>:
*(1) j = method();
*(2) 父类的静态代码块 static
*
*父类的实例化方法
*(1) super()(最前)
*(2) i = test();
*(3) 父类的非静态代码块
*(4) 父类的无参构造(最后)
*/
public class Father{
private int i = test();
private static int j = method();
static{
System.out.print("(1)");
}
Father(){
System.out.print("(2)");
}
{
System.out.print("(3)");
}
public int test(){
System.out.print("(4)");
return 1;
}
public static int method(){
System.out.print("(5)");
return 1;
}
}
/*
*子类的初始化<clinit>:
*(1) j = method();
*(2) 子类的静态代码块 static
*
*
*先初始化父类:(5)(1)
*初始化子类:(10)(6)
*
*子类的实例化方法
*(1) super()(最前)
*(2) i = test();
*(3) 子类的非静态代码块
*(4) 子类的无参构造(最后)
*/
public class Son extends Father{
private int i = test();
private static int j = method();
static {
System.out.print("(6)");
}
Son(){
//super();写或不写都在,在子类构造器中一定会调用父类的构造器
System.out.print("(7)");
}
{
System.out.print("(8)");
}
public int test(){
System.out.print("(9)");
return 1;
}
public static int method(){
System.out.print("(10)");
return 1;
}
public static void main(){
Son s1 = new Son();
System.out.println();
Son s2 = new Son();
}
}
结果:
(5)(1)(10)(6)(9)(3)(2)(9)(8)(7)
(9)(3)(2)(9)(8)(7)
类初始化过程:
1、一个类要创建实例需要先加载并初始化该类。
main方法所在的类需要先加载和初始化
2、一个子类要初始化需要先初始化父类
3、一个类初始化就是执行<clinit>()方法区中常量引用对象。
<clinit>()方法由静态类变量显示赋值代码和静态代码块组成
类变量显示赋值代码和静态代码块代码从上到下顺序执行
<clinit>()方法只执行一次
实列初始化过程
1、实列初始化就是执行<init>()方法
<init>()方法可能重载有多个,有几个构造器就有几个<init>方法
<init>()方法由非静态实例变量显示赋值代码和非静态代码块、对应构造器代码组成
非静态实例变量显示赋值代码和非静态代码块代码从上到下顺序执行,而对应构造器的代码最后执行
每次创建实例对象,调用对应构造器,执行的就是对应的<init>方法
<init>方法的首行是super()或super(实参列表),即对应父类的<init>方法
面试:
编写一个程序,开启3个线程,这三个线程的ID分别为A、B、C,每个线程将自已的ID在屏幕上打印10遍,要求输出的结果必须按顺序显示。
如:ABCABCABC。。。。依次递归
class AlternateDemo{
private int number=1;
private Lock lock=new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
public void loopA(){
lock.lock();
try{
//1 判断
if(number != 1){
condition1.await();
}
//2 打印
for(int i=1;i<=5;i++){
System.outprintln(Thread.currentThread().getName()+"\t"+i+"\t"+totalLoop);
}
//3 唤醒
number=2;
condition2.signal();
}catch(Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
public void loopB(){
lock.lock();
try{
//1 判断
if(number != 2){
condition2.await();
}
//2 打印
for(int i=1;i<=15;i++){
System.outprintln(Thread.currentThread().getName()+"\t"+i+"\t"+totalLoop);
}
//3 唤醒
number=3;
condition3.signal();
}catch(Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
public void loopC(){
lock.lock();
try{
//1 判断
if(number != 3){
condition3.await();
}
//2 打印
for(int i=1;i<=20;i++){
System.outprintln(Thread.currentThread().getName()+"\t"+i+"\t"+totalLoop);
}
//3 唤醒
number=1;
condition1.signal();
}catch(Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
}
public static void main(){
AlternateDemo ad = new AlternateDemo();
new Thread(new Runnable()){
public void run(){
for(int i=1;i<20;i++){
ad.lockA(i);
}
}
},"A").start();
}