还在为cpu突然飙升烦恼吗? 如果是的话,好好看看这篇文章吧,兴许能帮到你。

1. 死锁

死锁一般发生在两个或者多个线程一直在等待另一个线程释放锁或者资源的情形。这种情况可能会导致线程直接挂掉,因为都在等待锁。

1.代码示例

两个线程在完成指定操作时,都需要获取锁,T1线程是lock1->lock2,T2线程是lock2->lock1。因此两个线程都在以相反的顺序获取锁。

@GetMapping("/deadlock")
    public void deadlock() {
        new Thread(() -> {
            lock1.lock();
            log.info(Thread.currentThread().getName() + " 抢占到锁lock1,等待抢lock2");
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock2.lock();
            log.info(Thread.currentThread().getName() + " 抢占到锁2");
            lock2.unlock();
            lock1.unlock();
            log.info(Thread.currentThread().getName() + "  thread 执行完成");
        }, "T1").start();
        new Thread(() -> {
            lock2.lock();
            log.info(Thread.currentThread().getName() + " 抢占到锁lock2,等待抢lock1");
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock1.lock();
            log.info(Thread.currentThread().getName() + " 抢占到锁1");

            lock1.unlock();
            lock2.unlock();
            log.info(Thread.currentThread().getName() + " thread 执行完成");
        }, "T2").start();
    }

运行该例子看下输出:

ThreadController     : T1 抢占到锁lock1,等待抢lock2
ThreadController     : T2 抢占到锁lock2,等待抢lock1

一旦运行就永远也不会退出了,永远的等在了这里,也就是死锁了。

2.怎么避免死锁

死锁在Java中是很常见的并发问题,我们应该要从以下几点去避免。

  • 避免一个线程获取多个锁
  • 一个线程如果需要获取多个锁,应该按相同的顺序获取锁,来避免获取锁时的循环依赖问题
  • 使用带有超时的锁,例如Lock中的tryLock方法,避免线程如果获取不到锁也不会一直阻塞

2.活锁

活锁是另一个并发问题,它和死锁类似。
在活锁中,两个或多个线程彼此间一直在转移锁的状态,而不是像上个例子中相互等待。最终结果就是所有线程都不能执行他们各自的任务。

1.活锁代码示例

线程需要获取两把锁才能完成操作,线程在获取到第一把锁后尝试获取l第二把锁,但是都拿不到第二把锁,因此为了让另一个线程完成任务,每个线程都会释放第一把锁并会尝试再次获取两把锁。

