学习网站:https://www.bilibili.com/video/BV1B7411L7tE?p=39&spm_id_from=pageDriver
Syncheonized与Lock的区别
1.Syncheonized 是java关键字,Lock是一个java类
2.Syncheonized 无法判断获取锁的状态,Lock可以判断是否获取到了锁
3.Syncheonized 会自动释放锁,Lock必须要手动释放锁,如果不释放会造成死锁
4.Syncheonized 线程1(获得锁阻塞),线程2(会进行等待锁),Lock不一定会等待
5.Syncheonized可重入锁,不可中断的,
6.Syncheonized适合锁少量的代码,Lock适合锁大量的代码
集合类多线程安全问题,使用普通的集合会出现线程安全问题,错误如下:
java.util.ConcurrentModificationException
使用如下办法可以解决:
List<String> list = Collections.synchronizedList(new ArrayList<>());//使用工具类保证线程安全
List<String> list = new CopyOnWriteArrayList<>(); //写入时复制
Callable的使用:
创建一个类实现Callable的类,再创建一个FutureTask(new MyCallable()),最后new Thread(futureTask);注意futureTask只能执行一次
FutureTask task = new FutureTask(new MyCallable());
new Thread(task).start();
class MyCallable implements Callable{String run(){ return "String"; }}
JUC三大常用辅助类CountDownLatch,CycleBarrier,Semaphore
CountDownLatch:里面有一个计数器,每次调用需要手动减一,调用countDownLatch.await()可以当数量为一的时候执行
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(5);//总数是5
for (int i = 0; i < 5; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "go out");
countDownLatch.countDown();//计数减一
},String.valueOf(i)).start();
}
countDownLatch.await();//等待计数器归零,才能乡下执行
System.out.println("close");
}
}
Semaphore:共享资源的使用
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3); //线程数量,停车位
for (int i = 0; i < 6; i++) {
new Thread(()->{
try {
semaphore.acquire();//获得,加入已经满了,就等待释放
System.out.println(Thread.currentThread().getName()+"强盗车位");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"离开了车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();//释放,
}
},String.valueOf(i)).start();
}
}
}
读写锁,读可以多个线程一起读,而写必须一个一个的来,读写也是一样
/*
独占锁(写锁) 一次只能一个线程占有
共享锁一次可以被多个线程占有
读-读 可以共享
读-写 不能共享
写-写 不能共享
*/
public class ReadWriteLockDemo {
public static void main(String[] args) {
//MyCache myCache = new MyCache(); 不加锁
MyCacheLock myCache = new MyCacheLock();
for (int i = 0; i < 5; i++) {
final int temp = i;
new Thread(()->{
myCache.put(temp + "",temp + "");
},String.valueOf(i)).start();
}
for (int i = 0; i < 5; i++) {
final int temp = i;
new Thread(()->{
myCache.get(temp + "");
},String.valueOf(i)).start();
}
}
}
class MyCache{ //自定义缓存
private volatile Map<String,Object> map = new HashMap<>();
public void put(String key, Object value){
System.out.println(Thread.currentThread().getName() + "写入" + key);
map.put(key,value);
System.out.println(Thread.currentThread().getName() + "写入ok");
}
public void get(String key){
System.out.println(Thread.currentThread().getName() + "读取" + key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取ok");
}
}
class MyCacheLock{ //自定义缓存
private volatile Map<String,Object> map = new HashMap<>();
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void put(String key, Object value){
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "写入" + key);
map.put(key,value);
System.out.println(Thread.currentThread().getName() + "写入ok");
} catch (Exception e){
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
public void get(String key){
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "读取" + key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取ok");
} catch (Exception e){
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
阻塞队列,ArrayBlockingQueue,SynchronousQueue
ArrayBlockingQueueapi使用,参考如下
方式 | 抛出异常 | 有返回值,不抛出异常 | 阻塞等待 | 超市等待 |
---|---|---|---|---|
添加 | add | offer() | put() | offer(...) |
移除 | remove | poll() | take() | poll(...) |
检查队首元素 | element | peek() |
public class Test01 {
public static void main(String[] args) throws InterruptedException {
test4();
}
public static void test1(){ //抛出异常
ArrayBlockingQueue<Object> 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("d")); //出国队列大小抛出异常
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove()); //抛出异常,
}
public static void test2(){
ArrayBlockingQueue 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("c")); //不抛出异常
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.peek());
//System.out.println(blockingQueue.poll()); //不抛出异常,
}
public static void test3() throws InterruptedException {
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
//blockingQueue.put("d"); 队列没有位置了,一直阻塞
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take()); //没有这个元素,等待元素
}
public static void test4() throws InterruptedException {
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);
blockingQueue.offer("a",3, TimeUnit.SECONDS);
blockingQueue.offer("b",3, TimeUnit.SECONDS);
blockingQueue.offer("c",3, TimeUnit.SECONDS);
// blockingQueue.offer("a",3, TimeUnit.SECONDS); 超时退出
System.out.println(blockingQueue.poll(3,TimeUnit.SECONDS));
System.out.println(blockingQueue.poll(3,TimeUnit.SECONDS));
System.out.println(blockingQueue.poll(3,TimeUnit.SECONDS));
//System.out.println(blockingQueue.poll(3,TimeUnit.SECONDS)); 超时等待3秒,3秒没有则返回
}
}
SynchronousQueue:同步队列,存入一个数据,必须把这个数据取出来的时候才能存入下一个数据
public class SynTest {
public static void main(String[] args) {
BlockingQueue blockingQueue = new SynchronousQueue();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName() + " put 1");
blockingQueue.put("1");
System.out.println(Thread.currentThread().getName() + " put 2");
blockingQueue.put("2");
System.out.println(Thread.currentThread().getName() + " put 3");
blockingQueue.put("3");
}catch (Exception e){
e.printStackTrace();
}
},"T1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + " " + blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + " " + blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + " " + blockingQueue.take());
}catch (Exception e){
e.printStackTrace();
}
},"T2").start();
}
}
线程池,三大方法,七大参数,四大拒绝策略。线程池的优点:,节约资源,易于管理
public class Test01 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor(); //单列线程
//ExecutorService executorService = Executors.newCachedThreadPool(); //根据情况自动创建线程
//ExecutorService executorService = Executors.newFixedThreadPool(5); //固定线程数
try {
for (int i = 0; i < 10; i++) {
executorService.submit(()->{
System.out.println(Thread.currentThread().getName());
});
}
} catch (Exception e){
e.printStackTrace();
} finally {
executorService.shutdown();
}
}
}
方法一
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>());
}
方法三
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
底层代码
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;
}
拒绝策略
new ThreadPoolExecutor.AbortPolicy() //线程池开启了最大线程数,线程排队也满了,则不处理这个人,抛出异常
new ThreadPoolExecutor.CallerRunsPolicy() //哪来的去哪里,有点可怜
new ThreadPoolExecutor.DiscardOldestPolicy()//尝试和最早的竞争,也不会抛出异常
new ThreadPoolExecutor.DiscardPolicy() //线程池不能接收处理了,丢掉任务,不会抛出异常
原生线程池创建方法
CPU密集型:maxPool几核就是几
IO密集型:判断任务程序中耗费io的线程,15就可以创建为30个
Runtime.getRuntime().availableProcessors() //获取CPU核数
四大函数式接口新时代的程序员:lambda表达式,链式编程,函数式接口,Stream流式计算
函数
函数式接口:只有一个方法的接口,以下是常见的几种类型:
public class Demo02 { //Function 函数式接口,只有一个输入参数,有一个输出,只要是含属性接口,可以用lambda表达简化
public static void main(String[] args) {
Function<String,String> function1 = new Function<String, String>() {
public String apply(String str) { return str; }
};
Function<String,String> function2 = (str) ->{return str;};
System.out.println(function1.apply("123") + function2.apply("345"));
}
}
public class Demo03 { //断定型接口
public static void main(String[] args) {
Predicate<String> predicate1 = new Predicate<String>() {
public boolean test(String s) { return s.isEmpty(); }
};
Predicate<String> predicate2 = (str) ->{return str.isEmpty();};
System.out.println(predicate1.test("23") + "" + predicate2.test("34"));
}
}
public class Test03 {//消费型接口,只有输入,没有返回结果
public static void main(String[] args) {
Consumer<String> consumer1 = new Consumer<String>() {
public void accept(String s) { System.out.println(s); }
};
Consumer<String> consumer2 = (str)-> System.out.println(str);
consumer1.accept("we");
consumer2.accept("ddd");
}
}
public class Demo04 {//供给型接口,没有参数只有返回值
public static void main(String[] args) {
Supplier supplier1 = new Supplier<Integer>() {
public Integer get() { return 1024; }
};
Supplier supplier2 = ()-> {return 1024;};
System.out.println(supplier1.get() + "" + supplier2.get());
}
}
stream流式计算
public class Test01 {
public static void main(String[] args) {
User u1 = new User(1,"a1",11);
User u2 = new User(2,"b2",22);
User u3 = new User(3,"c3",33);
User u4 = new User(4,"d4",44);
User u5 = new User(6,"f6",66);
List<User> list = Arrays.asList(u1,u2,u3,u4,u5); //集合就是存储
list.stream()
.filter(user -> {return user.getId()%2==0;}) //过滤选择
.filter(user -> {return user.getAge()>30;})
.map(user -> {return user.getName().toUpperCase();})
.sorted((user1,user2)->{return user2.compareTo(user1);}) //排序
.forEach(System.out::println);
}
}
ForkJoin(jdk1.7),将大任务划分为小任务,任务窃取(先完成任务的会帮别人分担一点任务),看一下以下几种方式:
/*
求和计算的任务
如何使用forkjoin
1.forkjoinPool 通过它来执行
2.计算任务,forkjoinPool.execute(ForkJoinTask task)
3.计算类继承ForkjoinTask
*/
public class ForkJoinDemo extends RecursiveTask<Long> {
private Long start;
private Long end;
private Long temp = 10000L;
public ForkJoinDemo(Long start,Long end){
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
if((end - start) < temp){
Long sum = 0L;
for(Long i = start; i <= end ; i++){
sum += i;
}
return sum;
}else {
Long middle = (start + end) / 2;
ForkJoinDemo task1 = new ForkJoinDemo(start,middle);
task1.fork(); //执行任务,把人物压入线程队列
ForkJoinDemo task2 = new ForkJoinDemo(middle + 1,end);
task2.fork();
return task1.join() + task2.join();
}
}
}
public class Test02 { //求和计算
public static void main(String[] args) throws ExecutionException, InterruptedException {
// test1();
test3();
}
public static void test1(){
Long sum = 0L;
Long start = System.currentTimeMillis();
for(Long i = 0L; i <= 10_0000_0000 ; i++){
sum += i;
}
Long end = System.currentTimeMillis();
System.out.println("sum = " + sum + "时间:" + (end -start));
}
public static void test2() throws ExecutionException, InterruptedException {
Long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoinDemo(0L,10_0000_0000L);
ForkJoinTask<Long> submit = forkJoinPool.submit(task);
Long sum = submit.get();
Long end = System.currentTimeMillis();
System.out.println("sum = " + sum + "时间:" + (end -start));
}
public static void test3(){
Long start = System.currentTimeMillis();
Long sum = LongStream.rangeClosed(0L,10_0000_0000L).parallel().reduce(0,Long::sum);
Long end = System.currentTimeMillis();
System.out.println("sum = " + sum + "时间:" + (end -start));
}
}
异步任务,感觉和线程没有太大区别,代码如下所示
public class Test03 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//没有返回值
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
});
System.out.println("12345");
completableFuture.get();
//有返回值的
CompletableFuture<Integer> completableFuture1 = CompletableFuture.supplyAsync(()->{
System.out.println("有返回值");
int i = 1/0;
return 1024;
});
System.out.println(completableFuture1.whenComplete((t, u) -> {
System.out.println("t=>" + t); //t为正常的返回值
System.out.println("u=>" + u); //u为发生错误时候的异常信息
}).exceptionally((e) -> {
System.out.println(e.getMessage());
return 233;
}).get());
}
}
JMM:是一种概念,一种约定
1.线程解锁前,必须把共享变量立即刷回主存
2.线程枷锁前,必须读取竹村中的最新值取到工作内存中
3.加锁和解锁是同意把锁
Volatile是java虚拟机提供轻量级的同步机制
1.保证可见性
2.不保证原子性 //AtomicInteger可以解决
3.禁止指令重排
单列模式
饿汉式
public class Hungry { //饿汉式,加载单列模式,可能会浪费内存资源
private byte[] data1 = new byte[1024];
private byte[] data2 = new byte[1024];
private byte[] data3 = new byte[1024];
private Hungry(){}
private final static Hungry hungry = new Hungry();
public static Hungry getHungry(){
return hungry;
}
}
public class LazyMan {
private static boolean flag = false; //制裁无限获取
private LazyMan(){
System.out.println("dd");
synchronized (LazyMan.class){
if(flag == false){
flag = true;
}else {
throw new RuntimeException("不要试图使用反射破坏单列模式");
}
//if (lazyMan != null){//反射这里没有进行初始化,所以可以通过反射无限获取
// throw new RuntimeException("不要试图使用反射破坏单列模式");
//}
}
}
private volatile static LazyMan lazyMan;
public static LazyMan getLazyMan(){
if(lazyMan==null){
synchronized (LazyMan.class){
if(lazyMan==null){
lazyMan = new LazyMan();
// 1.分配内存
// 2.执行构造方法,初始化对象
// 3.把这个对象指向这个空间
// 单线程下 执行1 2 3, 1 3 2都是一样的,但是多线程下面第一个线程执行1 3 2在3的时候就部位空,
// 这时候有一个线程执行第一个锁不为空,返回对象,但是并没有初始化,所以不安全,这时加上volatile,这时指令就不可以重排了
}
}
}
return lazyMan;
}
public static void main(String[] args) throws Exception {
//LazyMan instance1 = LazyMan.getLazyMan();
Field flag = LazyMan.class.getDeclaredField("flag");//通过反射制裁开关
flag.setAccessible(true);
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance1 = declaredConstructor.newInstance();
flag.set(instance1,false);
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
public class Holder { //静态内部类解决单列模式,也是不安全的,可以使用反射构造
private Holder(){}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws Exception {
EnumSingle instance1 = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumSingle enumSingle1 = declaredConstructor.newInstance();
}
}
通过如上进行代码测试,只有枚举类型才是单列安全的
CAS(compare and swap):比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行交换!如果不不是则一直循环
缺点:
1.循环会耗时
2.一次性只能保证一个共享变量的原子性
3.ABA问题
原子引用(解决ABA的问题,在数据上卖弄添加一个版本,版本更换表明被修改过的,注意-128~127才可以直接传入,超过范围请使用对象)
public class Stamp {
public static void main(String[] args) {
Integer ref = new Integer(2020);
Integer newref = new Integer(2022);
Integer three = new Integer(2024);
AtomicStampedReference<Integer> atomicInteger = new AtomicStampedReference<>(ref,1);
new Thread(()->{
int stamp = atomicInteger.getStamp();
System.out.println("version1=>" + stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("stamp1=>" + atomicInteger.compareAndSet(ref, newref, atomicInteger.getStamp(), atomicInteger.getStamp() + 1));
System.out.println("stamp1=>" + atomicInteger.getStamp());
System.out.println("stamp2=>" + atomicInteger.compareAndSet(newref, ref, atomicInteger.getStamp(), atomicInteger.getStamp() + 1));
System.out.println("stamp2=>" + atomicInteger.getStamp());
},"a").start();
new Thread(()->{
int stamp = atomicInteger.getStamp();
System.out.println("version2=>" + stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("b=>" + atomicInteger.compareAndSet(ref, three, atomicInteger.getStamp(), atomicInteger.getStamp() + 1));
System.out.println("b=>" + atomicInteger.getStamp());
},"b").start();
}
}
各种锁的理解
1.公平锁,非公平锁
公平锁:先来后到,不允许插队
非公平锁:不允许插队(默认是非公平锁)
2.可重入锁,所有的锁都是可重入锁,递归锁
sychronized:
public class Demo01 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sms();
},"A").start();
new Thread(()->{
phone.sms();
},"B").start();
}
}
class Phone{
public synchronized void sms(){
System.out.println(Thread.currentThread().getName() + "sms");
call();
}
public synchronized void call(){
System.out.println(Thread.currentThread().getName() + "call");
}
}
Lock:同意线程里面必须进行配对解锁,否则其他线程可能会因为获取不到锁而处于死锁状态
public class Demo02 {
public static void main(String[] args) {
Phone2 phone = new Phone2();
new Thread(()->{
phone.sms();
},"A").start();
new Thread(()->{
phone.sms();
},"B").start();
}
}
class Phone2{
Lock lock = new ReentrantLock();
public synchronized void sms(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "sms");
call();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public synchronized void call(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "call");
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
3.自旋锁
public class Demo03 { //自旋锁
public static void main(String[] args) {
SpinLockTest lock = new SpinLockTest();
new Thread(()->{
lock.mylock();
try {
TimeUnit.SECONDS.sleep(2);
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.myunlock();
}
},"A").start();
new Thread(()->{
lock.mylock();
try {
TimeUnit.SECONDS.sleep(2);
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.myunlock();
}
},"B").start();
}
}
class SpinLockTest{
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void mylock(){
Thread thread = Thread.currentThread();
while (!atomicReference.compareAndSet(null,thread)); //第二个线程会等待第一个线程释放锁之后,才能获取锁,否则死循环
System.out.println(thread.getName() + "=>mylock");
}
public void myunlock(){
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread,null);
System.out.println(thread.getName() + "=>myunlock");
}
}
4.死锁:一般是由多个线程互相抢夺资源造成死锁
public class Demo04 {//使用jps定位问题
public static void main(String[] args) {
String srcA = "aaaa";
String srcB = "bbbb";
new Thread(new TestRunnable(srcA,srcB),"T1").start();
new Thread(new TestRunnable(srcB,srcA),"T2").start();
}
}
class TestRunnable implements Runnable{
private String srcA;
private String srcB;
public TestRunnable(String srcA,String srcB){
this.srcA = srcA;
this.srcB = srcB;
}
public void run() {
synchronized (srcA){
try {
System.out.println(Thread.currentThread().getName() + " locked=>" + srcA);
TimeUnit.SECONDS.sleep(2);
synchronized (srcB){
System.out.println(Thread.currentThread().getName() + " locked=>" + srcB);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
jps -l :查看当前进程
![](https://img.haomeiwen.com/i23472197/c2b46c4ccd6e58b9.png)
jstack pid:查看死锁问题
![](https://img.haomeiwen.com/i23472197/6d1f573ed7934ec7.png)
网友评论