@GetMapping("/liveLock")
    public void liveLock() {
        new Thread(() -> {
            while (true) {
                try {
                    lock1.tryLock(3, TimeUnit.SECONDS);
                    log.info(Thread.currentThread().getName() + " 抢占到锁lock1,等待抢lock2");
                    Thread.sleep(50);
                    if (lock2.tryLock()) {
                        log.info(Thread.currentThread().getName() + " 抢占到锁lock2");
                    } else {
                        log.info(Thread.currentThread().getName() + " 不能抢占到锁lock2,是否锁lock1");
                        lock1.unlock();
                        continue;
                    }
                    break;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                lock2.unlock();
                lock1.unlock();
            }
        }, "T1").start();

        new Thread(() -> {
            while (true) {
                try {
                    lock2.tryLock(3, TimeUnit.SECONDS);
                    log.info(Thread.currentThread().getName() + " 抢占到锁lock2,等待抢lock1");
                    Thread.sleep(50);
                    if (lock1.tryLock()) {
                        log.info(Thread.currentThread().getName() + " 抢占到锁lock1");
                    } else {
                        log.info(Thread.currentThread().getName() + " 不能抢占到锁lock1,是否锁lock2");
                        lock2.unlock();
                        continue;
                    }
                    break;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                lock1.unlock();
                lock2.unlock();
            }
        }, "T2").start();
    }

运行一下看下输出结果:

ThreadController     : T1 抢占到锁lock1,等待抢lock2
ThreadController     : T2 抢占到锁lock2,等待抢lock1
ThreadController     : T2 不能抢占到锁lock1,释放锁lock2
ThreadController     : T1 不能抢占到锁lock2,释放锁lock1
ThreadController     : T2 抢占到锁lock2,等待抢lock1
ThreadController     : T1 抢占到锁lock1,等待抢lock2
ThreadController     : T2 不能抢占到锁lock1,释放锁lock2
ThreadController     : T1 不能抢占到锁lock2,释放锁lock1

看到输出结果,两个线程都在重复的获取锁和释放锁,两个线程都不能完成操作。

2.怎么避免活锁

  • 多个线程重复的获取锁和释放锁导致了活锁,可以修改下代码加一个随机时间间隔来获取锁,这样就有机会获取到需要的锁了。
  • 消息队列活锁(消费者异常回滚事务并把消息丢到队列头部,然后相同的消息又被取出造成异常再次放入队列头中,以此循环,导致再也消费不到队列中的其他消息),这种情况一定要添加重试次数,避免死循环消费

3.Java死锁的定位方法

把上述代码本地跑起来或者丢liunx服务运行起来,调用死锁接口

#通过jps查看查看运行的java服务 找到我们的这个服务的进程号
[root@localhost ~] jps
4172 ThreadTsetApplication
#查看对应的服务的栈日志 是否存在deadlock
[root@localhost ~] jstack 4172
#查看日志的最后面,有死锁的话都会打印出来
#找到一个死锁
Found one Java-level deadlock:
=============================
"T2":
#T2在等在0x0000000772906c20这把锁,这把锁的持有是T1
  waiting for ownable synchronizer 0x0000000772906c20, (a java.util.concurrent.locks.ReentrantLock$FairSync),
  which is held by "T1"
"T1":
#T1在等在0x0000000772906c50这把锁,这把锁的持有是T2
  waiting for ownable synchronizer 0x0000000772906c50, (a java.util.concurrent.locks.ReentrantLock$FairSync),
  which is held by "T2"

Java stack information for the threads listed above:
===================================================
"T2":
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x0000000772906c20> (a java.util.concurrent.locks.ReentrantLock$FairSync)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
        at java.util.concurrent.locks.ReentrantLock$FairSync.lock(ReentrantLock.java:224)
        at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
        at 
		#对应我们代码里的位置
        com.example.demo.controller.ThreadController.lambda$deadlock$1(ThreadController.java:44)
        at com.example.demo.controller.ThreadController$$Lambda$466/2023309149.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)
"T1":
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x0000000772906c50> (a java.util.concurrent.locks.ReentrantLock$FairSync)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
        at java.util.concurrent.locks.ReentrantLock$FairSync.lock(ReentrantLock.java:224)
        at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
        at com.example.demo.controller.ThreadController.lambda$deadlock$0(ThreadController.java:30)
        at com.example.demo.controller.ThreadController$$Lambda$465/1629553669.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

4.Java服务器CPU突然飙升问题定位

常见于死循环,为了方便定位准备如下代码

@GetMapping("/while")
    public void while1() throws InterruptedException {
        while (true){
            Thread.sleep(1000);
            System.out.println(1111);
        }
    }

丢到服务器直接运行,调用。接下来开始定位

#找出最耗CPU的进程服务ID
[root@localhost ~] top -c
1701 root      20   0 6085924 440588  13464 S  98.0  3.1   2:34.80 java -jar thread-test-1.0.0.jar 
#找出对应服务ID中最耗CPU的线程ID
[root@localhost ~] top -H -p 1701
1742 root      20   0 6085924 431140  13480 R 98.7  3.0   3:58.39 java 
#将上面线程ID转换16进制
[root@localhost ~] printf '0x%x\n' 1742
#在栈日志中搜索这个十六进制号查看30行 能看到该线程的状态以及位置
[root@localhost ~] jstack 1701 | grep 0x6ce -A 30
"http-nio-9999-exec-1" #15 daemon prio=5 os_prio=0 tid=0x00007fca7d022800 nid=0x6ce runnable [0x00007fca5d66c000]
	#线程还在运行中
   java.lang.Thread.State: RUNNABLE
	at java.io.PrintStream.newLine(PrintStream.java:545)
	- eliminated <0x00000006e74d0358> (a java.io.PrintStream)
	at java.io.PrintStream.println(PrintStream.java:737)
	- locked <0x00000006e74d0358> (a java.io.PrintStream)
	at 
	#在我们的java中的方法
	Hcom.example.demo.controller.ThreadController.while1(ThreadController.java:105)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:888)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793)
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)

好了,以上就是Java中的死锁、活锁以及Java服务器CPU突然飙升问题排查的全部内容了。